[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  // This tells ESLint to load the config from the package `eslint-config-custom`\n  extends: [\"custom\"],\n  settings: {\n    next: {\n      rootDir: [\"apps/*/\"],\n    },\n  },\n  rules: {\n    \"react-hooks/exhaustive-deps\": \"off\",\n  },\n  // eslint-config-next causes warning on Remix's default remix.config.js\n  ignorePatterns: [\"recipes/remix/remix.config.js\"],\n};\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/bug-report.md",
    "content": "---\nname: \"Bug Report 🐛\"\nabout:\n  Report a problem with Puck. Please provide enough information to reproduce\n  the problem.\ntitle: \"\"\nlabels: [\"type: bug 🐛\"]\nassignees: \"\"\n---\n\n## Description\n\n[Example: \"The `<Puck />` component doesn't render correctly inside a CSS grid layout...\"]\n\n<!--\n  Provide a clear and concise description of the bug.\n  Don't assume we know anything about your repository or codebase.\n  Keep it centered around Puck—avoid detailing your use case unless it directly helps explain the issue.\n  Test the issue using the latest version of Puck to confirm it hasn't already been fixed.\n-->\n\n## Environment\n\n- Puck version: [0.19.0, 1.0.0...]\n- Browser version: [Chrome 135 (desktop), Firefox 133 (mobile)...]\n- Additional environment info: [bundler, OS, device type...]\n\n<!--\n  Detail the environment where the bug is occurring.\n-->\n\n## Steps to reproduce\n\n1. Render the `<Puck />` component in a grid layout...\n\n```tsx\nconst Editor = () => {\n  return (\n    <div style={{ display: \"grid\" }}>\n      <Puck config={config} data={data} />\n    </div>\n  );\n};\n```\n\n2. Run the application in development mode...\n\n<!--\n  Provide clear steps with code examples so that we can reproduce the bug.\n  Avoid including dependencies other than Puck.\n  Issues without reproduction steps or code examples may be closed as not actionable.\n  For help on providing minimal, reproducible examples: https://stackoverflow.com/help/mcve\n-->\n\n## What happens\n\n[Example: \"A white screen appears and the editor doesn't load...\"]\n\n<!--\n  State what is the result of the steps above.\n  Keep the explanation short and clear.\n-->\n\n## What I expect to happen\n\n[Example: \"The Puck component should render correctly in any CSS layout...\"]\n\n<!--\n  State what was the result you expected from the steps above.\n  Keep the explanation short and clear.\n-->\n\n## Additional Media\n\n<!--\n  Include any screenshots, videos, or other relevant media that may help\n  visualize the issue or demonstrate the behavior.\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n    - name: Guidance - Discord 👾\n      url: https://discord.gg/V9mDAhuxyZ\n      about: This issue tracker is not for guidance. Please refer to the Discord server for live help and general guidance.\n    - name: Questions and Help - Github Discussions 💬\n      url: https://github.com/puckeditor/puck/discussions\n      about: This issue tracker is not for support questions. Please open a discussion for that."
  },
  {
    "path": ".github/ISSUE_TEMPLATE/feature_request.md",
    "content": "---\nname: \"Feature Request ✨\"\nabout: \"Share ideas for new features.\"\ntitle: \"\"\nlabels: [\"type: feature\"]\nassignees: \"\"\n---\n\n## Description\n\n[Example: \"Currently, the `text` field doesn’t support validation for maximum and minimum lengths. However, in many cases, you need to limit user input to specific constraints...\"]\n\n<!--\n  Describe the expected outcome and why you want that outcome.\n  Don’t provide implementation details, save that for the 'Proposals' section.\n  Assume the person reading this has zero context about your problem or use case.\n-->\n\n## Considerations\n\n- This might be related to field validation, which is currently being tracked in issue: #1...\n- The file implementing the `text` field lives at `core/text.tsx`...\n\n<!--\n  List any special considerations for this feature that might help or make the changes more difficult.\n  List any other issues that might be related or affected by this feature.\n-->\n\n## Proposals\n\n### Proposal 1\n\n[Example: \"Introduce `min` and `max` props for the field configuration object and the `<AutoField />` component...\"]\n\n<!--\n  Add a high-level description of how you'd like to see the feature implemented.\n  Include code examples if appropriate.\n  Discuss the pros and cons of the approach where necessary.\n-->\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file\n\nversion: 2\nupdates:\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      # Check for updates to GitHub Actions every week\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/pull_request_template.md",
    "content": "Closes #XXXX\n\n<!--\n  Replace XXXX with the actual issue number this PR closes.\n  Every PR should be linked to an issue.\n  PRs without an issue may take longer to review or may be closed as non-actionable.\n-->\n\n## Description\n\nThis PR adds a `style` prop to the `Puck` component to allow customization of the editor layout styles.\n\n<!--\n  Include a concise and clear description of what this PR does.\n  Mention any considerations or reasons behind the changes.\n  Highlight any breaking changes.\n  Keep the explanation centered around Puck.\n -->\n\n## Changes made\n\n- The `Puck` component now receives an optional `style` prop and passes it to the editor `div` wrapper.\n\n<!--\n  List the key changes made and the reasons behind them.\n -->\n\n## How to test\n\n- Render the `Puck` component with a two-column grid layout using the `style` prop and confirm it renders in two columns:\n\n```tsx\n<Puck style={{ display: \"grid\", gridTemplateColumns: \"1fr 1fr\" }} />\n```\n\n<!--\n  List any manual tests you did to verify the behavior of the changes.\n  Add any media or screenshots that may help verify the outcome.\n -->\n"
  },
  {
    "path": ".github/workflows/ci.yml",
    "content": "# This workflow will run all checks required for a PR to be merged.\n\nname: Build and Test ci\n\non:\n  push:\n    branches: [main]\n  pull_request:\n    branches: [main]\n\njobs:\n  build-and-test:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout code\n        uses: actions/checkout@v6.0.2\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 20\n\n      - name: Check Yarn Version\n        run: yarn --version\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Run tests\n        run: yarn test\n\n      - name: Check linting and formatting\n        run: |\n          if yarn lint && yarn format:check; then\n            echo \"Linting and formatting checks passed.\"\n          else\n            echo \"Linting or formatting checks failed. Please fix the issues.\"\n            exit 1\n          fi\n\n      - name: Build everything\n        run: |\n          yarn build\n\n      - name: Check for build failures\n        run: |\n          if [ $? -ne 0 ]; then\n            echo \"Build failed. Please fix the issues.\"\n            exit 1\n          fi\n"
  },
  {
    "path": ".github/workflows/publish-canary.yml",
    "content": "name: Publish canary release\non:\n  push:\n    branches:\n      - \"main\"\n      - \"releases/**\"\n\njobs:\n  tag-and-publish-to-npm:\n    runs-on: ubuntu-latest\n\n    # Don't run on regular releases\n    if: \"!startsWith(github.event.head_commit.message, 'release: ')\"\n    steps:\n      - uses: actions/checkout@v6.0.2\n        with:\n          fetch-depth: 0\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 20\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Run release script\n        run: yarn release:canary\n\n      - name: Publish all packages\n        run: ./scripts/publish.sh canary\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n\n    timeout-minutes: 10\n"
  },
  {
    "path": ".github/workflows/publish.yml",
    "content": "name: Publish release\non:\n  push:\n    branches:\n      - \"releases/**\"\npermissions:\n  contents: write\njobs:\n  tag-and-publish-to-npm:\n    runs-on: ubuntu-latest\n    if: \"startsWith(github.event.head_commit.message, 'release: ')\"\n    steps:\n      - uses: actions/checkout@v6.0.2\n      - name: Extract version\n        shell: bash\n        run: echo \"TAG_NAME=$(git log -1 --oneline --pretty=%B | sed 's/release:\\ //g')\" >> $GITHUB_ENV\n      - name: Tag commit\n        uses: tvdias/github-tagger@v0.0.1\n        with:\n          repo-token: \"${{ secrets.GH_TOKEN }}\"\n          tag: ${{ env.TAG_NAME }}\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v6\n        with:\n          node-version: 20\n          registry-url: \"https://registry.npmjs.org\"\n\n      - name: Install dependencies\n        run: yarn\n\n      - name: Publish all packages\n        run: ./scripts/publish.sh latest\n        env:\n          NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}\n\n      # Trigger a new build to ensure Vercel creates a distinct release branch, rather than reusing latest\n      - name: Triggering new build\n        run: |\n          git config --global user.name 'chrisvxd'\n          git config --global user.email 'chrisvxd@users.noreply.github.com'\n          git commit -m \"ci: trigger build\" --allow-empty\n          git push\n\n    timeout-minutes: 10\n"
  },
  {
    "path": ".gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\nnode_modules\n.pnp\n.pnp.js\n\n# testing\ncoverage\n\n# next.js\n.next/\nout/\nbuild\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# turbo\n.turbo\n\n# vercel\n.vercel\n\n# webstorm\n.idea\n\ndist"
  },
  {
    "path": ".npmrc",
    "content": "auto-install-peers = true\n"
  },
  {
    "path": ".nvmrc",
    "content": "20"
  },
  {
    "path": ".prettierignore",
    "content": "CHANGELOG.md\n\n/.nx/workspace-data\n"
  },
  {
    "path": ".prettierrc.json",
    "content": "{}\n"
  },
  {
    "path": ".yarnrc",
    "content": "version-git-message \"release: v%s\""
  },
  {
    "path": "CHANGELOG.md",
    "content": "# CHANGELOG\n\n<!--__CHANGELOG_ENTRY__-->\n\n## [0.21.1](https://github.com/measuredco/puck/compare/v0.21.0...v0.21.1) (2026-01-23)\n\n\n### Bug Fixes\n\n* don't throw when disabling listItem rich text extension ([4ca0a46](https://github.com/measuredco/puck/commit/4ca0a46e895e8a378bc743c4c06451dca8c00838))\n* enforce disabled styles for rich text menu with no options ([3d56139](https://github.com/measuredco/puck/commit/3d56139a84912fcc5cc13142bd31df8b2d187368))\n* ensure consistent plugin ordering in Firefox ([274c5f6](https://github.com/measuredco/puck/commit/274c5f6abd8585d015ece2b59d11da0b4914b6d0))\n* ensure nested richtext fields render in <Render/> ([501a164](https://github.com/measuredco/puck/commit/501a1649afbb5fb4125146541e63b0f3fec3a962))\n* prevent array item expansion when empty ([1a6682a](https://github.com/measuredco/puck/commit/1a6682a3bc0eaaae2f6ffe1a2d20656d8d647a65))\n* prevent block deletion on backspace in rich text in Firefox ([71c8664](https://github.com/measuredco/puck/commit/71c8664dd7f0fe7d971ebcd4992167df2e45bcc1)), closes [#1529](https://github.com/measuredco/puck/issues/1529)\n* replace missed fast-deep-equals with fast-equals ([ffa5171](https://github.com/measuredco/puck/commit/ffa5171c1732875df9a82934a4924b0e86750161))\n\n\n\n\n## [0.21.0](https://github.com/measuredco/puck/compare/v0.20.2...v0.21.0) (2026-01-14)\n\n\n### Bug Fixes\n\n* account for drags during slow resolveData on insert ([dfecd01](https://github.com/measuredco/puck/commit/dfecd012a0f1a7527f2d3b58449448d3f770dc6e))\n* account for drags during slow resolveData on update ([304940a](https://github.com/measuredco/puck/commit/304940aedd9c7d8bf72686d923dc8c4d9b9b91b6))\n* add missing ref prop to Slot render types ([ac9eba2](https://github.com/measuredco/puck/commit/ac9eba2f603bcea4bfa9508af3e57272ed23eafa))\n* automatically set IDs for slots in root defaultProps ([f5820ce](https://github.com/measuredco/puck/commit/f5820ce9366bea60e02a62d8ed215dbb8a7d13fb))\n* check the user provides a name in create-puck-app ([b1de8bf](https://github.com/measuredco/puck/commit/b1de8bf3c155e177e6ff3e600dad85232508aad6))\n* don't unmount sidebars on hide ([ceebd0f](https://github.com/measuredco/puck/commit/ceebd0fb3deb5ce0853cbaf0aa37ffa8c24ed49c))\n* handle exits gracefully from create-puck-app ([abfa3c3](https://github.com/measuredco/puck/commit/abfa3c3972ebd1487e6495cbf40614bb9f1503a7))\n* memoize initialHistoryIndex and check the index provided by the user is not out of bounds ([ec77dd9](https://github.com/measuredco/puck/commit/ec77dd9f118f36f424be5695df95e8285d890102))\n* mitigate CVE-2025-57822 in Next.js recipe ([74d9a16](https://github.com/measuredco/puck/commit/74d9a16040dcc135dbac9cdd82792ef35383680c))\n* prevent empty objects from causing parent re-render ([a4bfae4](https://github.com/measuredco/puck/commit/a4bfae4f202ddaab12294cfffd2664d809e7cb54))\n* prevent frame overflowing on small viewports when embedded ([5c9515c](https://github.com/measuredco/puck/commit/5c9515c9b45b926ceb07da0a4c1dd74146ad2e9f))\n* prevent valueOf errors due to fast-deep-equals ([16a3eee](https://github.com/measuredco/puck/commit/16a3eee1a81f4df275dfbbb0903d4274ecaeb400))\n* prioritize disableDrag in overlay portals ([1df9dde](https://github.com/measuredco/puck/commit/1df9ddebfaf400ad6a65f90d4750dd95009f2549))\n* reset keystrokes when window is blurred ([5333fc7](https://github.com/measuredco/puck/commit/5333fc7e3d0a5b08b531aaccea60086f64d34b36))\n* respect custom root label in breadcrumb navigation ([de0baf3](https://github.com/measuredco/puck/commit/de0baf3937f5c902472c97c03c59c994b20643e4))\n* respect ui changes in replace action ([af2d5ac](https://github.com/measuredco/puck/commit/af2d5ac8534798f9460743f63f077b80019b2909))\n* restore valid type for renderDropZone prop ([29ef713](https://github.com/measuredco/puck/commit/29ef7131cfb43172c9194d2ab74fa89cc4bb630a))\n* retain overlay if dragRef changes ([e310a17](https://github.com/measuredco/puck/commit/e310a1755eda8dd1b60999c047bf0708440c9679))\n* run resolve permissions in slots ([cf074bc](https://github.com/measuredco/puck/commit/cf074bc6cb52e66e08bf6e36199ad9e89b0f19fb))\n* show title instead of undefined in React Router recipe ([68f8542](https://github.com/measuredco/puck/commit/68f8542800eae35f28863de2f53a31864e995618))\n* use latest reference for onChange callback ([b818cb1](https://github.com/measuredco/puck/commit/b818cb1fa24ba673d9264f1525b75bc62599e301))\n\n\n### Features\n\n* add \"as\" API to change slot element type ([1cec93c](https://github.com/measuredco/puck/commit/1cec93c4e698fba94a80bf0b4f6354b7317714fc))\n* add \"richtext\" field ([301595f](https://github.com/measuredco/puck/commit/301595f6232da4883eb3e5ed3b48aabeea7027ab))\n* add ActionBar.Separator component ([410819e](https://github.com/measuredco/puck/commit/410819e7cc6b507e6b95a9d1f07dd2721f567c81))\n* add active API to IconButton ([8116c86](https://github.com/measuredco/puck/commit/8116c86168a27a52655ff2dcdc7dfdda849abb22))\n* add cache control to external fields ([a5160e5](https://github.com/measuredco/puck/commit/a5160e5d600989c1651ae85ea05628ab0ed5cd1a))\n* add delete hotkeys for removing components ([20aafb6](https://github.com/measuredco/puck/commit/20aafb6a89f601c6a14fa0765f40288793977f4f))\n* add disabled option to ActionBar.Action ([32d0666](https://github.com/measuredco/puck/commit/32d0666afa1be3e873a7a4f05c14eb5a9d57569e))\n* add experimental full-screen canvas support ([c15c4f8](https://github.com/measuredco/puck/commit/c15c4f876c3fedba4146e6df226cd0d64fbd1832))\n* add full-width viewport to fit to container ([7373a6b](https://github.com/measuredco/puck/commit/7373a6bdf06524f53dd7ac97d8fc3d79b8e95d22))\n* add getParentById for getting the parent data for a child id ([38e79ba](https://github.com/measuredco/puck/commit/38e79ba80a1eef9fcd42f2d8b980c7a198306d57))\n* add key parameter to force custom field remounting ([ace4c8b](https://github.com/measuredco/puck/commit/ace4c8b354107b8bf7b4cd54a3924911f23beb10))\n* add parent to resolveData APIs and trigger resolveData on move ([7c2f928](https://github.com/measuredco/puck/commit/7c2f92837723cd772791e373b98a704de89b1214))\n* add Plugin Rail, a menu for plugins ([86526e7](https://github.com/measuredco/puck/commit/86526e7c3d1afedf088b0cc31284f058c04ad698))\n* add puck ai option to create-puck-app ([d1f05aa](https://github.com/measuredco/puck/commit/d1f05aa0ee05105b30c516198f8e3b84ce50ae95))\n* add resolveDataById utility to Puck API ([12f0a21](https://github.com/measuredco/puck/commit/12f0a2194d4d76a18e9d54d101483777bb9db181))\n* add resolveDataBySelector utility to Puck API ([4f0b390](https://github.com/measuredco/puck/commit/4f0b390e691b327038cf112a471f4ef9fe7844a9))\n* de-select component on second click ([ccff0d8](https://github.com/measuredco/puck/commit/ccff0d8e33f5424556fff398667178a0d8bb79f7))\n* enable modification of Puck height ([538f7bf](https://github.com/measuredco/puck/commit/538f7bffa8be556a828b5f5566a1fbfbc00702c3))\n* export ConfigParams and ComponentConfigParams for convenience ([0f9606c](https://github.com/measuredco/puck/commit/0f9606c645afd9e1f5097cb05cc53f729bcbb803))\n* expose default layout via Puck.Layout ([4a8e38f](https://github.com/measuredco/puck/commit/4a8e38f8d0356b59d04e41a47e1cc2138b61b63a))\n* extend Plugin API with support for Plugin Rail ([d68edca](https://github.com/measuredco/puck/commit/d68edcae12f2740dd89b4c2206718f5ce0cb22bf))\n* improve mobile interface ([3daa05e](https://github.com/measuredco/puck/commit/3daa05e762b241b4d8b739f4e2be4cfeaa8c94b1))\n* provide metadata to resolveFields ([6dae6cb](https://github.com/measuredco/puck/commit/6dae6cb73d63dae6ad119f0c0721f4e69e989770))\n* provide parent data to resolvePermissions API ([1b69a8f](https://github.com/measuredco/puck/commit/1b69a8f3ef2d5f44f592598c302cec5ebc787cde))\n* rename package to @puckeditor/core ([d1c0d6a](https://github.com/measuredco/puck/commit/d1c0d6a25d01d51f2489391e65540566c72278ab))\n* support CSS units for minEmptyHeight in slots ([79a2684](https://github.com/measuredco/puck/commit/79a2684949b6aaa98cd85c4ca6ed13e3e585124f))\n* support dynamic props for default array items via function ([6c44fdb](https://github.com/measuredco/puck/commit/6c44fdb9704618d2189157edc183be9e21432436))\n* support extending all field types via declaration merging ([26fd286](https://github.com/measuredco/puck/commit/26fd286164d41b14e90804187fa28adca1e1be43))\n* support extending ComponentConfig via declaration merging ([3c46c44](https://github.com/measuredco/puck/commit/3c46c447b6dae5b6c23f4ad238820bf4c700e465))\n* support overrides for custom fields ([89038d0](https://github.com/measuredco/puck/commit/89038d0c986f54c70202b27fb6ed836678828b8d))\n* support React nodes in getItemSummary for array fields ([73b98fb](https://github.com/measuredco/puck/commit/73b98fb4671bbac87a408538f6561bbb402c9b1c))\n* support typing metadata via declaration merging ([66c5aa3](https://github.com/measuredco/puck/commit/66c5aa399a197b1fef1a18a1bad106794dffdca5))\n* use dashed outline for overlay portals ([f79baa9](https://github.com/measuredco/puck/commit/f79baa98320854af545be8f31b125af7e1733b83))\n* use next@^16.0.8 in create-puck-app ([313bbb5](https://github.com/measuredco/puck/commit/313bbb53cb426c5c6ae0fbf519a1b7536c56141f))\n\n\n### Performance Improvements\n\n* don't remount slot when parent changes ([a7a6599](https://github.com/measuredco/puck/commit/a7a659972e4229050edc7d3cecf868146f5abf6d))\n* eliminate unnecessary re-renders in fields ([b633e3b](https://github.com/measuredco/puck/commit/b633e3b5339ba660c769838054b2bbf1fa781e43))\n* prevent re-rendering of user components on hover ([a62292f](https://github.com/measuredco/puck/commit/a62292fd052ba62f21b04b95148bdcb281ff2457))\n\n\n\n\n## [0.20.2](https://github.com/measuredco/puck/compare/v0.20.1...v0.20.2) (2025-09-05)\n\n\n### Bug Fixes\n\n* add CommonJS export to emotion cache package.json ([fbf3bd1](https://github.com/measuredco/puck/commit/fbf3bd1038a4c39d4e0c5079c3af33dd515c590b))\n* bypass resolveData cache when trigger is force ([eea53e0](https://github.com/measuredco/puck/commit/eea53e0464c15e7c2f964da973a16a04160224b8))\n* preserve selection when switching viewports and using zoom select ([38514a1](https://github.com/measuredco/puck/commit/38514a169592a3ffbaaedcfcb5b92767ba07b0e7))\n* prevent hidden category components from showing in 'Other' ([8333eaf](https://github.com/measuredco/puck/commit/8333eaf7e27d3225fb7133fe14326e1f8e93776a))\n\n\n\n\n## [0.20.1](https://github.com/measuredco/puck/compare/v0.20.0...v0.20.1) (2025-08-19)\n\n\n### Bug Fixes\n\n* address type issues when using root ([38076a9](https://github.com/measuredco/puck/commit/38076a94bcafbb9905d197195a750928ade757ab))\n* don't widen getItemSummary type for array fields ([92112b9](https://github.com/measuredco/puck/commit/92112b9f1e012cdd1ec9fdfe62e4573da36327f4))\n* don't widen getItemSummary type for external fields ([e58ac42](https://github.com/measuredco/puck/commit/e58ac425398ec115456eda9b4650031359fffa6d))\n* fix slots within array and object fields ([b7fba1b](https://github.com/measuredco/puck/commit/b7fba1b46ba2678e775632a4f5be4a9a1e5be7cf))\n* render spaces/line breaks consistently in inline text fields ([6c58995](https://github.com/measuredco/puck/commit/6c58995b9db7897bd8994a3f2fb1b2456ef32b2f))\n* strip line breaks when pasting into inline text field ([2533c55](https://github.com/measuredco/puck/commit/2533c552a3222e47ffcfb1450ecf95fc4e78fb4a))\n\n\n\n\n## [0.20.0](https://github.com/measuredco/puck/compare/v0.19.3...v0.20.0) (2025-08-14)\n\n\n### Features\n\n* add componentOverlay override ([1a47857](https://github.com/measuredco/puck/commit/1a47857137a1fb7a4c52579e4687a6ae06606e99))\n* add fieldTransform API for modifying field values rendering in canvas ([e67152b](https://github.com/measuredco/puck/commit/e67152bd3e9ef85e3550ee184a0238d6c0ae8da8))\n* add inline text editing to custom fields ([042f4b2](https://github.com/measuredco/puck/commit/042f4b2fc06175d6ded53c83fd653c1eb9095c6a))\n* add inline text editing to text and textarea fields ([ed7c12e](https://github.com/measuredco/puck/commit/ed7c12e3398e76f3c680344bfa6be87abb803afb))\n* add no-external.css bundle without external fonts ([d97af5e](https://github.com/measuredco/puck/commit/d97af5ee2eb5ecd9c704b2b5c3f3c2cde6f660a5))\n* add overlay portals for interactive UI under overlay ([7d50c23](https://github.com/measuredco/puck/commit/7d50c2325a16166021c0c99520816c55682a272b))\n* add setDeep convenience utility ([676e2ab](https://github.com/measuredco/puck/commit/676e2aba846c0182d1079e0290e124255b9db0a8))\n* enable resizing the sidebars ([8909f8c](https://github.com/measuredco/puck/commit/8909f8cc1451a1bf5bc0abe51a564f4a0daa6d75))\n* export SlotComponent type ([d12fc3a](https://github.com/measuredco/puck/commit/d12fc3a9f4edbac5a556e9714bcf2118cc441f4d))\n* extend migrate function to support dynamic DropZones to slots ([3da831b](https://github.com/measuredco/puck/commit/3da831b0975b065e0b9a0062a4c2ae8e8cc9d94a))\n* make overrides optional in plugins ([baed208](https://github.com/measuredco/puck/commit/baed208b685639f3f1104def34408d8252b38dbe))\n* rename components & componentList overrides to drawer & drawerItem ([e40fdfe](https://github.com/measuredco/puck/commit/e40fdfe14c366258ea6b83e415d1c510cf7b0e46))\n* simplify generic type API on Config and ComponentConfig ([04fc574](https://github.com/measuredco/puck/commit/04fc574bc08129f60863fd4ca23f634f616ba6bb))\n* support object, null and undefined in select/radio options ([6dace1c](https://github.com/measuredco/puck/commit/6dace1cf3d49c6fd78eaf2e397399dc38f53412c))\n* support theming of font family ([6cf56a8](https://github.com/measuredco/puck/commit/6cf56a8a7cd7211490d2549cc7ae2af76becef1d))\n\n\n### Bug Fixes\n\n* add missing tiny-invariant dependency in remix recipe ([d405985](https://github.com/measuredco/puck/commit/d405985b80527ae633f7f472af73cfca4acb1cef))\n* address error when using disallow on a slot in root ([93d525c](https://github.com/measuredco/puck/commit/93d525c57f513260f0a60cbb710308e820becd1d))\n* don't revert custom Drawer.Items during drag ([0578004](https://github.com/measuredco/puck/commit/057800431be6530bfef8e2c8ea3d79245f2bdb14))\n* ensure nested name is propagated to custom fields ([f09540d](https://github.com/measuredco/puck/commit/f09540db9e5ad9ff0bd0987a38c0e586f7fa8965))\n* prevent field zoom on mobile devices ([660fd08](https://github.com/measuredco/puck/commit/660fd08af21841d52804e37a3a4503bbbc019ea0))\n* prevent iframe hanging when stylesheets empty ([ea0610a](https://github.com/measuredco/puck/commit/ea0610acbaa47f5a20627afabebaf6b98630d9a3))\n* reattach inline ref if element changes ([ba585f9](https://github.com/measuredco/puck/commit/ba585f93711b55bd100a2ebb7560d5d6b20fa28d))\n* remove erroneous get() API from usePuck ([c119ed0](https://github.com/measuredco/puck/commit/c119ed0d0d707aefa1003efb9448ef736f7ce60c))\n* respect user generic in usePuck hook ([6760121](https://github.com/measuredco/puck/commit/6760121f9f2a37ac990477e5b67c8d9f17cb9601))\n* respect user types in getItemBySelector and getItemById ([f2d031f](https://github.com/measuredco/puck/commit/f2d031fb1285bb31989943e3090461ebc150180f))\n* retain DropZone content in interactive mode ([d2e09bd](https://github.com/measuredco/puck/commit/d2e09bdc1ddd8d1973c08dbe9e75cdf232e9c259))\n* retain metadata in interactive preview mode ([68dd73b](https://github.com/measuredco/puck/commit/68dd73b8a1209923e1c2b2efca0c36fd935a207c))\n* support user-defined fields in overrides ([5cb4cc6](https://github.com/measuredco/puck/commit/5cb4cc652e5db3a54580eb0215932ddc6a648fc9))\n* type object, array, and external field types correctly ([f768aab](https://github.com/measuredco/puck/commit/f768aab935ed9c5955dd558f249f29e347bc52f7))\n* type user fields in overrides ([54d71e3](https://github.com/measuredco/puck/commit/54d71e34017c3319b6db2352fd4551277501d0ee))\n\n\n\n\n### Performance Improvements\n\n* don't render array fields unless open ([14d9681](https://github.com/measuredco/puck/commit/14d968176a62b68af7fab764138a9d7e0b1f74ec))\n\n\n\n\n## [0.19.3](https://github.com/measuredco/puck/compare/v0.19.2...v0.19.3) (2025-07-15)\n\n\n### Bug Fixes\n\n* don't duplicate content in Render when using multiple root DropZones ([412cc57](https://github.com/measuredco/puck/commit/412cc57d9f800e7f862bb4dd59ea4fc8056daea3))\n\n\n\n\n## [0.19.2](https://github.com/measuredco/puck/compare/v0.19.1...v0.19.2) (2025-07-11)\n\n\n### Bug Fixes\n\n* ensure setHistories resets the appState ([7cccfdd](https://github.com/measuredco/puck/commit/7cccfddf2c3c74ea8b17b1e6dda7dbdc2e231022))\n* prevent icon buttons from submitting parent form ([5f6f6d8](https://github.com/measuredco/puck/commit/5f6f6d8e8e2e33281ff235a51786d1af9a6e5622))\n* release ctrl/cmd when tab changes ([37d387b](https://github.com/measuredco/puck/commit/37d387bddec49f03dc10b1df6f72768bf39b24dd))\n* respect slot label in outline panel ([cccf912](https://github.com/measuredco/puck/commit/cccf9125587f14ee1336447a4e63e044af787ca6))\n\n\n\n\n## [0.19.1](https://github.com/measuredco/puck/compare/v0.19.0...v0.19.1) (2025-06-09)\n\n\n### Bug Fixes\n\n* address flat package import errors ([61f2e46](https://github.com/measuredco/puck/commit/61f2e46822471fcdbc52476eaae813a3fb2dd27b))\n* handle multiple slots in migrate function ([8597105](https://github.com/measuredco/puck/commit/859710536bc17102f73db6b9cd411c55e1ed6517))\n* prevent type erasure in object props ([00fbbb8](https://github.com/measuredco/puck/commit/00fbbb8ae2217e38a304a4114069c4e124a1a28f))\n* tidy internal index when array slots removed ([f9d3f0e](https://github.com/measuredco/puck/commit/f9d3f0e93f21735b9a3fb605b4eed4fa8c7ded3e))\n\n\n\n\n## [0.19.0](https://github.com/measuredco/puck/compare/v0.18.2...v0.19.0) (2025-06-04)\n\n\n### Features\n\n* add convenience metadata API to fields ([5fe936e](https://github.com/measuredco/puck/commit/5fe936e08d2f27f663e8849fc59f20973a289332))\n* add getItem helpers to usePuck ([ad947d8](https://github.com/measuredco/puck/commit/ad947d8c2f9f25bb71585f7158900292237a46d4))\n* add labelIcon param to all fields for custom label icons ([24030a9](https://github.com/measuredco/puck/commit/24030a9caa4091382561019c9adc123839a90569))\n* add mapSlots helper function for manipulating slot data ([a27944f](https://github.com/measuredco/puck/commit/a27944ff7c1135c9ee582ff41173b060dd0d79bf))\n* add metadata API for passing data to every component ([b9add22](https://github.com/measuredco/puck/commit/b9add22951755737e272dbf6e475e24198c401ec))\n* add placeholder param for text, textarea and number fields ([32a6f78](https://github.com/measuredco/puck/commit/32a6f7844b7bd3962a16fd2c6989b412aa91685f))\n* add react-router v7 recipe ([706ea0c](https://github.com/measuredco/puck/commit/706ea0c1f0d9237046675674903a0ba7f61fd785))\n* add replaceRoot action to dispatcher ([586eccd](https://github.com/measuredco/puck/commit/586eccd6f3d80af6a4f43001f5d51feb29eeb887))\n* add selector to usePuck for improved performance ([8976e5f](https://github.com/measuredco/puck/commit/8976e5f28736d97d61a99f6e219e88797d67e309))\n* add slots API ([40bc2ee](https://github.com/measuredco/puck/commit/40bc2eec7e5b409cb2479dc10a989b7b5824ae60))\n* add step parameter to number fields ([0ea6ce4](https://github.com/measuredco/puck/commit/0ea6ce41281710ceecdc5dee8a632c06004244f8))\n* add useGetPuck hook for getting latest internal PuckApi ([1d9a47d](https://github.com/measuredco/puck/commit/1d9a47d78ff0e239ef343f851ef0e5e5de8e1d0d))\n* add visible param to show/hide fields ([e5911f3](https://github.com/measuredco/puck/commit/e5911f3d075bcc560672f27e7b74849ab8c6df50))\n* deprecate DropZone component ([d54145d](https://github.com/measuredco/puck/commit/d54145d8bedf6df319620fde82d867edb9d034e3))\n* export package.json for module federation ([b918900](https://github.com/measuredco/puck/commit/b918900b1ffbd4de46c405a73b6a5c74f8db76f5))\n* expose CustomFieldRender type for custom field render functions ([8d459e4](https://github.com/measuredco/puck/commit/8d459e4e848f70e0256c736b1b6cd58081ff7cb7))\n* expose RootConfig type ([638e066](https://github.com/measuredco/puck/commit/638e066fb655bc3e258093bf2428189f3afd88f9))\n* expose WithSlotProps type ([6dc5101](https://github.com/measuredco/puck/commit/6dc5101e26093960a528c9c908acebfe2c917b4e))\n* provide `trigger` event to resolveData parameters ([55b42ae](https://github.com/measuredco/puck/commit/55b42aeeddbc1ada53646bf93c29592523c27e54))\n* rename mapSlots to walkTree ([427e686](https://github.com/measuredco/puck/commit/427e686fdd2a54f35b2de5370bad289a4d409873))\n* support slots in transformProps via optional config arg ([7d59b94](https://github.com/measuredco/puck/commit/7d59b94046e121b4e6afa7e502a66f43a2b236c7))\n\n\n### Performance Improvements\n\n* eliminate most re-renders ([9fcd968](https://github.com/measuredco/puck/commit/9fcd96851fd1472890c5846183f64d62a3a2643f))\n* eliminate re-renders during drag operations ([3ba3ac5](https://github.com/measuredco/puck/commit/3ba3ac51b0733be0c2ce822d9dd82524873a31d3))\n\n\n### Bug Fixes\n\n* account for transforms in overlays ([22f5e3a](https://github.com/measuredco/puck/commit/22f5e3a37611e4d86c43ace936b0ffbbfaf9dd90))\n* add missing `id` to changed type for resolvers ([eb4f9d8](https://github.com/measuredco/puck/commit/eb4f9d857952ee3323447c355edb9fd3d6770716))\n* avoid query selector collision with multiple iframes ([2c1db86](https://github.com/measuredco/puck/commit/2c1db862bfddfee06c78bcf211ece0b9400bec58))\n* bind array item to correct field when using multiple arrays ([7e231b7](https://github.com/measuredco/puck/commit/7e231b782e49162f2b5050ba17fa4178af6ec7c8))\n* deeply check items before populating resolveData changed ([db75e42](https://github.com/measuredco/puck/commit/db75e42b85cfd2cff38bc633d0cd0335213db1fa))\n* don't artificially constrain array items to container ([36b5713](https://github.com/measuredco/puck/commit/36b5713d04e6ad1d116faa25e41fc9683dddb09e))\n* don't collide with parent if component contains drop zone ([e7d2371](https://github.com/measuredco/puck/commit/e7d237137f3381b517eebf39bedf29997e76d4fa))\n* don't render array item until dropped ([1dfc1b3](https://github.com/measuredco/puck/commit/1dfc1b334678ad5b63de0919c92e810bbea19311))\n* don't reset old values when modifying fields in other array items ([ad78e98](https://github.com/measuredco/puck/commit/ad78e98fc7f711da476fa0678a702fd4aa0310d2))\n* don't track dragged headings in heading-outline-analyzer ([2e1a24e](https://github.com/measuredco/puck/commit/2e1a24ed77aa5157647ad93a9dfe2e0723f38cd1))\n* ensure array items can be opened on mobile ([a60c81e](https://github.com/measuredco/puck/commit/a60c81eb4836c368c90cd1d000c77cecc33610cb))\n* ensure file inputs work inside array fields ([83f8f2d](https://github.com/measuredco/puck/commit/83f8f2d7a1eab1bd7a9c182c5435b3782d3dc720))\n* ensure nested array fields are draggable ([af4f756](https://github.com/measuredco/puck/commit/af4f756348db1a13c6e3139890e2d8b6750a1dc2))\n* export migrate util in RSC bundle ([2568ac3](https://github.com/measuredco/puck/commit/2568ac34b4e6f5f00b3c0840b409d6b4f9655e03))\n* expose transformProps in server bundle ([020071e](https://github.com/measuredco/puck/commit/020071e4a8820880b5c5e3389a71b97ce7d9ad4e))\n* fix undo/redo hotkeys for Windows ([a994207](https://github.com/measuredco/puck/commit/a994207e11e6f04cfd23cf827f5e7176a87bab1e))\n* prevent ActionBar clipping if it exceeds top bounds ([56f23e8](https://github.com/measuredco/puck/commit/56f23e8166e9ddf96f929d9f52faa072af2e98da))\n* prevent horizontal scroll on touch  devices ([cb4b6ee](https://github.com/measuredco/puck/commit/cb4b6ee597f6e603bc084de3458195e3dfb6b84f))\n* prevent input-type fields from exceeding container boundaries ([b22833e](https://github.com/measuredco/puck/commit/b22833ee9d3365789fe38d4350c79ee43b8441a1))\n* prevent item from sometimes sticking to window during drag ([e62832e](https://github.com/measuredco/puck/commit/e62832e171de88e2710581d3a3096d5d759d2d63))\n* reflect resolveData value changes in fields ([69dd799](https://github.com/measuredco/puck/commit/69dd799d0bb20ca2baa0bd206f84a575b68dbd70))\n* remove erroneous React 17 from supported peer dependencies ([46212f0](https://github.com/measuredco/puck/commit/46212f0fdb11c2327d92317551a1503646f10e03))\n* remove unexpected license from recipes ([7010bdc](https://github.com/measuredco/puck/commit/7010bdc2f971cb4ee535524e40b3d5c01a5e48f3))\n* reorder array items more predictably ([64c65c3](https://github.com/measuredco/puck/commit/64c65c32ef6f8130832d11c1b532563890b9dcfe))\n* reset stacking context in Puck entry ([6bf9c99](https://github.com/measuredco/puck/commit/6bf9c995ce8e3445e350fa864978fc2dabec52be))\n* restore ability to drop between sibling zones ([2807cba](https://github.com/measuredco/puck/commit/2807cbaa83a161974802886ec8d12d555cf8c65d))\n* restore field values during undo/redo ([6917928](https://github.com/measuredco/puck/commit/6917928d3ce053eee214c438268dbd115520c1f6))\n* retain minimum height when ActionBar is empty ([a52ccb9](https://github.com/measuredco/puck/commit/a52ccb96abdd37ade8a76e12f902b56d5f4f4efd))\n* set ready status more reliably when using strict mode ([5a526d0](https://github.com/measuredco/puck/commit/5a526d0a2fffdd243c8eb191961bd136639f75f2))\n* show correct styles when insert permission is disabled ([f19cdca](https://github.com/measuredco/puck/commit/f19cdcab2c3579f38ad79c55f168bdfba5699ed2))\n* show top border on array button when array empty ([add5a17](https://github.com/measuredco/puck/commit/add5a175286036bd029113b2746bc0c94fa0a2ff))\n* still select item from outline if element not in document ([8e1d722](https://github.com/measuredco/puck/commit/8e1d722355409e0985195415a4b05e56a62af470))\n* support strings in readOnly type for arrays and object ([9358a3b](https://github.com/measuredco/puck/commit/9358a3b53f245a1caf245ee984da8017edac3fc6))\n\n\n## [0.18.3](https://github.com/measuredco/puck/compare/v0.18.2...v0.18.3) (2025-04-05)\n\n\n### Bug Fixes\n\n* bind array item to correct field when using multiple arrays ([934cfae](https://github.com/measuredco/puck/commit/934cfaeed36b9737cd51c0d400b4e7ec8686ae35))\n* don't artificially constrain array items to container ([648a235](https://github.com/measuredco/puck/commit/648a235f5bb20b276f7fc307e81842b1215838f9))\n* don't render array item until dropped ([94f5e23](https://github.com/measuredco/puck/commit/94f5e238e2a8dd2ec5b3eba211d1160a2faa739a))\n* don't reset old values when modifying fields in other array items ([73c17b3](https://github.com/measuredco/puck/commit/73c17b30d1c13a8971731942f690c44ddc1794cc))\n* don't track dragged headings in heading-outline-analyzer ([3c16391](https://github.com/measuredco/puck/commit/3c16391bc8085cbddc910f70657e440471216995))\n* ensure file inputs work inside array fields ([746033f](https://github.com/measuredco/puck/commit/746033f53623b1824f31e987339737fd92ce38dc))\n* ensure nested array fields are draggable ([0bdd243](https://github.com/measuredco/puck/commit/0bdd2434a254151cfbe636840ed1294fef5c0c26))\n* expose transformProps in server bundle ([d234345](https://github.com/measuredco/puck/commit/d234345c73d530803f3effb1c8a458353ac006e2))\n* prevent ActionBar clipping if it exceeds top bounds ([e8355f0](https://github.com/measuredco/puck/commit/e8355f09c668baa044629d923076c59817d43072))\n* remove erroneous React 17 from supported peer dependencies ([98ad734](https://github.com/measuredco/puck/commit/98ad73412c1addb9214a3d953d962f401f044a3f))\n* reorder array items more predictably ([659f2d8](https://github.com/measuredco/puck/commit/659f2d8fc0c3c4511211a4933e0337ab771b29dd))\n* show top border on array button when array empty ([7442118](https://github.com/measuredco/puck/commit/7442118598793d808c80535428dd21de9315daa1))\n\n\n\n\n## [0.18.2](https://github.com/measuredco/puck/compare/v0.18.0...v0.18.2) (2025-01-31)\n\n\n### Bug Fixes\n\n* add missing types for root render method ([0f52caf](https://github.com/measuredco/puck/commit/0f52caf0a3d77978d913b0915bbb23725fc94a3d))\n* address ownerDocument permission error in Firefox ([c22b3a9](https://github.com/measuredco/puck/commit/c22b3a9838fad6c65c71091eb89bb9104b6aabaf))\n* address prop name collision regression ([3a69ad4](https://github.com/measuredco/puck/commit/3a69ad4ac3e982340b3fcae6b5773240d88e7f92))\n* correctly infer types when using Render with RSC ([ad7fbf4](https://github.com/measuredco/puck/commit/ad7fbf4bc7534cfb348dd7fdbac56b84b03552e3))\n* don't jump to end of textarea fields during change ([36c27a9](https://github.com/measuredco/puck/commit/36c27a9c166affc83ecfd36f79974cb9990917c6))\n* don't trigger clicks when dropping array items ([29a7f1d](https://github.com/measuredco/puck/commit/29a7f1df55f1eff6bee55ee7984ec7974a4feaec))\n* don't trigger drag when interacting with array fields ([c7cd341](https://github.com/measuredco/puck/commit/c7cd34165cbde90ae0297ab79a1ea6852f9d9726))\n* ensure ctrl+i interactive toggle hotkey works on Windows ([5db6f4d](https://github.com/measuredco/puck/commit/5db6f4d5f90b222423df363e9964ec344b9a0a7e))\n* ensure renderDropZone provided correctly to root render ([b9ce5b7](https://github.com/measuredco/puck/commit/b9ce5b700056f985362d1f4a9125996badfcbdd7))\n* fix RTL drag-and-drop behaviour ([28f518a](https://github.com/measuredco/puck/commit/28f518aad9da211042e3b51e52369b6c126c75d8))\n* improve behaviour of array drag-and-drop ([565fabd](https://github.com/measuredco/puck/commit/565fabdaa78f92fdbb327d878d7142e0455f5e8a))\n* provide empty readOnly object instead of undefined to root resolveFields ([8992a94](https://github.com/measuredco/puck/commit/8992a9476ab2bc3837bb986bbd3e1042babd81a8))\n* provide updated props to resolveFields ([b7ff689](https://github.com/measuredco/puck/commit/b7ff6898618a1a30b07a85669f16e891db975ffd))\n* reinstate padding for external field filters ([28ccfda](https://github.com/measuredco/puck/commit/28ccfdaf8b6c546464df18f1a3ebb2b8b6363dc7))\n* tidy up stale items on drag cancellation ([de48691](https://github.com/measuredco/puck/commit/de48691b017a66c251616ba31861bd0b8816ef68))\n* update styles for RTL ([23c8dda](https://github.com/measuredco/puck/commit/23c8dda031b0f50e30eacdb15d3a774bd8045879))\n* use correct type for onChange args when overriding fieldTypes ([daff71e](https://github.com/measuredco/puck/commit/daff71ef726ad74d2b5628c466b2ddbaa1850160))\n\n\n\n\n## [0.18.1](https://github.com/measuredco/puck/compare/v0.18.0...v0.18.1) (2025-01-24)\n\n\n### Bug Fixes\n\n* address React 19 peer dependency issues ([7649086](https://github.com/measuredco/puck/commit/7649086009deb9b9ceb5c4790e9c356b107a20b6))\n* address ResizeObserver loop error ([d3e6b57](https://github.com/measuredco/puck/commit/d3e6b57190e3f6e8a4f857a45a51cbb060daf050))\n* don't access selectedItem if undefined right after drop ([0573b18](https://github.com/measuredco/puck/commit/0573b182452f1c614a15f4125fefa81a880e37a2))\n* ensure nested drag-and-drop works in Firefox ([f077a37](https://github.com/measuredco/puck/commit/f077a37158194867a10c1406252fe4a8f4f6974c))\n\n\n\n\n## [0.18.0](https://github.com/measuredco/puck/compare/v0.17.1...v0.18.0) (2025-01-21)\n\n\n### Features\n\n* add action to select parent component to ActionBar ([7c910d5](https://github.com/measuredco/puck/commit/7c910d5272e8d6d77819ccb3280dff143ea848fd))\n* add ActionBar.Label component for adding labels to action bars ([d2645fd](https://github.com/measuredco/puck/commit/d2645fd68a57b4c07bb8a3948ab6a845c2ce1988))\n* add DropZone collisionAxis API for forcing collision direction ([ba68732](https://github.com/measuredco/puck/commit/ba687329c6fac5085f78768bff6eb37bfd842f33))\n* add meta+i hotkey and previewMode state to toggle interactivity ([ec1eba5](https://github.com/measuredco/puck/commit/ec1eba58525e0245ee1214f8e401fa935c41fe23))\n* add wrapFields prop to control padding of fields in Puck.Fields ([30f9a92](https://github.com/measuredco/puck/commit/30f9a926d2640a5bf9f65d8f4c2b6018e73f8719))\n* control empty DropZone height with minEmptyHeight prop ([96f8340](https://github.com/measuredco/puck/commit/96f83408f4e6219dd35f5c29b204ef18e6d11d64))\n* deselect item on viewport change ([e35585d](https://github.com/measuredco/puck/commit/e35585d767c857413ed5560f311d64bcab1218c4))\n* forward the ref to the DropZone component ([676aa1c](https://github.com/measuredco/puck/commit/676aa1c974bd1260aaa687aa3edc2c54ef34e22b))\n* introduce new drag-and-drop engine ([6ebb3b8](https://github.com/measuredco/puck/commit/6ebb3b8724b8ed56cc76d3ce166b1dc87ed07dad))\n* reduce DropZone to height of items unless empty ([2b2595a](https://github.com/measuredco/puck/commit/2b2595a4e3e1c5ed8352cdfbec704290a1b396e8))\n* remove `position: fixed;` from Puck layout ([5deb774](https://github.com/measuredco/puck/commit/5deb7744c07fca12e6aa44d058b495f65b298eab))\n* support inline Drawers, deprecating unnecessary props ([f93b71e](https://github.com/measuredco/puck/commit/f93b71e1ad555184fc1a43f151ef1b161be148c6))\n\n\n### Bug Fixes\n\n* deselect item on delete ([f27871b](https://github.com/measuredco/puck/commit/f27871b5b63be8246cd281d93c49f7744d7e186f))\n* improve heading-analyzer reliability ([ab6c018](https://github.com/measuredco/puck/commit/ab6c01862c35e27929b249a6d4bc4d2e9065dc12))\n* never render FieldLabel with padding or borders ([a97b54f](https://github.com/measuredco/puck/commit/a97b54fd9427f3cd587951a0a30a95d56c5ff020))\n* prevent propagation of custom ActionBar actions by default ([14909bd](https://github.com/measuredco/puck/commit/14909bdc5a782330af661a32bc80ab387ab12897))\n* prevent user pollution of ActionBar styles ([e154cb7](https://github.com/measuredco/puck/commit/e154cb7c72c4fce735ccd60ccbdc862314f0ad26))\n* render DropZones the same in Puck and Render ([d975aaf](https://github.com/measuredco/puck/commit/d975aaf90bf7d0956ccf1d6c377a6e20ba224801))\n* reset resolveFields lastFields param when changing component ([7fead35](https://github.com/measuredco/puck/commit/7fead35fddf8fef49b41508a27c0e6be458ab2c4))\n* select new item when dispatching duplicate action ([e3d0025](https://github.com/measuredco/puck/commit/e3d0025d08408103940c2f84c4524266288f38fd))\n* set root DropZone to 100% height ([3d93f46](https://github.com/measuredco/puck/commit/3d93f46555372e83ead6f671e40970937802f5f4))\n* stop actions from overflowing outside left of frame ([c036b6d](https://github.com/measuredco/puck/commit/c036b6d2036cc759e0a2eda6154bdec5b8a7784e))\n* trigger iframe resize when closing devtools ([2c0b782](https://github.com/measuredco/puck/commit/2c0b782d41817caa2b6fae41fc52b1a7ccbb8d09))\n\n\n\n## [0.17.4](https://github.com/measuredco/puck/compare/v0.17.3...v0.17.4) (2025-01-19)\n\n\n### Bug Fixes\n\n* handle null when provided to text/textarea/number fields ([e778246](https://github.com/measuredco/puck/commit/e778246e4ae8925f3d04962369a33a9c1a4b6589))\n* improve stability of resolveFields API ([5c60d6a](https://github.com/measuredco/puck/commit/5c60d6a11512086f395ace352eec868fcd748f44))\n* respect allow prop for existing items ([e414e34](https://github.com/measuredco/puck/commit/e414e34680acb7259dcee1da081060f5be923c02))\n\n\n\n\n## [0.17.3](https://github.com/measuredco/puck/compare/v0.17.2...v0.17.3) (2025-01-13)\n\n\n### Bug Fixes\n\n* ensure items in root DropZone can be selected ([f61dd4a](https://github.com/measuredco/puck/commit/f61dd4a955e6c09d49f4fc1967e1cac5445697f7))\n\n\n\n\n## [0.17.2](https://github.com/measuredco/puck/compare/v0.17.1...v0.17.2) (2025-01-10)\n\n\n### Bug Fixes\n\n* always respect history hotkeys inside iframes ([1134e8b](https://github.com/measuredco/puck/commit/1134e8b893e6828ad6407d570d987d4206e71566))\n* clear old readOnly data when running resolveData ([3e91adc](https://github.com/measuredco/puck/commit/3e91adcf38a3a0f03537d592d15458f368048857))\n* don't trigger move action if source / destination the same ([8a0b811](https://github.com/measuredco/puck/commit/8a0b811c79d7ec91cd6cc0007f05048680e42997))\n* ensure parent is not null on first render in resolveFields ([773a81a](https://github.com/measuredco/puck/commit/773a81a330bc133b2d77b58d3ec99300cda1546e))\n* factor in border when setting viewport size ([cc3b3b8](https://github.com/measuredco/puck/commit/cc3b3b8685e63cccba2c5a59e349a9394445f1f6))\n* fix plugin-emotion-cache style sync when using initialData ([ac8679c](https://github.com/measuredco/puck/commit/ac8679c309a5b9b46670aa41b263b7369d155a46))\n* fix readOnly behaviour in nested fields ([f6ab512](https://github.com/measuredco/puck/commit/f6ab51269d6f2acfb3a366ac5c33337158ac30ba))\n* remove unnecessary transpile from next recipe ([a5f2d08](https://github.com/measuredco/puck/commit/a5f2d08efe6e3aec8c65ed1a1d59df26f45277be))\n* respect min/max for freeform input in number field ([715710a](https://github.com/measuredco/puck/commit/715710a37c06ec6f255036c3e1334cf4fb0b2549))\n* use correct label for array and object subfields ([c00ea00](https://github.com/measuredco/puck/commit/c00ea007f20242766786c57b915e43c65047a045))\n\n\n\n\n## [0.17.1](https://github.com/measuredco/puck/compare/v0.17.0...v0.17.1) (2024-12-18)\n\n\n### Bug Fixes\n\n* respect falsey booleans types in select/radio fields ([3406b01](https://github.com/measuredco/puck/commit/3406b01d5ce00e8f2b885a1f951b5c96aa7a7989))\n\n\n\n\n## [0.17.0](https://github.com/measuredco/puck/compare/v0.16.2...v0.17.0) (2024-12-18)\n\n### Features\n\n* add duplicate action to array field ([229cbdd](https://github.com/measuredco/puck/commit/229cbddb7eed513c8ac9a2e36e3af3b53ff28d7e))\n* add renderFooter API to external field ([ccec96e](https://github.com/measuredco/puck/commit/ccec96e5ddf831fcd89a2af335449ad4cff1ea81))\n* allow react elements in external field mapRow ([2f781de](https://github.com/measuredco/puck/commit/2f781de0a910a193f0a4bae795725119476f8e94))\n* enable resolveFields to access parent data ([196227b](https://github.com/measuredco/puck/commit/196227bdf33ee678ce47b68fc624804448008cc1))\n* list React 19 as supported peer dependency ([85e8cc1](https://github.com/measuredco/puck/commit/85e8cc1a6fcd29d9dd04e5e53c6e7f9a85f99959))\n* track focused field in app state ([91bc97a](https://github.com/measuredco/puck/commit/91bc97a760d1750d65dedbbffee962a6c6ee8d60))\n* upgrade next recipe to v15.1 ([8ef51c5](https://github.com/measuredco/puck/commit/8ef51c54e386528fca69be1e54b8a3ce69651bd0))\n* use React 19 in next recipe ([6b3d97f](https://github.com/measuredco/puck/commit/6b3d97f9f3d0cc2283178ba6f4bda3b23f1f718a))\n\n\n### Bug Fixes\n\n* always run field resolvers when item change ([159d819](https://github.com/measuredco/puck/commit/159d819e0263f4e91bff8a83adfa404601850aa5))\n* always update fields when resolveData runs ([39dd619](https://github.com/measuredco/puck/commit/39dd61934c15a452c59f26b0c6721802df0c1889))\n* ensure radio fields are functional inside arrays ([7736294](https://github.com/measuredco/puck/commit/7736294d201f432799c0854be14b35edbad156d8))\n* prevent field name collision causing hook render mismatch ([b51954a](https://github.com/measuredco/puck/commit/b51954a19875e1f3c87e0cdc03c10173e9786820))\n* prevent flicker when using resolveData with arrays ([1be9b88](https://github.com/measuredco/puck/commit/1be9b886325a1515434759011e9e3514c583bd2e))\n* provide better error when usePuck used inappropriately ([9991c07](https://github.com/measuredco/puck/commit/9991c079b2b7d8f18ecb42efc3ebc32e5d679b88))\n* remove leading zeros in Number field ([5ba9399](https://github.com/measuredco/puck/commit/5ba9399e6546919ae744d7a4986b59faa1cd7aef))\n* respect original value type in radio and select fields ([00ccd1d](https://github.com/measuredco/puck/commit/00ccd1df6513d2420c87cd136577e1df1ac9a9a3) and [6e5864a](https://github.com/measuredco/puck/commit/6e5864a5df01a52fb4e6b23132d68d4496f1e64e))\n\n\n\n\n## [0.16.2](https://github.com/measuredco/puck/compare/v0.16.1...v0.16.2) (2024-11-07)\n\n\n### Bug Fixes\n\n* always treat data as immutable, fixing Redux issues ([51154e9](https://github.com/measuredco/puck/commit/51154e92b9022311afa79d086f69b70b6b8beb77))\n* don't crash if component definition missing ([525b506](https://github.com/measuredco/puck/commit/525b5065563675d03d89cf090ce1f7fdf8ff0486))\n* don't crash when selecting component with no config ([cb90f5d](https://github.com/measuredco/puck/commit/cb90f5d9109b340407bc9828fcd9761183d83e68)), closes [#671](https://github.com/measuredco/puck/issues/671)\n* export missing resolveAllData lib in RSC bundle ([2f5fb7b](https://github.com/measuredco/puck/commit/2f5fb7ba69b61b857ad14720b93ceab026571aa7))\n* fix RTL styles in action bar overlay ([bf5c5a3](https://github.com/measuredco/puck/commit/bf5c5a33081599331049063c79c7859aea96d0da))\n* remove internal AutoField and FieldLabel components from bundle ([5df1597](https://github.com/measuredco/puck/commit/5df1597feede2f0ff922ad13297fd3acaf942da2))\n* remove unused label from AutoField type ([18b6f1a](https://github.com/measuredco/puck/commit/18b6f1acae0186245817f35d4a27e6fdf4153ea1))\n\n\n\n\n## [0.16.1](https://github.com/measuredco/puck/compare/v0.16.0...v0.16.1) (2024-10-07)\n\n\n### Bug Fixes\n\n* don't delete array field on click in FieldLabel ([ed282b9](https://github.com/measuredco/puck/commit/ed282b98ebe8574258444ba91716d8da7e8117d1))\n* don't overwrite user input when field recently changed ([6126040](https://github.com/measuredco/puck/commit/61260407c5c87cc8c5c4fe925835f2d0d2a6f9ff))\n* don't show field loader if no resolver defined ([8c706cd](https://github.com/measuredco/puck/commit/8c706cda92474114faffc7ed77f4b4024f75bf68))\n* hide ActionBar.Group border when empty ([4345165](https://github.com/measuredco/puck/commit/4345165ee71b9762e6bca9baaa53d0c53144d0c4))\n* prevent item click before iframe load ([61e1653](https://github.com/measuredco/puck/commit/61e1653020b9e272133c70fa9494f1a81782531e))\n* prevent flash of field loader when no data changed ([20d7309](https://github.com/measuredco/puck/commit/20d730924d2f235871bfec4f0467a6652a518704))\n* respect readOnly styles in AutoField ([9ffe817](https://github.com/measuredco/puck/commit/9ffe8176c1c437524fd9f7b2912f1a5846fc5e55))\n\n\n\n\n## [0.16.0](https://github.com/measuredco/puck/compare/v0.15.0...v0.16.0) (2024-09-16)\n\n\n### Features\n\n* add actionBar override for adding component controls ([48ec0d7](https://github.com/measuredco/puck/commit/48ec0d786c7c589efc8b97152a5e1a4c065c0312))\n* add automatic RSC export, replacing /rsc bundle ([d21eba6](https://github.com/measuredco/puck/commit/d21eba6185da8efcbcb5458eaaa5be6c321b3d1a))\n* add isDisabled prop to Drawer.Item ([cad95b8](https://github.com/measuredco/puck/commit/cad95b887c6b06a41a2bacf28792fd4dbc808d72))\n* add generic type to usePuck hook ([01703a9](https://github.com/measuredco/puck/commit/01703a95093413a57af1314b1f31cc34f85c38e0))\n* add iframe override for style injection ([7cac376](https://github.com/measuredco/puck/commit/7cac3764d1f9336776b97fa08cbd48bec95e6a10))\n* add initialHistory prop to Puck ([54b5a87](https://github.com/measuredco/puck/commit/54b5a871570120a3d0d55e96738746ec375dee0d))\n* add onAction API to track and react to state changes ([c7007ac](https://github.com/measuredco/puck/commit/c7007acab334ec2d08f95669d685edb8c3947bcc))\n* add permissions API ([a43914d](https://github.com/measuredco/puck/commit/a43914dc36e70c5596c186d3c63b9497949365a9))\n* add plugin for injecting Emotion cache ([f8a88b9](https://github.com/measuredco/puck/commit/f8a88b9c2447c76f2f7a00ce5705f8fae07be58c))\n* add resolvePermissions API ([f0655f0](https://github.com/measuredco/puck/commit/f0655f08a96b853cf18d681025f40e8d30df3013))\n* add waitForStyles option to iframe config ([bc81d9c](https://github.com/measuredco/puck/commit/bc81d9c7de671fea0bc155911ee11598a1b920c2))\n* call resolveData when new item inserted ([3298831](https://github.com/measuredco/puck/commit/329883165c9e428b9f291add7b6009ba29680146))\n* don't mandate fields for optional props ([5a219ef](https://github.com/measuredco/puck/commit/5a219eff0c2f4763ec1d9f48f45fe684e6482b8f))\n* export ActionBar component for use in overrides ([04fd6c5](https://github.com/measuredco/puck/commit/04fd6c5c7a65fc3ec9a05da277865341efe229af))\n* infer Data type from user config ([50045bb](https://github.com/measuredco/puck/commit/50045bbda2cf3b64e37e0e6bedcfce14f680cda1))\n* make ID optional in History type (BREAKING CHANGE) ([d917229](https://github.com/measuredco/puck/commit/d917229ae4f553bb54a420e1c708c1a509431106))\n* provide ES Module build ([ff9076b](https://github.com/measuredco/puck/commit/ff9076b9d24d030ad47619b6a359b1f120422d70))\n* rename history.data to history.state (BREAKING CHANGE) ([b09244c](https://github.com/measuredco/puck/commit/b09244c864fd049ceeda2b7eb20ec6cab9f40054))\n* show spinner if iframe load takes over 500ms ([cfecf54](https://github.com/measuredco/puck/commit/cfecf5499d06b8e90438dc151e5e915da06ccb87))\n* streamline usePuck history API ([c8b2807](https://github.com/measuredco/puck/commit/c8b28075fde0081b8ac824eb256114c9b8836f9e))\n* upgrade \"next\" recipe to typescript@5.5.4 ([60fe631](https://github.com/measuredco/puck/commit/60fe63113f8ad8bbce52d8457ee4372aa4b09509))\n\n\n### Bug Fixes\n\n* add favicon to next recipe to prevent Puck 404 ([2c52d27](https://github.com/measuredco/puck/commit/2c52d271c6c20e9368a59eb1f2a5df184cef72bc))\n* add missing readOnly state to External fields ([bf1449d](https://github.com/measuredco/puck/commit/bf1449dd8b299a4f469986d94f8986b02b79a688))\n* always record history on component insert ([88c5ab6](https://github.com/measuredco/puck/commit/88c5ab6b545ecbd045de3ee0d43801c48f50e8b0))\n* don't cache /edit route in Next recipe ([94f16b2](https://github.com/measuredco/puck/commit/94f16b25efea86ff475683d3a21f5937e07b201c))\n* don't submit buttons if Puck used in form ([f761e5f](https://github.com/measuredco/puck/commit/f761e5fed63fc698e3a9d6ba94607364ed46f31b))\n* ensure demo types are satisfied with TypeScript@5 ([958dc25](https://github.com/measuredco/puck/commit/958dc255ac5d285f98b6b592df677883b74e2830))\n* export missing Plugin type ([eb42734](https://github.com/measuredco/puck/commit/eb427343fd58752861cac850f59c1098cf473f50))\n* fix crash if component in data is missing from config ([0daf478](https://github.com/measuredco/puck/commit/0daf478d9ad8b14d2844ff6ae2db9bd72970d680))\n* improve resiliency of iframe CSS for some frameworks, like Mantine ([538cb05](https://github.com/measuredco/puck/commit/538cb05606126c338e97c047b97065463e618d36))\n* make Config and Data types more robust ([6bcf555](https://github.com/measuredco/puck/commit/6bcf555da74d54d70f00f37878d35fa166bb7e4c))\n* prevent infinite loop when using plugins with some frameworks ([3870871](https://github.com/measuredco/puck/commit/38708716f32d65a9131b87fe664ba96b32aead15))\n* prevent Tailwind from clashing with viewport zoom select ([9151255](https://github.com/measuredco/puck/commit/91512553430b295c37c80a935f0db929bb37870c))\n* remove body margin in remix recipe ([0898b26](https://github.com/measuredco/puck/commit/0898b26cd021680dfb77a439b04140ce2fb8cb2c))\n* resize viewport when changed via app state ([14419ec](https://github.com/measuredco/puck/commit/14419ecf1c606e6fa0d6d9c5198401eb01bc72dd))\n* resolve fields when switching between items of same type ([a3518ca](https://github.com/measuredco/puck/commit/a3518ca8560ba9fcdbe5086220490920ecf24fc0))\n* return lastData as null instead of empty object in resolvers (BREAKING CHANGE) ([648eb92](https://github.com/measuredco/puck/commit/648eb92b3d2c5be8f5fc99a22db5eff64cefb155))\n* show warning if heading-analyzer styles aren't loaded ([4e7110b](https://github.com/measuredco/puck/commit/4e7110b591a4a12e2b3c89eb1fa98faf5f9338d4))\n* use correct color in FieldLabel labels ([b0469a1](https://github.com/measuredco/puck/commit/b0469a1134ac8eafc9a3b16de4d7805241127947))\n\n\n\n\n## [0.15.0](https://github.com/measuredco/puck/compare/v0.14.2...v0.15.0) (2024-05-30)\n\n\n### Bug Fixes\n\n* align Drawer behaviour and docs with expectation ([e2cd445](https://github.com/measuredco/puck/commit/e2cd445f9d3abccca5b3daf95a4d92774a1dd47a))\n* animate loader in iframe ([151a267](https://github.com/measuredco/puck/commit/151a2675bf8e700368aad0652192bc7d9fd2bbd6))\n* don't inline link stylesheets for more predictable behaviour ([c0a331d](https://github.com/measuredco/puck/commit/c0a331de31c2d59e0e21ef342eb4c821850e10be))\n* don't overflow external inputs inside arrays/objects ([42ef582](https://github.com/measuredco/puck/commit/42ef582cac949f8a24f9cdad204baf24d808b410))\n* don't throw warning when user is correctly specifying root props ([46aa8ff](https://github.com/measuredco/puck/commit/46aa8ff3a68dcbd4aec4ebfef246d400469ca4d4))\n* don't unintentionally use read-only styles in external fields ([acaf727](https://github.com/measuredco/puck/commit/acaf72746c2c82881a753dab6350161c774cd13f))\n* fix defaultProps for root ([9a1cc7c](https://github.com/measuredco/puck/commit/9a1cc7c925f0b8a79b5f523fc7c8a6d6afdc2067))\n* infer correct value types in Custom fields ([5c8c0e1](https://github.com/measuredco/puck/commit/5c8c0e1bfa9ca4da04e1cfac83c7a3ab5883fc5c))\n* position field loader relative to sidebar, not fields ([2e8936e](https://github.com/measuredco/puck/commit/2e8936e4f416b0a04b273250cf3848447fb7e045))\n* show external field modal when using custom interfaces ([6e97a0e](https://github.com/measuredco/puck/commit/6e97a0e18aea72581ba466e8cf3f87e60f3a65f3))\n* show field loader when using field overrides ([8ccfa4c](https://github.com/measuredco/puck/commit/8ccfa4c0c3477b8e1d2db2fcc7a352b353643095))\n* still load iframe if styles fail to load ([3e56bc1](https://github.com/measuredco/puck/commit/3e56bc1816c40c555de2eb28148baf5dcdcacbea))\n\n\n### Features\n\n* add AutoField component for using Puck fields inside custom fields ([106028b](https://github.com/measuredco/puck/commit/106028b59bb1a02756645bb76ce400adc398430d))\n* add isEditing flag to `puck` object prop ([13bb1bd](https://github.com/measuredco/puck/commit/13bb1bdf03a62000c07a7d49a56ad09c1433fda0))\n* add resolveFields API for dynamic fields ([0a18bdb](https://github.com/measuredco/puck/commit/0a18bdb9387f302565f74fa30f09fd912ea0769b))\n* allow data prop to accept an empty object ([aedd401](https://github.com/measuredco/puck/commit/aedd401dd415e9d7dc1cbd6e33e59f5264180374))\n* bump next recipe to Next@14 ([47a27ed](https://github.com/measuredco/puck/commit/47a27ed2c6aee80d4093975c399d96b950cb6956))\n* enable override of publish button (breaking change) ([480467a](https://github.com/measuredco/puck/commit/480467ae2e06ae4d36c4fd67f75757557058f561))\n* expose previous data to resolveData via `lastData` param ([dd7051e](https://github.com/measuredco/puck/commit/dd7051e8fbb3770714100c92f7f5c69d0be5dab6))\n* replace history chevrons with undo/redo icons ([91dff22](https://github.com/measuredco/puck/commit/91dff227c382ddd5ad183cd69cb4d2fabd56f093))\n\n\n\n\n## [0.14.2](https://github.com/measuredco/puck/compare/v0.14.0...v0.14.2) (2024-04-17)\n\n\n### Bug Fixes\n\n* add DropZone iframe compatablity mode for bug in Safari 17.2, 17.3 and 17.4 ([47496c2](https://github.com/measuredco/puck/commit/47496c25407b1a5fdb88333e1fbf5416efc51c50))\n* check for optionality to handle race condition when dragging ([4dbd487](https://github.com/measuredco/puck/commit/4dbd487f6055ea3d38ab7de54e29bd6e4ffe84ce))\n* defer iframe event binding until contentWindow is ready ([268ea53](https://github.com/measuredco/puck/commit/268ea53f969a892843c026e5ba9ced15edb9f801))\n* don't crash if component is missing after referenced in category ([dc93789](https://github.com/measuredco/puck/commit/dc93789c4311e386b022b5c3d7c8595c00a8a212))\n* don't force height of DropZones in custom interfaces ([046c255](https://github.com/measuredco/puck/commit/046c2557b6baa62994380c547ad006759b02cc92))\n* don't query iframe document if not ready ([2b2ef32](https://github.com/measuredco/puck/commit/2b2ef32555387d4656872674289740b73dcd406b))\n* don't throw undefined error if rapidly zooming browser in some environments ([282a8b0](https://github.com/measuredco/puck/commit/282a8b0d9f170ea95f5717c8b2ad08ec487d7d8f))\n* fix drag-and-drop when entire Puck component used inside an iframe ([23db292](https://github.com/measuredco/puck/commit/23db292b9a2caa8e65117c08706843d3ed343454))\n* fix support for boolean values in select fields ([c4a66ad](https://github.com/measuredco/puck/commit/c4a66addacd9acdc1f042ac54831b7dac38f2757))\n* make draggable outlines consistent ([9008b70](https://github.com/measuredco/puck/commit/9008b70ed63155140a5241914c86456a2d4c9388))\n* prevent grid layout issues in generated apps ([5c05f94](https://github.com/measuredco/puck/commit/5c05f945679f7f2c0edd5d99c652989c00920ac6))\n* reflect value changes made via resolveData in radio fields ([9a7066f](https://github.com/measuredco/puck/commit/9a7066f4e837575aecbde0de4dd2bc96328a2a15))\n* remove peer dependencies causing warnings ([041ca64](https://github.com/measuredco/puck/commit/041ca64a6fe96539681d88e9cd0e66a6ac27a6ce))\n* resolve security warning when additional iframes present ([03ab0bd](https://github.com/measuredco/puck/commit/03ab0bd3314a4d6dfc863bdcf5f23246331b959b))\n* use 100% width for Puck preview when iframe disabled ([#414](https://github.com/measuredco/puck/issues/414)) ([64303c8](https://github.com/measuredco/puck/commit/64303c8510df15b6ca94bc7be0294d9746193b35))\n* use more custom interface friendly styles for iframes ([e6e01c6](https://github.com/measuredco/puck/commit/e6e01c6ec5b2bee9ab3a4a9425276ad4f1840c20))\n\n\n### Performance Improvements\n\n* add API for disabling auto-scroll due to performance issues ([3e5599e](https://github.com/measuredco/puck/commit/3e5599e687643094f7c80d0ce99a7c6a0c947e28))\n* batch load initial iframe styles ([e585f20](https://github.com/measuredco/puck/commit/e585f2090c0457d124006bd6349a69c9883d3c03))\n* don't lock main thread when iframe styles changed ([e529e85](https://github.com/measuredco/puck/commit/e529e8525eb758025261577c424d8601c1ed8daf))\n* reuse host window styles in iframes ([e7fe7e0](https://github.com/measuredco/puck/commit/e7fe7e0d7577bae1ab90650e5d7986d6745fbaf9))\n\n\n\n\n## [0.14.1](https://github.com/measuredco/puck/compare/v0.14.0...v0.14.1) (2024-04-01)\n\n\n### Bug Fixes\n\n* don't throw undefined error if rapidly zooming browser in some environments ([282a8b0](https://github.com/measuredco/puck/commit/282a8b0d9f170ea95f5717c8b2ad08ec487d7d8f))\n* prevent grid layout issues in generated apps ([5c05f94](https://github.com/measuredco/puck/commit/5c05f945679f7f2c0edd5d99c652989c00920ac6))\n* remove peer dependencies causing warnings ([041ca64](https://github.com/measuredco/puck/commit/041ca64a6fe96539681d88e9cd0e66a6ac27a6ce))\n\n\n\n\n## [0.14.0](https://github.com/measuredco/puck/compare/v0.13.0...v0.14.0) (2024-03-28)\n\n\n### Features\n\n* add \"name\" prop to componentItem override ([45bbceb](https://github.com/measuredco/puck/commit/45bbceb1d2805455fa38f5bce91d892f6acacfbf))\n* add `min` and `max` APIs to array fields ([53b7937](https://github.com/measuredco/puck/commit/53b7937675303bc3cf282bbd005309c8c276d1b2))\n* add API to opt-out of iframes ([03dd90b](https://github.com/measuredco/puck/commit/03dd90b98c8a72e2af3baa8fc436ff7d4f4c7449))\n* add Contentful field package ([d944288](https://github.com/measuredco/puck/commit/d94428819a958b4f566e5d0e8cd29b3bf1107881))\n* add filter fields to ExternalFields ([7a55053](https://github.com/measuredco/puck/commit/7a5505374953ab8004720a9c91d8975ad3df94e5))\n* add iframe support ([1d0bf57](https://github.com/measuredco/puck/commit/1d0bf57894200edc6b9a883a41937f7a3141074f))\n* add `min` and `max` APIs to number fields ([4932a6e](https://github.com/measuredco/puck/commit/4932a6ef1b640410b3291cc67fb1f3153c04eac4))\n* add `selectedItem` convenience param to usePuck ([c1224d0](https://github.com/measuredco/puck/commit/c1224d026d37bbbcf1366804947771902e29d9bb))\n* add viewport switching ([ccf9149](https://github.com/measuredco/puck/commit/ccf91495f3a9f20a37051ba407abd992095a7b4d))\n* enable mapping of table rows in external fields ([d50c56e](https://github.com/measuredco/puck/commit/d50c56e829b482f13c5ec08acc76eed70494d3cf))\n* expose history via usePuck hook ([1b907cb](https://github.com/measuredco/puck/commit/1b907cba506dda7a2b1fe201a426e1c4bcfffecc))\n* hide array Add button when array is readOnly ([4e27c3f](https://github.com/measuredco/puck/commit/4e27c3f18a0fa9a97dcd5fd240b01a133d7cb153))\n* improve touch, contrast & keyboard a11y ([f975d87](https://github.com/measuredco/puck/commit/f975d87c5c2823e1f27161e6b6aa76a0d3fafad2))\n* refine UI for external field modal ([6a2afa1](https://github.com/measuredco/puck/commit/6a2afa1abbd33a062bca6962b547b5534ed93036))\n* support custom component labels via the new label param ([712fb8e](https://github.com/measuredco/puck/commit/712fb8eeac0502b2baea4c86a4494eb8f924ed82))\n* update to 12-tint color palette ([d43da58](https://github.com/measuredco/puck/commit/d43da581da3bd79324ed846ca5c5cd0c86469b23))\n* use InterVariable font ([88532fb](https://github.com/measuredco/puck/commit/88532fbc248a3a171dc2e26906dcd68ba5979570))\n\n\n### Bug Fixes\n\n* avoid FOUC of side bars on mobile ([83be956](https://github.com/measuredco/puck/commit/83be95643e4dcb96e30d0e6a9dbfe03c60f83002))\n* correctly infer objectFields type from props ([e8991cc](https://github.com/measuredco/puck/commit/e8991cc90d5fd899a3357f6d1f50b382d90aad23))\n* don't attempt to resolve data if component missing from config ([cc7d391](https://github.com/measuredco/puck/commit/cc7d391503cce3cbdbad9b769b5fb0fca6610cb0))\n* don't flash nested DropZones on first drag ([38c3dc4](https://github.com/measuredco/puck/commit/38c3dc418e047b7f1218c8c50cf3ba3f2e6b74d8))\n* don't unexpectedly show DropZone background ([2001fa2](https://github.com/measuredco/puck/commit/2001fa2bb6e69451f68cd94a3f872a0f83ff2b4b))\n* ensure font loads for ExternalFields ([e9bca75](https://github.com/measuredco/puck/commit/e9bca751926db8a88f4f6ad2bc135a10705987d9))\n* ensure heading-analyzer updates when content changes ([d75df7a](https://github.com/measuredco/puck/commit/d75df7a5c8ab365a4ef0de6c81c707e706433383))\n* ensure select and radio fields support read only arrays ([cbdf66d](https://github.com/measuredco/puck/commit/cbdf66d348acc3461f321956c80dbc87a896069e))\n* fix array field when used on root ([95280e6](https://github.com/measuredco/puck/commit/95280e686409342d3be3d68ec2acb90f7cfc570e))\n* fix renderDropZone method in editor ([2c738dd](https://github.com/measuredco/puck/commit/2c738dd3761596925caecfee2bfdcb2960a10b83))\n* lower opacity of DropZone background to support dark backgrounds ([9a5c0b8](https://github.com/measuredco/puck/commit/9a5c0b8ec57e41eeda3592d9a45ab00907a7a313))\n* make getItemSummary optional on ExternalFields, as expected ([26bc4ff](https://github.com/measuredco/puck/commit/26bc4ff320cc93bf4376edd190b3779774f2f87c))\n* only import Puck CSS on editor pages ([22a4182](https://github.com/measuredco/puck/commit/22a41823559d36fd06842496d59788004b316797))\n* prevent unexpected field behaviour when pressing \"Enter\" key ([bf4f527](https://github.com/measuredco/puck/commit/bf4f5277f5d5cbf7a7ccf473130055575a5e983a))\n* use strict return type for resolveData ([777cd3c](https://github.com/measuredco/puck/commit/777cd3c02a0b0ec8df1b81e19654b1179b56cb53))\n* vertically align field icons ([fa92436](https://github.com/measuredco/puck/commit/fa924363c8f2e5ad3d866793ba34a1b488250ce5))\n\n\n\n## [0.13.1](https://github.com/measuredco/puck/compare/v0.13.0...v0.13.1) (2023-12-23)\n\n\n### Bug Fixes\n\n* don't render plugins twice when using React strict mode ([f70c722](https://github.com/measuredco/puck/commit/f70c7222dd844257fab791fb4d5f8cf90e3361df))\n* replace crypto with uuid lib ([a84e06f](https://github.com/measuredco/puck/commit/a84e06feec977bca1ac7e08b6e55ba8afe0141dc))\n\n\n\n\n## [0.13.0](https://github.com/measuredco/puck/compare/v0.12.0...v0.13.0) (2023-12-19)\n\n\n### Features\n\n* add \"ui\" prop to Puck to set the initial state ([71f8b2f](https://github.com/measuredco/puck/commit/71f8b2f1143b9774fd763a8f5a3685957474237b))\n* add APIs to restrict components dropped in DropZones ([28f24f9](https://github.com/measuredco/puck/commit/28f24f927a2d1c378834f124e85abfcc2267a0d7))\n* add data migration API ([f987324](https://github.com/measuredco/puck/commit/f987324804d59e55a3a5e6770389305d88f39194))\n* add generic Config type to Puck and Render components ([1c4b97f](https://github.com/measuredco/puck/commit/1c4b97f0a8487785b5a677a2a1ba168b292e5ca4))\n* add object field type ([243278b](https://github.com/measuredco/puck/commit/243278bb01e34de6123a47d902fcc58ea7678642))\n* add Puck class to outer div ([0698a12](https://github.com/measuredco/puck/commit/0698a127e093cb2cf66fa35dafca80ebd4c73f89))\n* add search to external fields ([fe3b439](https://github.com/measuredco/puck/commit/fe3b4394c7464eeab69e1af5a96bd525bd15872a))\n* add transformProps lib to migrate component props ([1ec2a78](https://github.com/measuredco/puck/commit/1ec2a78968e10efc5666aaf994b6feea6c820449))\n* add usePuck hook ([13f3ccb](https://github.com/measuredco/puck/commit/13f3ccbd314e5a82f5a509c713ad34d3d0614b34))\n* introduce UI overrides API ([8a7c325](https://github.com/measuredco/puck/commit/8a7c3252d8aed2c160e390c1ba7c411d8b884b6f))\n* make onPublish prop optional ([60f317f](https://github.com/measuredco/puck/commit/60f317f75bb1a18bd59819d1323c45266334138c))\n* remove renderComponentList in favour of overrides API ([97f65e3](https://github.com/measuredco/puck/commit/97f65e3f0411abab66a72ea3c9ecd485cd941b4e))\n* replace existing plugin API with plugin overrides ([46cca26](https://github.com/measuredco/puck/commit/46cca26c879a2ae53cf3e668f1dad37bb480bd84))\n* support compositional Puck ([22f053f](https://github.com/measuredco/puck/commit/22f053fa6209735c27b172eb625ea25d9df4bb3d))\n* track isDragging in app state ([841ae12](https://github.com/measuredco/puck/commit/841ae126d3f5e8a9e40c064b69d5ee675169e4cd))\n\n\n### Bug Fixes\n\n* don't crash when loading external data into array field items ([d13d00b](https://github.com/measuredco/puck/commit/d13d00b67a7106889a0fc3beae94fa9c2e5bfcc3))\n* enable user to pass in config without casting ([ee211e2](https://github.com/measuredco/puck/commit/ee211e2a3ae6fbcb3d2b12316172e49f11fecd1e)), closes [#185](https://github.com/measuredco/puck/issues/185)\n* fix broken nested array fields ([7a3949f](https://github.com/measuredco/puck/commit/7a3949f7f10b2323504b31bcae9a9aa5d46f4074))\n* fix initial UI state on mobile ([3aa0057](https://github.com/measuredco/puck/commit/3aa005740b650879d95318a01ac9e2949ec5e9d8))\n* prevent pollution of global styles into component overlay ([3fcf8e3](https://github.com/measuredco/puck/commit/3fcf8e3f9975a14d8bc355e025585c9f55f233b1))\n* record history when a user selects an item ([3a649c9](https://github.com/measuredco/puck/commit/3a649c9922cc0a6c8c6c2b96f5fbe44bd3a6176a))\n* remove packages triggering superficial security warning ([0f52b61](https://github.com/measuredco/puck/commit/0f52b610769550b3365ab91f856b264d02d005c2))\n* respect label in radio fields ([fe550d7](https://github.com/measuredco/puck/commit/fe550d795eed20ce3a3004a2e7c8dfdbaca0b67d))\n* set aria-label on all loaders ([9adca27](https://github.com/measuredco/puck/commit/9adca2774dae5e532134be76de9c79e0b4af751c))\n* stop color pollution in external field modals ([2e1b5ef](https://github.com/measuredco/puck/commit/2e1b5ef330ebbddee8c44b5002be65c2361fda4f))\n* use correct title path in recipes ([60244ba](https://github.com/measuredco/puck/commit/60244ba5637d889530ae646986b1890c6b89efea))\n* watch puck.config.tsx in Remix recipe ([ecb276c](https://github.com/measuredco/puck/commit/ecb276c39fd3cf03d524b221b3f34b3a8df99823))\n\n\n\n\n## [0.12.0](https://github.com/measuredco/puck/compare/v0.11.0...v0.12.0) (2023-11-23)\n\n\n### Features\n\n* support React server components via @measured/puck/rsc bundle ([90ac161](https://github.com/measuredco/puck/commit/90ac161513d0c8c84f6b2bb968f7e5400c732a0a))\n* add remix recipe ([f882878](https://github.com/measuredco/puck/commit/f882878e081b44a2b0bd1f773114f3c35b8398b1))\n* add explicit rsc and css exports ([0b6a527](https://github.com/measuredco/puck/commit/0b6a52792628225d392775ba6b3d549aab5be59b))\n* improve responsive behaviour ([889b4c7](https://github.com/measuredco/puck/commit/889b4c7a91f1a9b95c9fd7d4b3cdb20b2ee4946b))\n* add visibility toggle for right-hand sidebar ([3d6c5d4](https://github.com/measuredco/puck/commit/3d6c5d479f2237400e0dc7cab6d5ed5773058d3b))\n* allow custom fields to set UI state during onChange ([388793c](https://github.com/measuredco/puck/commit/388793c9b0ac27b14a538b70357abd0dc4f26779))\n* expose field \"id\" to custom fields ([849161e](https://github.com/measuredco/puck/commit/849161ef0e2e2e01f6a1b9f517ba4bcc66cf6bd1))\n* improve IconButton accessibility ([4c71d39](https://github.com/measuredco/puck/commit/4c71d39d1138f0fc823ada04710d0057433475b7))\n* add new monospaced font stack ([c484ea6](https://github.com/measuredco/puck/commit/c484ea6bae5e6283bf82860e9a84413e60720163))\n* tweak Field input focus state ([8012afd](https://github.com/measuredco/puck/commit/8012afdd9be2e3bc96185b4f0208b3ebdef0ed21))\n\n\n### Bug Fixes\n\n* don't enable style pollution of input background color ([bb1a76b](https://github.com/measuredco/puck/commit/bb1a76b314f744b76197cb670c448abc7896a45e))\n* don't reset array item labels when changing order ([57563e1](https://github.com/measuredco/puck/commit/57563e1da1826dbfa08a32fabb27153e4618ab40))\n* ensure field icon and label are vertically aligned ([caa40e0](https://github.com/measuredco/puck/commit/caa40e0499570831e5779f9a6a031e38f054c3f8))\n* ensure root render receives props from latest data API ([abb6ff1](https://github.com/measuredco/puck/commit/abb6ff1bd53d7f93ef0ac287290712943ca2c1ce))\n* export missing PuckAction type ([f22f32d](https://github.com/measuredco/puck/commit/f22f32dc5569eaa9cea90f896cf4cdafc59940fe))\n* fix rootResolver behaviour when using recommended root data API ([5c13de5](https://github.com/measuredco/puck/commit/5c13de58a335f2b4c81f2b424fee8b4a356fb563))\n* migrate to @hello-pangea/dnd to fix defaultProps warning ([2c97362](https://github.com/measuredco/puck/commit/2c97362e15f5d2046dc216c6e5fc25f5199d0a37))\n* prevent inconsistent default input font-size ([99f90b3](https://github.com/measuredco/puck/commit/99f90b3ba81bf286758685f7c2a457abaffeb2e1))\n* show a default value when no placeholder set on external fields ([e30b5b6](https://github.com/measuredco/puck/commit/e30b5b69b6a9f6467db4b05c55ffdc5f1ecebcfb))\n* stop `zones` getting wiped out if data prop updated ([0c4514f](https://github.com/measuredco/puck/commit/0c4514fcde24d0ba585fea0981d73e7a8188840f))\n* stop style pollution into array field items ([03b89d5](https://github.com/measuredco/puck/commit/03b89d568ded7cae6eb34e0dcf45e60eb758b552))\n* stretch external field table to width of modal ([f6d89f6](https://github.com/measuredco/puck/commit/f6d89f69f1a24f94479365b9d955a3ea60b17b8d))\n* use correct root data API in next recipe example database ([b598144](https://github.com/measuredco/puck/commit/b5981446ee64a3b5451eb17b8d42263f42df179f))\n* use Inter font in button type Buttons ([1973847](https://github.com/measuredco/puck/commit/19738473723c49ddb0d764864283bf597280c7c5))\n\n\n\n\n## [0.11.3](https://github.com/measuredco/puck/compare/v0.11.2...v0.11.3) (2023-11-12)\n\n\n### Bug Fixes\n\n* ensure field debounce doesn't sporadically lock preview update ([487ab83](https://github.com/measuredco/puck/commit/487ab83e2ffa42ad93ab90c2eadea9486008de9b))\n* stop generator crashing on Windows due to commits with single quotes ([ab9d43f](https://github.com/measuredco/puck/commit/ab9d43f08113ef1c3f6fa30f7f87ba881b74a1e1))\n\n\n\n\n## [0.11.2](https://github.com/measuredco/puck/compare/v0.11.1...v0.11.2) (2023-11-11)\n\n\n### Bug Fixes\n\n* add missing database.json back to generated next recipe ([3c15255](https://github.com/measuredco/puck/commit/3c15255a8f7f5e77c047ce853382f92715045c8d))\n\n\n\n\n## [0.11.1](https://github.com/measuredco/puck/compare/v0.11.0...v0.11.1) (2023-11-11)\n\n\n### Bug Fixes\n\n* include next recipe in generator ([5b833ef](https://github.com/measuredco/puck/commit/5b833efd0f87b21e57303256e89f1456254b82bf))\n\n\n\n\n## [0.11.0](https://github.com/measuredco/puck/compare/v0.10.0...v0.11.0) (2023-11-03)\n\n\n### Bug Fixes\n\n* don't flicker root DropZone when dragging ([358435c](https://github.com/measuredco/puck/commit/358435c36a216e6749be73599ab631ffdd8069c8))\n* ensure array fields can render if value is undefined ([47ab3c9](https://github.com/measuredco/puck/commit/47ab3c971e4aafec443e8b4d73e7c921dec38ac6))\n* isolate external field modal from high z-indexes ([fdf97c7](https://github.com/measuredco/puck/commit/fdf97c7f6da6035447e9b7deec9019217875c4ef))\n* make Field types required based on type ([daf36ac](https://github.com/measuredco/puck/commit/daf36ac8864dc1b0f324c3e08294f9d62568acf2))\n* prevent global style pollution in external fields ([429731d](https://github.com/measuredco/puck/commit/429731dbb77de2d8ca1c4a88832c73294a9b141c))\n* prevent long header titles from rendering over actions ([4613df4](https://github.com/measuredco/puck/commit/4613df47fdde9ac796419f02a2d9f649892b3d35))\n* use correct heading component for external inputs ([462266d](https://github.com/measuredco/puck/commit/462266d069b04a3de09684af4b816e1d1dac46dc))\n\n\n### Features\n\n* add categories API for grouping components in side bar ([594cc76](https://github.com/measuredco/puck/commit/594cc76c763a7d2ce06cd78f34a4683c0fa89f8e))\n* add read-only states to all field types ([746d896](https://github.com/measuredco/puck/commit/746d896996f01d086d557f2a2918f4e76e3f5b35))\n* add icon to external fields ([a3a018b](https://github.com/measuredco/puck/commit/a3a018bb1876fd4b831676e8ff848052ec7ba527))\n* add loading state to external field modal ([5b4fc92](https://github.com/measuredco/puck/commit/5b4fc92f96caf83148fa335321dad3a5f1a65789))\n* add lock icon when field is read-only ([a051000](https://github.com/measuredco/puck/commit/a05100016fed1e368be333f2707087b152fb4c0e))\n* add mapProp API to external fields ([86c4979](https://github.com/measuredco/puck/commit/86c49795ac1d198836242772ec01bd755ee699c8))\n* add renderComponentList API ([ec985e3](https://github.com/measuredco/puck/commit/ec985e3d28a4915f8fb2816b9599060d20bbf621))\n* add resolveData API for modifying props dynamically ([c1181ad](https://github.com/measuredco/puck/commit/c1181ad9b1de6cc036cfedebcc3e57334ef62196))\n* deprecate adaptors in favour of new external field APIs ([7f13efc](https://github.com/measuredco/puck/commit/7f13efc769ddc77fc7931a8191796f017354e89a))\n* deprecate magic adaptor _data behaviour in favour of resolveData API ([4ee31e7](https://github.com/measuredco/puck/commit/4ee31e7c0d93578976b2b655e0c56477571f8341))\n* deprecate props under root in favour of `root.props` ([7593584](https://github.com/measuredco/puck/commit/759358446e01b4320e55156dbe849d264e4e7edf))\n* make external field more consistent with other fields ([5bfbc5b](https://github.com/measuredco/puck/commit/5bfbc5bf71b0af72e97e24b5828ad7009836e51e))\n* update next recipe to render to static ([a333857](https://github.com/measuredco/puck/commit/a33385783022179e12ef3f732cb4e2e387985030))\n\n\n### Performance Improvements\n\n* cache data between fetchList calls in external fields ([04b7322](https://github.com/measuredco/puck/commit/04b7322d5fa5a5506b853c3dcde7a0b47d5b21bc))\n* improve render performance of fields ([d92de7f](https://github.com/measuredco/puck/commit/d92de7fe6eaf081deff139b010e4741d07ba6114))\n\n\n\n\n## [0.10.0](https://github.com/measuredco/puck/compare/v0.9.0...v0.10.0) (2023-10-18)\n\n\n### Bug Fixes\n\n* ensure layer tree consistently shows selected item ([6a9145c](https://github.com/measuredco/puck/commit/6a9145c23b1461e46f3568e9a107d3c429aa87d2))\n* only render strings or numbers in external adaptors ([3c337be](https://github.com/measuredco/puck/commit/3c337be171c5fa6ad464f5a16fcb7f17e9b1a4f9))\n* prevent style pollution for select fields ([fa7af7d](https://github.com/measuredco/puck/commit/fa7af7da9d770d5e790944d421dc0a30f0da84b1))\n\n\n### Features\n\n* align component list UI with refreshed array fields ([74cd3a7](https://github.com/measuredco/puck/commit/74cd3a7ba9100e5e7e1a5e626511906fbdf75b98))\n* enable drag-and-drop of array items ([12800f8](https://github.com/measuredco/puck/commit/12800f816b872d614ed50c9fcf3179f41dbbbfb2))\n* expose state dispatcher to plugins ([e94accb](https://github.com/measuredco/puck/commit/e94accb22bae2afbb30728e0d58f8c6a558b3e39))\n* expose state to plugins, removing data ([89f9f2e](https://github.com/measuredco/puck/commit/89f9f2e3a526a1459d14bdd7301f2c761f7c340d))\n* expose state to renderHeader, removing data ([29ddaaf](https://github.com/measuredco/puck/commit/29ddaaf376b57134be46a489e7686978d0465669))\n* record application state in undo/redo history ([0f2d7c5](https://github.com/measuredco/puck/commit/0f2d7c55aebe898925084ff27d5af97e9a7b9090))\n* refresh UI for array fields ([5ef8a96](https://github.com/measuredco/puck/commit/5ef8a96b6952d450927a499f1ec0f93610450864))\n\n\n\n\n## [0.9.0](https://github.com/measuredco/puck/compare/v0.8.0...v0.9.0) (2023-10-06)\n\n\n### Bug Fixes\n\n* fill empty space under puck-root ([d42cfb6](https://github.com/measuredco/puck/commit/d42cfb69aa7c7e0b70321b4b509efd3c6fdbe393))\n* prevent global pollution of Heading color ([327721c](https://github.com/measuredco/puck/commit/327721c705546a538fedd0a3b794926605cd58fc))\n* render `icon` if provided to FieldLabel ([ae01891](https://github.com/measuredco/puck/commit/ae01891ce55b844c5a76a20faa33e5df16c2d593))\n* reset stacking context for each item ([a826492](https://github.com/measuredco/puck/commit/a826492ee7bab57710edad6b7df498f294398606))\n\n\n### Features\n\n* add undo/redo history ([222697e](https://github.com/measuredco/puck/commit/222697e5b9e95e3b28d0dfd9ac0b85f46c56068e))\n* make actions sticky to component scroll ([f3e5b50](https://github.com/measuredco/puck/commit/f3e5b50d921f0c75978f805a7d44b88511fbaf69))\n\n\n\n\n## [0.8.0](https://github.com/measuredco/puck/compare/v0.7.0...v0.8.0) (2023-10-03)\n\n\n ### Features\n\n * introduce DropZone API for nesting components and advanced layouts ([5053a84](https://github.com/measuredco/puck/commit/5053a8430de1f4bfb6fb7a4b1f194a1474ed3ae3))\n * introduce new outline UI ([e32c4ff](https://github.com/measuredco/puck/commit/e32c4ff784a2fcc5f2e2879807c045bd2742f4ac))\n * redesign action overlay and move outside of component ([5145cba](https://github.com/measuredco/puck/commit/5145cba6595e2051d14a7bfd37d9b180d9553330))\n * cast number field types to Number ([d5df959](https://github.com/measuredco/puck/commit/d5df95946dd9abf1502cb21bfc8682dd98efb1e1))\n\n\n ### Bug Fixes\n\n * add missing id type to render props ([18753cf](https://github.com/measuredco/puck/commit/18753cf1142d70f7100bc6fd5aa913813491042e))\n * add missing optional chaining operator to next recipe ([a368319](https://github.com/measuredco/puck/commit/a368319ec73adfc5bce8fb6bd31ac8e46e669400))\n * don't show margin underneath placeholder when dragging in ([2620455](https://github.com/measuredco/puck/commit/26204557b6fc92b208ee1051921965b793a78b1e))\n * don't switch between controlled/uncontrolled inputs ([b20e298](https://github.com/measuredco/puck/commit/b20e2980be6df6d57f9dfb6987b512686ccc5a7a))\n * ensure form styles override global styles ([104091a](https://github.com/measuredco/puck/commit/104091ac87c95d1395687d1785e621f5580efd87))\n * ensure hooks can always be used within render functions ([cbf8e8e](https://github.com/measuredco/puck/commit/cbf8e8e49fc5d43a8818cf41010cfba6034bbf28))\n * ensure types allow for nested arrays ([06b145b](https://github.com/measuredco/puck/commit/06b145b9089548725166fec3dd54f757b6e932cc))\n * fix unpredictable rendering of drop placeholder ([bf5f16b](https://github.com/measuredco/puck/commit/bf5f16b394ef950318949e9a440dd1bf2407636e))\n * only show sidebar scroll bars if necessary ([87c8736](https://github.com/measuredco/puck/commit/87c87369003f417600ca0a7bb38041de5c675afb))\n * prevent global styles from overwriting fieldset styles ([550bd0e](https://github.com/measuredco/puck/commit/550bd0ef9263766817709cea2c0365e9bd3e95cf))\n * respect labels for array item fields ([f2e7843](https://github.com/measuredco/puck/commit/f2e7843de0b12df4b15b1c1dd953e8b4d82ce366))\n * prevent global styles from overwriting outline styles ([1dc222c](https://github.com/measuredco/puck/commit/1dc222cfa5924aca2e5eb5ea535f77cfe2fe1281))\n * prevent styles from clashing with dark mode root element ([8506e8e](https://github.com/measuredco/puck/commit/8506e8e7f72aa8df7e69a1e7349eae273ebdee0e))\n * upgrade next version in recipe to ensure vercel builds pass ([c2d7fae](https://github.com/measuredco/puck/commit/c2d7faeed59fea5c7c795f76915cf354151d644d))\n\n\n ### Performance Improvements\n\n * reduce bundle size by 61% by removing unused react-feather icons ([f4b0563](https://github.com/measuredco/puck/commit/f4b0563e38a93a5f582b0210b0d75a846e3bada4))\n\n\n## [0.7.0](https://github.com/measuredco/puck/compare/v0.6.2...v0.7.0) (2023-09-14)\n\n\n### Features\n\n* add support for custom fields ([b46b721](https://github.com/measuredco/puck/commit/b46b721aea70698e249cd3dfff34f88717952da7))\n\n\n\n\n## [0.6.2](https://github.com/measuredco/puck/compare/v0.6.1...v0.6.2) (2023-09-07)\n\n\n### Bug Fixes\n\n* bust cache in generated app on publish ([6e1c8ed](https://github.com/measuredco/puck/commit/6e1c8ed9df1be9634e49d18edc8c42c7ebf6e864))\n* don't 404 on homepage in generated app ([8fd7b3b](https://github.com/measuredco/puck/commit/8fd7b3b38a046776f69105e25f86a622b5e41c40))\n* don't call API when building generated app ([8041fc1](https://github.com/measuredco/puck/commit/8041fc1da598f61b4c30c711d8233466c8643099))\n* fix type issues in generated app ([b16e98e](https://github.com/measuredco/puck/commit/b16e98e15407678524d904211ecc74230b205018))\n\n\n\n\n## [0.6.1](https://github.com/measuredco/puck/compare/v0.6.0...v0.6.1) (2023-09-06)\n\n\n### Bug Fixes\n\n* add missing glob dependency for create-puck-app ([7dbe190](https://github.com/measuredco/puck/commit/7dbe1902bf1c31a674b35c1269ee44ac09aac763))\n* return component to original position when drag cancelled ([cae760f](https://github.com/measuredco/puck/commit/cae760fbfb8497de09311bb81e3059c07efe75ac))\n* use correct peer dependencies for react ([39f4e7f](https://github.com/measuredco/puck/commit/39f4e7fab5818266aa75046d2c2ca6e858803a13))\n\n\n\n\n## [0.6.0](https://github.com/measuredco/puck/compare/v0.5.0...v0.6.0) (2023-08-15)\n\n\n### Bug Fixes\n\n* ensure component label doesn't inherit user styles ([5c0d65b](https://github.com/measuredco/puck/commit/5c0d65b8519897c454b2f321330dd24dd30f831f))\n* make default props on root optional ([dc5b1ae](https://github.com/measuredco/puck/commit/dc5b1aec6518f1c3ed1ad8f798bcfe359077865f))\n\n\n### Features\n\n* export Button and IconButton to make extending header seamless ([d98eb29](https://github.com/measuredco/puck/commit/d98eb298f14ef0ae8888a710cadf85fac13e084d))\n\n\n\n\n## [0.5.0](https://github.com/measuredco/puck/compare/v0.4.1...v0.5.0) (2023-08-14)\n\n\n### Features\n\n* add headerTitle and headerPath APIs ([ae5c7c2](https://github.com/measuredco/puck/commit/ae5c7c2083b16e8f69e9995d74f8be7fffbe6ea5))\n* gracefully fallback if component definition doesn't exist ([d7e3190](https://github.com/measuredco/puck/commit/d7e31901626734ce43cd9161971d9811b6d5c483))\n* refine editor styles ([9e57649](https://github.com/measuredco/puck/commit/9e57649e7bd9444b290122ecbc1c40bc6d88c3d1))\n* support booleans in radios and selects ([acb7a96](https://github.com/measuredco/puck/commit/acb7a96b727c9bc6d4599dcd06e2448c10e82d0f))\n\n\n\n\n## [0.4.1](https://github.com/measuredco/puck/compare/v0.4.0...v0.4.1) (2023-08-09)\n\n\n### Bug Fixes\n\n* move incorrect dependency to devDependencies ([6ffd86c](https://github.com/measuredco/puck/commit/6ffd86c9d668449991a0642d79fa85c1a364deae))\n\n\n\n\n## [0.4.0](https://github.com/measuredco/puck/compare/v0.3.2...v0.4.0) (2023-07-07)\n\n\n### Bug Fixes\n\n* avoid hardcoding localhost in strapi adaptor ([f8d920c](https://github.com/measuredco/puck/commit/f8d920c6d188e9b8c9ea1bc7cb58d63e6f25d823))\n* stretch ExternalInput button to fill container ([69ee221](https://github.com/measuredco/puck/commit/69ee221e41ab09aae3d4d4d89c92d799d9b387f9))\n\n\n### Features\n\n* add adaptor-fetch package ([eaf7875](https://github.com/measuredco/puck/commit/eaf787527c0f76f3d43cbb8fd6fd1542aebdf5b0))\n* rename page to root in API ([8519675](https://github.com/measuredco/puck/commit/8519675ab450438ae459bee54a8ae00bdc7553b4))\n\n\n\n\n## [0.3.2](https://github.com/measuredco/puck/compare/v0.3.1...v0.3.2) (2023-07-06)\n\n\n### Bug Fixes\n\n* export correct files for Strapi adaptor ([577a849](https://github.com/measuredco/puck/commit/577a84928cd3c8e4f7a57d1f2746abd69db23eeb))\n* set correct font family for empty outlines ([3d45841](https://github.com/measuredco/puck/commit/3d4584190e13f9b07077d6012d1ce4197de0a436))\n\n\n\n\n## [0.3.1](https://github.com/measuredco/puck/compare/v0.3.0...v0.3.1) (2023-07-05)\n\n\n### Bug Fixes\n\n* include .gitignore in recipes ([e18bf67](https://github.com/measuredco/puck/commit/e18bf67e366c431a6bea08a9965b7d40866119e2))\n\n\n\n\n## [0.3.0](https://github.com/measuredco/puck/compare/v0.2.2...v0.3.0) (2023-07-05)\n\n\n### Features\n\n* release create-puck-app ([0722a65](https://github.com/measuredco/puck/commit/0722a656c7da4b4caa9212385affd62323a56c92))\n\n\n\n\n## [0.2.2](https://github.com/measuredco/puck/compare/v0.2.1...v0.2.2) (2023-07-05)\n\n\n### Bug Fixes\n\n* ensure margin collapse fix works with coloured backgrounds ([fdec4fa](https://github.com/measuredco/puck/commit/fdec4faac197e541a04785ab7c16919223b3ec9d))\n\n\n\n\n## [0.2.1](https://github.com/measuredco/puck/compare/v0.2.0...v0.2.1) (2023-07-05)\n\n\n### Bug Fixes\n\n* remove border on draggable components ([726a27c](https://github.com/measuredco/puck/commit/726a27cc0df6b8c439d0aa8e0dd05cac32774b3e))\n\n\n\n\n## [0.2.0](https://github.com/measuredco/puck/compare/v0.1.3...v0.2.0) (2023-07-04)\n\n\n### Bug Fixes\n\n* inject react into libraries ([7e10d91](https://github.com/measuredco/puck/commit/7e10d9141901aaf79ae4ebfa3a7b60b589c6c715))\n* render drag and drop correctly when using margins ([f88025b](https://github.com/measuredco/puck/commit/f88025bf27479036426305a1004acfe8f0ab6644))\n\n\n### Features\n\n* add icons to inputs ([f47482e](https://github.com/measuredco/puck/commit/f47482e8cabd334360666ea90d2e6a12b3648cf9))\n* improve UI for fields ([aa0d2fe](https://github.com/measuredco/puck/commit/aa0d2fe56ff633b9c2cff2023ae00c8b9ec04df3))\n* rename \"group\" field type to \"array\" ([4f99c7d](https://github.com/measuredco/puck/commit/4f99c7d761b8e1cfa280fb5e74f6f369be84d7a2))\n\n\n\n\n## [0.1.6](https://github.com/measuredco/puck/compare/v0.1.3...v0.1.6) (2023-07-04)\n\n\n### Bug Fixes\n\n* inject react into libraries ([7e10d91](https://github.com/measuredco/puck/commit/7e10d9141901aaf79ae4ebfa3a7b60b589c6c715))\n\n\n\n\n## 0.1.5 (2023-07-03)\n\n- Publish all packages\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "# Contributor Covenant Code of Conduct\n\n## Our Pledge\n\nWe as members, contributors, and leaders pledge to make participation in our\ncommunity a harassment-free experience for everyone, regardless of age, body\nsize, visible or invisible disability, ethnicity, sex characteristics, gender\nidentity and expression, level of experience, education, socio-economic status,\nnationality, personal appearance, race, caste, color, religion, or sexual\nidentity and orientation.\n\nWe pledge to act and interact in ways that contribute to an open, welcoming,\ndiverse, inclusive, and healthy community.\n\n## Our Standards\n\nExamples of behavior that contributes to a positive environment for our\ncommunity include:\n\n- Demonstrating empathy and kindness toward other people\n- Being respectful of differing opinions, viewpoints, and experiences\n- Giving and gracefully accepting constructive feedback\n- Accepting responsibility and apologizing to those affected by our mistakes,\n  and learning from the experience\n- Focusing on what is best not just for us as individuals, but for the overall\n  community\n\nExamples of unacceptable behavior include:\n\n- The use of sexualized language or imagery, and sexual attention or advances of\n  any kind\n- Trolling, insulting or derogatory comments, and personal or political attacks\n- Public or private harassment\n- Publishing others' private information, such as a physical or email address,\n  without their explicit permission\n- Other conduct which could reasonably be considered inappropriate in a\n  professional setting\n\n## Enforcement Responsibilities\n\nCommunity leaders are responsible for clarifying and enforcing our standards of\nacceptable behavior and will take appropriate and fair corrective action in\nresponse to any behavior that they deem inappropriate, threatening, offensive,\nor harmful.\n\nCommunity leaders have the right and responsibility to remove, edit, or reject\ncomments, commits, code, wiki edits, issues, and other contributions that are\nnot aligned to this Code of Conduct, and will communicate reasons for moderation\ndecisions when appropriate.\n\n## Scope\n\nThis Code of Conduct applies within all community spaces, and also applies when\nan individual is officially representing the community in public spaces.\nExamples of representing our community include using an official e-mail address,\nposting via an official social media account, or acting as an appointed\nrepresentative at an online or offline event.\n\n## Enforcement\n\nInstances of abusive, harassing, or otherwise unacceptable behavior may be\nreported to the community leaders responsible for enforcement via Discord.\nAll complaints will be reviewed and investigated promptly and fairly.\n\nAll community leaders are obligated to respect the privacy and security of the\nreporter of any incident.\n\n## Enforcement Guidelines\n\nCommunity leaders will follow these Community Impact Guidelines in determining\nthe consequences for any action they deem in violation of this Code of Conduct:\n\n### 1. Correction\n\n**Community Impact**: Use of inappropriate language or other behavior deemed\nunprofessional or unwelcome in the community.\n\n**Consequence**: A private, written warning from community leaders, providing\nclarity around the nature of the violation and an explanation of why the\nbehavior was inappropriate. A public apology may be requested.\n\n### 2. Warning\n\n**Community Impact**: A violation through a single incident or series of\nactions.\n\n**Consequence**: A warning with consequences for continued behavior. No\ninteraction with the people involved, including unsolicited interaction with\nthose enforcing the Code of Conduct, for a specified period of time. This\nincludes avoiding interactions in community spaces as well as external channels\nlike social media. Violating these terms may lead to a temporary or permanent\nban.\n\n### 3. Temporary Ban\n\n**Community Impact**: A serious violation of community standards, including\nsustained inappropriate behavior.\n\n**Consequence**: A temporary ban from any sort of interaction or public\ncommunication with the community for a specified period of time. No public or\nprivate interaction with the people involved, including unsolicited interaction\nwith those enforcing the Code of Conduct, is allowed during this period.\nViolating these terms may lead to a permanent ban.\n\n### 4. Permanent Ban\n\n**Community Impact**: Demonstrating a pattern of violation of community\nstandards, including sustained inappropriate behavior, harassment of an\nindividual, or aggression toward or disparagement of classes of individuals.\n\n**Consequence**: A permanent ban from any sort of public interaction within the\ncommunity.\n\n## Attribution\n\nThis Code of Conduct is adapted from the [Contributor Covenant](https://www.contributor-covenant.org),\nversion 2.1, available at [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html](https://www.contributor-covenant.org/version/2/1/code_of_conduct.html).\n\nCommunity Impact Guidelines were inspired by\n[Mozilla's code of conduct enforcement ladder](https://opensource.creativecommons.org/community/code-of-conduct/enforcement/).\n\nFor answers to common questions about this code of conduct, see the FAQ at [https://www.contributor-covenant.org/faq](https://www.contributor-covenant.org/faq). Translations are available at\n[https://www.contributor-covenant.org/translations](https://www.contributor-covenant.org/translations).\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Contributing to Puck\n\nPuck is still under heavy development, having gained significant interest at an early stage. The contribution guidelines are designed to help us balance community engagement with our vision and direction.\n\n## Reporting bugs or requesting features\n\nBugs and feature requests are best reported via GitHub issues. Always check if the issue already exists before opening a new one.\n\nIf you're unsure whether or not you've encountered a bug, feel free to ask first 👇\n\n## Asking questions\n\nThere are several ways to ask questions or ask for help:\n\n- Open a [discussion](https://github.com/puckeditor/puck/discussions) via GitHub\n- Use the #chat or #help channels in our [Discord server](https://discord.gg/D9e4E3MQVZ)\n\n_Please only use GitHub issues for bugs and feature requests, and not for questions._\n\n## Labels\n\nWe manage our backlog using labels. Labels can help you understand the status of each ticket.\n\n### Status labels\n\n- **ready** - this ticket has a description and is ready to be worked on.\n- **in triage** - this ticket has been seen by the Puck team and we are identifying next steps. Tickets may stay in this state until we're ready to process them.\n- **blocked** - this ticket is blocked by another ticket. The relationship should be made apparent in the comments.\n\n### Type labels\n\nDenoted by the `type:` prefix.\n\n- **type: bug**\n- **type: feature**\n- **type: docs**\n- **type: performance**\n- **type: test**\n\n### Other labels\n\n- **good first issue** - if you're new to contributing on Puck, this is a good place to start.\n- **opinions wanted** - we're looking for opinions on this ticket. Feel free to chime in with comments or suggestions.\n\n## Contributing code\n\n### When to contribute\n\n#### Existing issues\n\nIf picking up an existing GitHub issue, please respect the **ready** status label.\n\nAny PRs made to close issues without the **ready** label are at risk of being premature and likely to be rejected.\n\n#### New issues\n\nIf you've reported a bug via an issue and have a fix, you don't need to wait for the **ready** label before proposing a fix.\n\nIt's also okay to propose solutions to your own feature requests, but without proper discussion the solution may be rejected.\n\n#### No issue\n\nPRs without issues may be accepted for small fixes, but larger changes may be rejected or require further discussion.\n\n### Setting up the environment\n\nPuck uses:\n\n- TypeScript\n- CSS Modules\n- Turborepo for monorepo tooling\n- Yarn for package management and release automation\n- Next.js for demo applications\n\nTo get setup, first clone the repo and then install the dependencies:\n\n```sh\nyarn\n```\n\nRather than running the entire monorepo, it's quicker to run the project you need.\n\nGenerally, it's easiest to work in the context of the demo application:\n\n```sh\ncd apps/demo\nyarn dev\n```\n\n### Style\n\n#### TypeScript\n\n- Avoid the use of `any`.\n- Tests appreciated, but not required. They may be requested for complicated code.\n\n#### CSS\n\n- Class names must follow the [SUIT CSS](https://suitcss.github.io) methodology. This is a tooling-angostic convention used at [@puckeditor](https://github.com/puckeditor) for all CSS work.\n- Don't rely on global styles. Puck is deployed into hostile third-party environments and we have no control over what CSS may be running on the page.\n\n#### Commits\n\n**Keep your PRs focused to a single issue**. This makes it easier to review and is necessary for our release process.\n\nWe rely on [angular-style conventional commits](https://github.com/angular/angular.js/blob/master/DEVELOPERS.md#-git-commit-guidelines) for automating our releases, determining the version bump and generating our changelog.\n\nYou generally don't need to write perfect commit messages yourselves - we squash most PRs and rewrite the messages on merge.\n\nIf you need to solve multiple issues, it's best to split it into multiple PRs. Or, if you're comfortable writing conventional commits, you can also split each change into a separate commit. The team is more likely to have opinions about this and you may be asked to reword your commits.\n\n### Additional guidance\n\n#### Public APIs\n\nIf your PR introduces or changes public APIs, it will come under additional scrutiny to avoid introducing breaking changes.\n\n## Releases\n\n### Canary\n\nA canary release is automatically deployed after each merge to `main`. These are suffixed with the hash of the commit, for example `0.10.0-canary.42c24f1`.\n\n### Latest\n\nReleases are triggered manually when the team feels the `main` branch is sufficiently stable.\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) The Puck Contributors.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<br /><br /><br />\n\n<div align=\"center\">\n\n<a href=\"https://puckeditor.com?utm_source=readme&utm_medium=code&utm_campaign=repo&utm_contents=logo\">\n  <picture>\n    <source media=\"(prefers-color-scheme: dark)\" srcset=\"https://res.cloudinary.com/die3nptcg/image/upload/Puck_Logo_White_RGB_j2rwgg.svg\" height=\"100px\" aria-label=\"Puck logo\">\n    <img src=\"https://res.cloudinary.com/die3nptcg/image/upload/Puck_Logo_Black_RGB_dqsjag.svg\" height=\"100px\" aria-label=\"Puck logo\">\n  </picture>\n</a>\n\n_Create your own AI page builder_\n\n[Documentation](https://puckeditor.com/docs?utm_source=readme&utm_medium=code&utm_campaign=repo&utm_contents=docs_link) • [Demo](https://demo.puckeditor.com/edit?utm_source=readme&utm_medium=code&utm_campaign=repo&utm_contents=demo_link) • [Discord](https://discord.gg/V9mDAhuxyZ) • [Contributing](https://github.com/puckeditor/puck/blob/main/CONTRIBUTING.md)\n\n⭐️ Enjoying Puck? Please [leave a star](https://github.com/puckeditor/puck)!\n\n<br />\n\n[![GIF showing a page being created in the Puck Editor, with components being added, arranged, and customized in real time](https://github.com/user-attachments/assets/25e1ae25-ca5e-450f-afa0-01816830b731)](https://demo.puckeditor.com/edit)\n\n</div>\n\n## What is Puck?\n\nPuck is a modular, open-source visual editor for React.js. You can use Puck to build custom drag-and-drop experiences with your own application and React components.\n\nBecause Puck is just a React component, it plays well with all React.js environments, including Next.js. You own your data and there’s no vendor lock-in.\n\nPuck is also [licensed under MIT](https://github.com/puckeditor/puck?tab=MIT-1-ov-file#readme), making it suitable for both internal systems and commercial applications.\n\n## Quick start\n\nInstall the package:\n\n```sh\nnpm i @puckeditor/core --save # or npx create-puck-app my-app\n```\n\nRender the editor:\n\n```jsx\n// Editor.jsx\nimport { Puck } from \"@puckeditor/core\";\nimport \"@puckeditor/core/puck.css\";\n\n// Create Puck component config\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        children: {\n          type: \"text\",\n        },\n      },\n      render: ({ children }) => {\n        return <h1>{children}</h1>;\n      },\n    },\n  },\n};\n\n// Describe the initial data\nconst initialData = {};\n\n// Save the data to your database\nconst save = (data) => {};\n\n// Render Puck editor\nexport function Editor() {\n  return <Puck config={config} data={initialData} onPublish={save} />;\n}\n```\n\nRender the page:\n\n```jsx\n// Page.jsx\nimport { Render } from \"@puckeditor/core\";\nimport \"@puckeditor/core/puck.css\";\n\nexport function Page() {\n  return <Render config={config} data={data} />;\n}\n```\n\n## Recipes\n\nUse `create-puck-app` to quickly spin up a a pre-configured app based on our provided [recipes](https://github.com/puckeditor/puck/tree/main/recipes):\n\n```sh\nnpx create-puck-app my-app\n```\n\nAvailable recipes include:\n\n- [**next**](https://github.com/puckeditor/puck/tree/main/recipes/next): Next.js example, using App Router and static page generation\n- [**remix**](https://github.com/puckeditor/puck/tree/main/recipes/remix): Remix Run v2 example, using dynamic routes at root-level\n- [**react-router**](https://github.com/puckeditor/puck/tree/main/recipes/react-router): React Router v7 app example, using dynamic routes to create pages at any level\n\n## Community\n\n- [Discord server](https://discord.gg/D9e4E3MQVZ) for discussions\n- [awesome-puck](https://github.com/puckeditor/awesome-puck) community repo for plugins, custom fields & more\n\n## Get support\n\nIf you have any questions about Puck, please open a [GitHub issue](https://github.com/puckeditor/puck/issues) or join us on [Discord](https://discord.gg/D9e4E3MQVZ).\n\nOr [book a discovery call](https://app.cal.com/chrisvxd/puck-enquiry/) for hands-on support and consultancy.\n\n## License\n\nMIT © [The Puck Contributors](https://github.com/puckeditor/puck/graphs/contributors)\n"
  },
  {
    "path": "apps/demo/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: [\"custom\"],\n};\n"
  },
  {
    "path": "apps/demo/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# vercel\n.vercel\n\ndatabase.json"
  },
  {
    "path": "apps/demo/README.md",
    "content": "# `next` recipe\n\nThe `next` recipe showcases one of the most powerful ways to implement Puck using to provide an authoring tool for any route in your Next app.\n\n## Demonstrates\n\n- Next.js 13 App Router implementation\n- JSON database implementation with HTTP API\n- Catch-all routes to use puck for any route on the platform\n\n## Usage\n\nRun the generator and enter `next` when prompted\n\n```\nnpx create-puck-app my-app\n```\n\nStart the server\n\n```\nyarn dev\n```\n\nNavigate to the homepage at https://localhost:3000. To edit the homepage, access the Puck editor at https://localhost:3000/edit.\n\nYou can do this for any route on the application, **even if the page doesn't exist**. For example, visit https://localhost:3000/hello/world and you'll receive a 404. You can author and publish a page by visiting https://localhost:3000/hello/world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the example API routes in `/app/api/puck/route.ts` and server component in `/app/[...puckPath]/page.tsx`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the API calls in `/app/api/puck/route.ts`\n- Implement a custom puck configuration in `puck.config.tsx`\n\n## License\n\nMIT © [The Puck Contributors](https://github.com/puckeditor/puck/graphs/contributors).\n"
  },
  {
    "path": "apps/demo/app/[...puckPath]/client.tsx",
    "content": "\"use client\";\n\nimport { AutoField, Button, FieldLabel, Puck, Render } from \"@/core\";\nimport headingAnalyzer from \"@/plugin-heading-analyzer/src/HeadingAnalyzer\";\nimport config from \"../../config\";\nimport { useDemoData } from \"../../lib/use-demo-data\";\nimport { useEffect, useState } from \"react\";\nimport { Type } from \"lucide-react\";\n\nexport function Client({ path, isEdit }: { path: string; isEdit: boolean }) {\n  const metadata = {\n    example: \"Hello, world\",\n  };\n\n  const { data, resolvedData, key } = useDemoData({\n    path,\n    isEdit,\n    metadata,\n  });\n\n  const [isClient, setIsClient] = useState(false);\n\n  useEffect(() => {\n    setIsClient(true);\n  }, []);\n\n  if (!isClient) return null;\n\n  const params = new URL(window.location.href).searchParams;\n\n  if (isEdit) {\n    return (\n      <div>\n        <Puck\n          config={config}\n          data={data}\n          onPublish={async (data) => {\n            localStorage.setItem(key, JSON.stringify(data));\n          }}\n          plugins={[headingAnalyzer]}\n          headerPath={path}\n          iframe={{\n            enabled: params.get(\"disableIframe\") === \"true\" ? false : true,\n          }}\n          fieldTransforms={{\n            userField: ({ value }) => value, // Included to check types\n          }}\n          _experimentalFullScreenCanvas={false}\n          overrides={{\n            fieldTypes: {\n              // Example of user field provided via overrides\n              userField: ({ readOnly, field, name, value, onChange }) => (\n                <FieldLabel\n                  label={field.label || name}\n                  readOnly={readOnly}\n                  icon={<Type size={16} />}\n                >\n                  <AutoField\n                    field={{ type: \"text\" }}\n                    onChange={onChange}\n                    value={value}\n                  />\n                </FieldLabel>\n              ),\n            },\n            headerActions: ({ children }) => (\n              <>\n                <div>\n                  <Button href={path} newTab variant=\"secondary\">\n                    View page\n                  </Button>\n                </div>\n\n                {children}\n              </>\n            ),\n          }}\n          metadata={metadata}\n        />\n      </div>\n    );\n  }\n\n  if (data.content) {\n    return <Render config={config} data={resolvedData} metadata={metadata} />;\n  }\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        height: \"100vh\",\n        textAlign: \"center\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      }}\n    >\n      <div>\n        <h1>404</h1>\n        <p>Page does not exist in session storage</p>\n      </div>\n    </div>\n  );\n}\n\nexport default Client;\n"
  },
  {
    "path": "apps/demo/app/[...puckPath]/page.tsx",
    "content": "import resolvePuckPath from \"../../lib/resolve-puck-path\";\nimport { Metadata } from \"next\";\nimport Client from \"./client\";\nexport async function generateMetadata({\n  params,\n}: {\n  params: Promise<{ framework: string; uuid: string; puckPath: string[] }>;\n}): Promise<Metadata> {\n  const { puckPath } = await params;\n  const { isEdit, path } = resolvePuckPath(puckPath);\n\n  if (isEdit) {\n    return {\n      title: \"Editing: \" + path,\n    };\n  }\n\n  return {\n    title: \"\",\n  };\n}\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise<{ framework: string; uuid: string; puckPath: string[] }>;\n}) {\n  const { puckPath } = await params;\n  const { isEdit, path } = resolvePuckPath(puckPath);\n\n  return <Client isEdit={isEdit} path={path} />;\n}\n\nexport const dynamic = \"force-dynamic\";\n"
  },
  {
    "path": "apps/demo/app/custom-ui/[...puckPath]/client.tsx",
    "content": "// Disable rules of hooks as they are regularly used inside render functions\n/* eslint-disable react-hooks/rules-of-hooks */\n\n\"use client\";\n\nimport {\n  ActionBar,\n  AutoField,\n  Button,\n  Data,\n  FieldLabel,\n  Puck,\n  Render,\n  useGetPuck,\n} from \"@/core\";\nimport { HeadingAnalyzer } from \"@/plugin-heading-analyzer/src/HeadingAnalyzer\";\nimport config from \"../../../config\";\nimport { UserConfig } from \"../../../config/types\";\nimport { useDemoData } from \"../../../lib/use-demo-data\";\nimport { IconButton, createUsePuck } from \"@/core\";\nimport { ReactNode, useEffect, useRef, useState } from \"react\";\nimport { Drawer } from \"@/core/components/Drawer\";\nimport {\n  ChevronUp,\n  ChevronDown,\n  Globe,\n  Lock,\n  Unlock,\n  Type,\n} from \"lucide-react\";\n\nconst usePuck = createUsePuck<UserConfig>();\n\nconst CustomHeader = ({ onPublish }: { onPublish: (data: Data) => void }) => {\n  const getPuck = useGetPuck();\n  const dispatch = usePuck((s) => s.dispatch);\n  const previewMode = usePuck((s) => s.appState.ui.previewMode);\n\n  const toggleMode = () => {\n    dispatch({\n      type: \"setUi\",\n      ui: {\n        previewMode: previewMode === \"edit\" ? \"interactive\" : \"edit\",\n      },\n    });\n  };\n\n  return (\n    <header\n      style={{\n        display: \"flex\",\n        flexWrap: \"wrap\",\n        gap: 16,\n        padding: \"16px 24px\",\n        background: \"white\",\n        color: \"black\",\n        alignItems: \"center\",\n        borderBottom: \"1px solid #ddd\",\n      }}\n      onClick={() => dispatch({ type: \"setUi\", ui: { itemSelector: null } })}\n    >\n      <span style={{ fontWeight: 600 }}>Custom UI example </span>\n      <div style={{ marginLeft: \"auto\", display: \"flex\", gap: 8 }}>\n        <div style={{ gap: 8, display: \"flex\" }}>\n          <Button onClick={toggleMode} variant=\"secondary\">\n            Switch to {previewMode === \"edit\" ? \"interactive\" : \"edit\"}\n          </Button>\n          <Button\n            onClick={() => onPublish(getPuck().appState.data)}\n            icon={<Globe size=\"14\" />}\n          >\n            Publish\n          </Button>\n        </div>\n      </div>\n    </header>\n  );\n};\n\nconst Tabs = ({\n  tabs,\n  onTabCollapse,\n  scrollTop,\n}: {\n  tabs: { label: string; body: ReactNode }[];\n  onTabCollapse: () => void;\n  scrollTop: number;\n}) => {\n  const [currentTab, setCurrentTab] = useState(-1);\n  const itemSelector = usePuck((s) => s.appState.ui.itemSelector);\n  const isDragging = usePuck((s) => s.appState.ui.isDragging);\n\n  const currentTabRef = useRef(currentTab);\n\n  useEffect(() => {\n    if (currentTabRef.current !== -1 && itemSelector) {\n      setCurrentTab(1);\n    }\n  }, [itemSelector]);\n\n  useEffect(() => {\n    currentTabRef.current = currentTab;\n  }, [currentTab]);\n\n  useEffect(() => {\n    if (isDragging && currentTab === 1) {\n      setCurrentTab(-1);\n    }\n  }, [currentTab, isDragging]);\n\n  useEffect(() => {\n    if (scrollTop === 0) {\n      setCurrentTab(-1);\n      onTabCollapse();\n    }\n  }, [scrollTop]);\n\n  return (\n    <div\n      onClick={(e) => e.stopPropagation()}\n      style={{\n        background: \"#ffffff\",\n        pointerEvents: \"all\",\n        borderTop: \"1px solid #ddd\",\n        boxShadow: \"rgba(140, 152, 164, 0.25) 0px 0px 6px 0px\",\n      }}\n    >\n      <div\n        style={{\n          display: \"flex\",\n          paddingLeft: 16,\n          paddingRight: 16,\n          borderBottom: \"1px solid #ddd\",\n          overflowX: \"auto\",\n        }}\n      >\n        {tabs.map((tab, idx) => {\n          const isCurrentTab = currentTab === idx;\n          return (\n            <button\n              key={idx}\n              type=\"button\"\n              onClick={() => {\n                if (currentTab === idx) {\n                  setCurrentTab(-1);\n                } else {\n                  setCurrentTab(idx);\n                  if (scrollTop < 20) {\n                    setTimeout(() => {\n                      document\n                        .querySelector(\"#action-bar\")\n                        ?.scroll({ top: 128, behavior: \"smooth\" });\n                    }, 25);\n                  }\n                }\n              }}\n              style={{\n                fontFamily: \"inherit\",\n                fontSize: 16,\n                padding: \"16px 16px\",\n                paddingTop: 19,\n                color: isCurrentTab ? \"var(--puck-color-azure-04)\" : \"black\",\n                border: \"none\",\n                borderBottom: isCurrentTab\n                  ? \"3px solid var(--puck-color-azure-04)\"\n                  : \"3px solid transparent\",\n                background: \"white\",\n                cursor: \"pointer\",\n              }}\n            >\n              {tab.label}\n            </button>\n          );\n        })}\n        <div\n          style={{\n            marginLeft: \"auto\",\n            display: \"flex\",\n            alignItems: \"center\",\n            gap: 8,\n          }}\n        >\n          <div>\n            <IconButton\n              onClick={() => {\n                setCurrentTab(currentTab === -1 ? 0 : -1);\n\n                if (currentTab !== -1) {\n                  onTabCollapse();\n                } else {\n                  setTimeout(() => {\n                    document\n                      .querySelector(\"#action-bar\")\n                      ?.scroll({ top: 128, behavior: \"smooth\" });\n                  }, 25);\n                }\n              }}\n              title={currentTab !== -1 ? \"Collapse Tabs\" : \"Expand Tabs\"}\n            >\n              {currentTab === -1 ? <ChevronUp /> : <ChevronDown />}\n            </IconButton>\n          </div>\n        </div>\n      </div>\n      <div style={{ overflowX: \"auto\" }}>\n        {tabs.map((tab, idx) => {\n          const isCurrentTab = currentTab === idx;\n          return (\n            <div\n              key={idx}\n              style={{\n                display: isCurrentTab ? \"block\" : \"none\",\n              }}\n            >\n              {tab.body}\n            </div>\n          );\n        })}\n      </div>\n    </div>\n  );\n};\n\nconst CustomPuck = ({ dataKey }: { dataKey: string }) => {\n  const [hoveringTabs, setHoveringTabs] = useState(false);\n\n  const [actionBarScroll, setActionBarScroll] = useState(0);\n\n  return (\n    <div\n      style={{\n        position: \"relative\",\n      }}\n    >\n      <div style={{ position: \"sticky\", top: 0, zIndex: 2 }}>\n        <CustomHeader\n          onPublish={async (data: Data) => {\n            localStorage.setItem(dataKey, JSON.stringify(data));\n          }}\n        />\n      </div>\n      <div\n        style={{\n          position: \"relative\",\n          overflowY: hoveringTabs ? \"hidden\" : \"auto\",\n          zIndex: 0,\n        }}\n      >\n        <Puck.Preview />\n      </div>\n      <div\n        id=\"action-bar\"\n        style={{\n          position: \"fixed\",\n          bottom: 0,\n          overflowY: \"auto\",\n          overflowX: \"hidden\",\n          maxHeight: \"100vh\",\n          width: \"100%\",\n          boxSizing: \"border-box\",\n          paddingTop: \"calc(100vh - 58px)\",\n          pointerEvents: hoveringTabs ? undefined : \"none\",\n          zIndex: 1,\n          overscrollBehavior: \"none\",\n        }}\n        onTouchStart={() => setHoveringTabs(false)}\n        onScrollCapture={(e) => {\n          setActionBarScroll(e.currentTarget.scrollTop);\n        }}\n      >\n        <div\n          style={{\n            background: \"white\",\n            position: \"relative\",\n            pointerEvents: \"none\",\n            zIndex: 0,\n          }}\n          onMouseOver={(e) => {\n            e.stopPropagation();\n            setHoveringTabs(true);\n          }}\n          onTouchStart={(e) => {\n            e.stopPropagation();\n            setHoveringTabs(true);\n          }}\n          onMouseOut={() => {\n            setHoveringTabs(false);\n          }}\n        >\n          {/* Force react to render when hoveringTabs changes, otherwise scroll gets trapped */}\n          {hoveringTabs && <span />}\n          <Tabs\n            onTabCollapse={() => {\n              setTimeout(() => setHoveringTabs(false), 50);\n            }}\n            scrollTop={actionBarScroll}\n            tabs={[\n              { label: \"Components\", body: <Puck.Components /> },\n              { label: \"Fields\", body: <Puck.Fields /> },\n              { label: \"Outline\", body: <Puck.Outline /> },\n              {\n                label: \"Headings\",\n                body: (\n                  <div style={{ padding: 24 }}>\n                    <HeadingAnalyzer />\n                  </div>\n                ),\n              },\n            ]}\n          />\n        </div>\n      </div>\n    </div>\n  );\n};\n\nconst CustomDrawer = () => {\n  const getPermissions = usePuck((s) => s.getPermissions);\n\n  return (\n    <Drawer>\n      <div\n        style={{\n          display: \"grid\",\n          gridTemplateColumns: \"repeat(auto-fill, minmax(256px, 1fr))\",\n          pointerEvents: \"all\",\n          padding: \"16px\",\n          background: \"var(--puck-color-grey-12)\",\n          gap: 8,\n        }}\n      >\n        {Object.keys(config.components).map((componentKey, componentIndex) => {\n          const canInsert = getPermissions({\n            type: componentKey as keyof UserConfig[\"components\"],\n          }).insert;\n\n          return (\n            <Drawer.Item\n              key={componentKey}\n              name={componentKey}\n              isDragDisabled={!canInsert}\n            />\n          );\n        })}\n      </div>\n    </Drawer>\n  );\n};\n\nexport function Client({ path, isEdit }: { path: string; isEdit: boolean }) {\n  const { data, resolvedData, key } = useDemoData({\n    path,\n    isEdit,\n  });\n\n  const [lockedComponents, setLockedComponents] = useState<\n    Record<string, boolean>\n  >({});\n\n  const configOverride: UserConfig = {\n    ...config,\n    components: {\n      ...Object.keys(config.components).reduce((acc, componentKey) => {\n        return {\n          ...acc,\n          [componentKey]: {\n            ...acc[componentKey as keyof UserConfig[\"components\"]],\n            resolvePermissions: (data: any, { permissions }: any) => {\n              if (lockedComponents[data.props.id]) {\n                return {\n                  drag: false,\n                  edit: false,\n                  duplicate: false,\n                  delete: false,\n                };\n              }\n\n              return permissions;\n            },\n          },\n        };\n      }, config.components),\n    },\n  };\n\n  const [isClient, setIsClient] = useState(false);\n\n  useEffect(() => {\n    setIsClient(true);\n  }, []);\n\n  if (!isClient) return null;\n\n  if (isEdit) {\n    return (\n      <Puck<UserConfig>\n        config={configOverride}\n        data={data}\n        iframe={{ enabled: false }}\n        headerPath={path}\n        permissions={{\n          lockable: true,\n        }}\n        overrides={{\n          fieldTypes: {\n            userField: ({ readOnly, field, name, value, onChange }) => (\n              <FieldLabel\n                label={field.label || name}\n                readOnly={readOnly}\n                icon={<Type size={16} />}\n              >\n                <AutoField\n                  readOnly={readOnly}\n                  field={{ type: \"text\" }}\n                  onChange={onChange}\n                  value={value}\n                />\n              </FieldLabel>\n            ),\n          },\n          outline: ({ children }) => (\n            <div style={{ padding: 16 }}>{children}</div>\n          ),\n          componentOverlay: ({ hover, isSelected }) => {\n            return (\n              <div\n                style={{\n                  width: \"100%\",\n                  height: \"100%\",\n                  background: hover ? \"red\" : \"transparent\",\n                  outline: isSelected ? \"2px solid blue\" : \"\",\n                  opacity: 0.4,\n                }}\n              />\n            );\n          },\n          actionBar: ({ children, label, parentAction }) => {\n            const selectedItem = usePuck((s) => s.selectedItem);\n            const getPermissions = usePuck((s) => s.getPermissions);\n            const refreshPermissions = usePuck((s) => s.refreshPermissions);\n\n            const globalPermissions = getPermissions();\n\n            // eslint-disable-next-line react-hooks/rules-of-hooks\n            useEffect(() => {\n              if (selectedItem) {\n                // We have to force refresh the permission resolver to refresh, since it relies on lockedComponents state\n                // Without this, the resolver won't trigger as no props will have changed\n                refreshPermissions({ item: selectedItem });\n              }\n              // eslint-disable-next-line react-hooks/exhaustive-deps\n            }, [lockedComponents, selectedItem?.props.id, refreshPermissions]);\n\n            if (!selectedItem)\n              return (\n                <ActionBar>\n                  <ActionBar.Group>\n                    {parentAction}\n                    {label && <ActionBar.Label label={label} />}\n                  </ActionBar.Group>\n                  <ActionBar.Group>{children}</ActionBar.Group>\n                </ActionBar>\n              );\n\n            const isLocked = !!lockedComponents[selectedItem.props.id];\n\n            return (\n              <ActionBar>\n                <ActionBar.Group>\n                  {parentAction}\n                  {label && <ActionBar.Label label={label} />}\n                </ActionBar.Group>\n                <ActionBar.Group>\n                  {children}\n                  {globalPermissions.lockable && (\n                    <ActionBar.Action\n                      onClick={() => {\n                        setLockedComponents({\n                          ...lockedComponents,\n                          [selectedItem.props.id as string]: !isLocked,\n                        });\n\n                        refreshPermissions({ item: selectedItem });\n                      }}\n                      label={isLocked ? \"Unlock component\" : \"Lock component\"}\n                    >\n                      {isLocked ? <Unlock size={16} /> : <Lock size={16} />}\n                    </ActionBar.Action>\n                  )}\n                </ActionBar.Group>\n              </ActionBar>\n            );\n          },\n          drawer: () => <CustomDrawer />,\n          puck: () => <CustomPuck dataKey={key} />,\n        }}\n      />\n    );\n  }\n\n  if (data) {\n    return <Render<UserConfig> config={config} data={resolvedData} />;\n  }\n\n  return (\n    <div\n      style={{\n        display: \"flex\",\n        height: \"100vh\",\n        textAlign: \"center\",\n        justifyContent: \"center\",\n        alignItems: \"center\",\n      }}\n    >\n      <div>\n        <h1>404</h1>\n        <p>Page does not exist in session storage</p>\n      </div>\n    </div>\n  );\n}\n\nexport default Client;\n"
  },
  {
    "path": "apps/demo/app/custom-ui/[...puckPath]/page.tsx",
    "content": "import resolvePuckPath from \"../../../lib/resolve-puck-path\";\nimport { Metadata } from \"next\";\nimport Client from \"./client\";\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Promise<{ framework: string; uuid: string; puckPath: string[] }>;\n}): Promise<Metadata> {\n  const { puckPath } = await params;\n  const { isEdit, path } = resolvePuckPath(puckPath);\n\n  if (isEdit) {\n    return {\n      title: \"Editing: \" + path,\n    };\n  }\n\n  return {\n    title: \"\",\n  };\n}\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise<{ framework: string; uuid: string; puckPath: string[] }>;\n}) {\n  const { puckPath } = await params;\n  const { isEdit, path } = resolvePuckPath(puckPath);\n\n  return <Client isEdit={isEdit} path={path} />;\n}\n"
  },
  {
    "path": "apps/demo/app/layout.tsx",
    "content": "import \"@/core/styles.css\";\nimport \"./styles.css\";\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"en\">\n      <head>\n        {process.env.NEXT_PUBLIC_PLAUSIBLE_DATA_DOMAIN && (\n          <script\n            defer\n            data-domain={process.env.NEXT_PUBLIC_PLAUSIBLE_DATA_DOMAIN}\n            src=\"https://plausible.io/js/plausible.js\"\n          ></script>\n        )}\n      </head>\n      <body>\n        <div>{children}</div>\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "apps/demo/app/page.tsx",
    "content": "export { default } from \"./[...puckPath]/page\";\nexport * from \"./[...puckPath]/page\";\n"
  },
  {
    "path": "apps/demo/app/rsc/page.tsx",
    "content": "import { Metadata } from \"next\";\nimport config from \"../../config/server\";\nimport { initialData } from \"../../config/initial-data\";\nimport { Components, RootProps } from \"../../config/types\";\n\nimport { Config } from \"@/core\";\nimport { Render, resolveAllData } from \"@/core/bundle/rsc\";\n\n// NB This is only necessary for this demo app, as the `@/core/bundle/rsc` path does not resolve to dist but the type for Config does\n// This will be resolved once the RSC package is merged with the regular package after DropZone support is dropped\nconst conf = config as unknown as Config;\n\nexport async function generateMetadata(): Promise<Metadata> {\n  return {\n    title: initialData[\"/\"].root.title,\n  };\n}\n\nexport default async function Page() {\n  const data = initialData[\"/\"];\n  const metadata = {\n    example: \"Hello, world\",\n  };\n\n  const resolvedData = await resolveAllData<Components, RootProps>(\n    data,\n    conf,\n    metadata\n  );\n\n  return <Render config={conf} data={resolvedData} metadata={metadata} />;\n}\n"
  },
  {
    "path": "apps/demo/app/styles.css",
    "content": "@import url(\"https://rsms.me/inter/inter.css\");\n\nbody {\n  font-family: Inter, -apple-system, BlinkMacSystemFont, Segoe UI,\n    Helvetica Neue, sans-serif, Apple Color Emoji, Segoe UI Emoji,\n    Segoe UI Symbol;\n  margin: 0;\n}\n\n@supports (font-variation-settings: normal) {\n  body {\n    font-family: InterVariable, -apple-system, BlinkMacSystemFont, Segoe UI,\n      Helvetica Neue, sans-serif, Apple Color Emoji, Segoe UI Emoji,\n      Segoe UI Symbol;\n  }\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Blank/index.tsx",
    "content": "import React from \"react\";\nimport { ComponentConfig } from \"@/core\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\n\nconst getClassName = getClassNameFactory(\"Blank\", styles);\n\nexport type BlankProps = {};\n\nexport const Blank: ComponentConfig<BlankProps> = {\n  fields: {},\n  defaultProps: {},\n  render: () => {\n    return <div className={getClassName()}></div>;\n  },\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Blank/styles.module.css",
    "content": ".Blank {\n  background: hotpink;\n  padding: 16px;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Button/index.tsx",
    "content": "import React from \"react\";\nimport { ComponentConfig } from \"@/core/types\";\nimport { Button as _Button } from \"@/core/components/Button\";\n\nexport type ButtonProps = {\n  label: string;\n  href: string;\n  variant: \"primary\" | \"secondary\";\n};\n\nexport const Button: ComponentConfig<ButtonProps> = {\n  label: \"Button\",\n  fields: {\n    label: {\n      type: \"text\",\n      placeholder: \"Lorem ipsum...\",\n      contentEditable: true,\n    },\n    href: { type: \"text\" },\n    variant: {\n      type: \"radio\",\n      options: [\n        { label: \"primary\", value: \"primary\" },\n        { label: \"secondary\", value: \"secondary\" },\n      ],\n    },\n  },\n  defaultProps: {\n    label: \"Button\",\n    href: \"#\",\n    variant: \"primary\",\n  },\n  render: ({ href, variant, label, puck }) => {\n    return (\n      <div>\n        <_Button\n          href={puck.isEditing ? \"#\" : href}\n          variant={variant}\n          size=\"large\"\n          tabIndex={puck.isEditing ? -1 : undefined}\n        >\n          {label}\n        </_Button>\n      </div>\n    );\n  },\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Card/index.tsx",
    "content": "/* eslint-disable @next/next/no-img-element */\nimport React, { ReactElement } from \"react\";\nimport { ComponentConfig } from \"@/core/types\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport dynamic from \"next/dynamic\";\nimport dynamicIconImports from \"lucide-react/dynamicIconImports\";\nimport { withLayout, WithLayout } from \"../../components/Layout\";\n\nconst getClassName = getClassNameFactory(\"Card\", styles);\n\nconst icons = Object.keys(dynamicIconImports).reduce<\n  Record<string, ReactElement>\n>((acc, iconName) => {\n  const El = dynamic((dynamicIconImports as any)[iconName]);\n\n  return {\n    ...acc,\n    [iconName]: <El />,\n  };\n}, {});\n\nconst iconOptions = Object.keys(dynamicIconImports).map((iconName) => ({\n  label: iconName,\n  value: iconName,\n}));\n\nexport type CardProps = WithLayout<{\n  title: string;\n  description: string;\n  icon?: string;\n  mode: \"flat\" | \"card\";\n}>;\n\nconst CardInner: ComponentConfig<CardProps> = {\n  fields: {\n    title: {\n      type: \"text\",\n      contentEditable: true,\n    },\n    description: {\n      type: \"textarea\",\n      contentEditable: true,\n    },\n    icon: {\n      type: \"select\",\n      options: iconOptions,\n    },\n    mode: {\n      type: \"radio\",\n      options: [\n        { label: \"card\", value: \"card\" },\n        { label: \"flat\", value: \"flat\" },\n      ],\n    },\n  },\n  defaultProps: {\n    title: \"Title\",\n    description: \"Description\",\n    icon: \"Feather\",\n    mode: \"flat\",\n  },\n  render: ({ title, icon, description, mode }) => {\n    return (\n      <div className={getClassName({ [mode]: mode })}>\n        <div className={getClassName(\"inner\")}>\n          <div className={getClassName(\"icon\")}>{icon && icons[icon]}</div>\n\n          <div className={getClassName(\"title\")}>{title}</div>\n          <div className={getClassName(\"description\")}>{description}</div>\n        </div>\n      </div>\n    );\n  },\n};\n\nexport const Card = withLayout(CardInner);\n"
  },
  {
    "path": "apps/demo/config/blocks/Card/styles.module.css",
    "content": ".Card {\n  height: 100%;\n}\n\n.Card--card {\n  background: white;\n  box-shadow: rgba(140, 152, 164, 0.25) 0px 3px 6px 0px;\n  border-radius: 8px;\n  max-width: 100%;\n}\n\n.Card-inner {\n  align-items: center;\n  display: flex;\n  gap: 16px;\n  flex-direction: column;\n}\n\n.Card--card .Card-inner {\n  align-items: flex-start;\n  padding: 24px;\n}\n\n.Card-icon {\n  border-radius: 256px;\n  background: var(--puck-color-azure-09);\n  color: var(--puck-color-azure-06);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: 64px;\n  height: 64px;\n}\n\n.Card-title {\n  font-size: 22px;\n  text-align: center;\n}\n\n.Card--card .Card-title {\n  text-align: left;\n}\n\n.Card-description {\n  font-size: 16px;\n  line-height: 1.5;\n  color: var(--puck-color-grey-05);\n  text-align: center;\n  font-weight: 300;\n}\n\n.Card--card .Card-description {\n  text-align: left;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Flex/index.tsx",
    "content": "import React from \"react\";\nimport { ComponentConfig, Slot } from \"@/core/types\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport { Section } from \"../../components/Section\";\nimport { WithLayout, withLayout } from \"../../components/Layout\";\n\nconst getClassName = getClassNameFactory(\"Flex\", styles);\n\nexport type FlexProps = WithLayout<{\n  justifyContent: \"start\" | \"center\" | \"end\";\n  direction: \"row\" | \"column\";\n  gap: number;\n  wrap: \"wrap\" | \"nowrap\";\n  items: Slot;\n}>;\n\nconst FlexInternal: ComponentConfig<FlexProps> = {\n  fields: {\n    direction: {\n      label: \"Direction\",\n      type: \"radio\",\n      options: [\n        { label: \"Row\", value: \"row\" },\n        { label: \"Column\", value: \"column\" },\n      ],\n    },\n    justifyContent: {\n      label: \"Justify Content\",\n      type: \"radio\",\n      options: [\n        { label: \"Start\", value: \"start\" },\n        { label: \"Center\", value: \"center\" },\n        { label: \"End\", value: \"end\" },\n      ],\n    },\n    gap: {\n      label: \"Gap\",\n      type: \"number\",\n      min: 0,\n    },\n    wrap: {\n      label: \"Wrap\",\n      type: \"radio\",\n      options: [\n        { label: \"true\", value: \"wrap\" },\n        { label: \"false\", value: \"nowrap\" },\n      ],\n    },\n    items: {\n      type: \"slot\",\n    },\n  },\n  defaultProps: {\n    justifyContent: \"start\",\n    direction: \"row\",\n    gap: 24,\n    wrap: \"wrap\",\n    layout: {\n      grow: true,\n    },\n    items: [],\n  },\n  render: ({ justifyContent, direction, gap, wrap, items: Items }) => {\n    return (\n      <Section style={{ height: \"100%\" }}>\n        <Items\n          className={getClassName()}\n          style={{\n            justifyContent,\n            flexDirection: direction,\n            gap,\n            flexWrap: wrap,\n          }}\n          disallow={[\"Hero\", \"Stats\"]}\n        />\n      </Section>\n    );\n  },\n};\n\nexport const Flex = withLayout(FlexInternal);\n"
  },
  {
    "path": "apps/demo/config/blocks/Flex/styles.module.css",
    "content": ".Flex {\n  display: flex;\n  flex-wrap: wrap;\n  height: 100%;\n}\n\n.Flex-item {\n  flex: 1;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Grid/index.tsx",
    "content": "import React from \"react\";\nimport { ComponentConfig, Slot } from \"@/core/types\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport { Section } from \"../../components/Section\";\nimport { withLayout } from \"../../components/Layout\";\n\nconst getClassName = getClassNameFactory(\"Grid\", styles);\n\nexport type GridProps = {\n  numColumns: number;\n  gap: number;\n  items: Slot;\n};\n\nconst CustomSlot = (props: any) => {\n  return <span {...props} />;\n};\n\nexport const GridInternal: ComponentConfig<GridProps> = {\n  fields: {\n    numColumns: {\n      type: \"number\",\n      label: \"Number of columns\",\n      min: 1,\n      max: 12,\n    },\n    gap: {\n      label: \"Gap\",\n      type: \"number\",\n      min: 0,\n    },\n    items: {\n      type: \"slot\",\n    },\n  },\n  defaultProps: {\n    numColumns: 4,\n    gap: 24,\n    items: [],\n  },\n  render: ({ gap, numColumns, items: Items }) => {\n    return (\n      <Section>\n        <Items\n          as={CustomSlot}\n          disallow={[\"Hero\", \"Stats\"]}\n          className={getClassName()}\n          style={{\n            gap,\n            gridTemplateColumns: `repeat(${numColumns}, 1fr)`,\n          }}\n        />\n      </Section>\n    );\n  },\n};\n\nexport const Grid = withLayout(GridInternal);\n"
  },
  {
    "path": "apps/demo/config/blocks/Grid/styles.module.css",
    "content": ".Grid {\n  display: flex;\n  flex-direction: column;\n  width: auto;\n}\n\n@media (min-width: 768px) {\n  .Grid {\n    display: grid;\n  }\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Heading/index.tsx",
    "content": "import React from \"react\";\n\nimport { ComponentConfig } from \"@/core/types\";\nimport { Heading as _Heading } from \"@/core/components/Heading\";\nimport type { HeadingProps as _HeadingProps } from \"@/core/components/Heading\";\nimport { Section } from \"../../components/Section\";\nimport { WithLayout, withLayout } from \"../../components/Layout\";\n\nexport type HeadingProps = WithLayout<{\n  align: \"left\" | \"center\" | \"right\";\n  text?: string;\n  level?: _HeadingProps[\"rank\"];\n  size: _HeadingProps[\"size\"];\n}>;\n\nconst sizeOptions = [\n  { value: \"xxxl\", label: \"XXXL\" },\n  { value: \"xxl\", label: \"XXL\" },\n  { value: \"xl\", label: \"XL\" },\n  { value: \"l\", label: \"L\" },\n  { value: \"m\", label: \"M\" },\n  { value: \"s\", label: \"S\" },\n  { value: \"xs\", label: \"XS\" },\n];\n\nconst levelOptions = [\n  { label: \"\", value: \"\" },\n  { label: \"1\", value: \"1\" },\n  { label: \"2\", value: \"2\" },\n  { label: \"3\", value: \"3\" },\n  { label: \"4\", value: \"4\" },\n  { label: \"5\", value: \"5\" },\n  { label: \"6\", value: \"6\" },\n];\n\nconst HeadingInternal: ComponentConfig<HeadingProps> = {\n  fields: {\n    text: {\n      type: \"textarea\",\n      contentEditable: true,\n    },\n    size: {\n      type: \"select\",\n      options: sizeOptions,\n    },\n    level: {\n      type: \"select\",\n      options: levelOptions,\n    },\n    align: {\n      type: \"radio\",\n      options: [\n        { label: \"Left\", value: \"left\" },\n        { label: \"Center\", value: \"center\" },\n        { label: \"Right\", value: \"right\" },\n      ],\n    },\n  },\n  defaultProps: {\n    align: \"left\",\n    text: \"Heading\",\n    size: \"m\",\n    layout: {\n      padding: \"8px\",\n    },\n  },\n  render: ({ align, text, size, level }) => {\n    return (\n      <Section>\n        <_Heading size={size} rank={level as any}>\n          <span style={{ display: \"block\", textAlign: align, width: \"100%\" }}>\n            {text}\n          </span>\n        </_Heading>\n      </Section>\n    );\n  },\n};\n\nexport const Heading = withLayout(HeadingInternal);\n"
  },
  {
    "path": "apps/demo/config/blocks/Heading/styles.module.css",
    "content": ".Heading {\n  margin: 0;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Hero/Hero.tsx",
    "content": "/* eslint-disable @next/next/no-img-element */\nimport React, { ReactNode } from \"react\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport { Button } from \"@/core/components/Button\";\nimport { Section } from \"../../components/Section\";\nimport { PuckComponent, RichText, Slot } from \"@/core/types\";\n\nconst getClassName = getClassNameFactory(\"Hero\", styles);\n\nexport type HeroProps = {\n  quote?: { index: number; label: string };\n  title: string | ReactNode;\n  description: RichText;\n  align?: string;\n  padding: string;\n  image?: {\n    content?: Slot;\n    mode?: \"inline\" | \"background\" | \"custom\";\n    url?: string;\n  };\n  buttons: {\n    label: string;\n    href: string;\n    variant?: \"primary\" | \"secondary\";\n  }[];\n};\n\nexport const Hero: PuckComponent<HeroProps> = ({\n  align,\n  title,\n  description,\n  buttons,\n  padding,\n  image,\n  puck,\n}) => {\n  return (\n    <Section\n      className={getClassName({\n        left: align === \"left\",\n        center: align === \"center\",\n        hasImageBackground: image?.mode === \"background\",\n      })}\n      style={{ paddingTop: padding, paddingBottom: padding }}\n    >\n      {image?.mode === \"background\" && (\n        <>\n          <div\n            className={getClassName(\"image\")}\n            style={{\n              backgroundImage: `url(\"${image?.url}\")`,\n            }}\n          ></div>\n\n          <div className={getClassName(\"imageOverlay\")}></div>\n        </>\n      )}\n\n      <div className={getClassName(\"inner\")}>\n        <div className={getClassName(\"content\")}>\n          <h1>{title}</h1>\n          <div className={getClassName(\"subtitle\")}>{description}</div>\n          <div className={getClassName(\"actions\")}>\n            {buttons.map((button, i) => (\n              <Button\n                key={i}\n                href={button.href}\n                variant={button.variant}\n                size=\"large\"\n                tabIndex={puck.isEditing ? -1 : undefined}\n              >\n                {button.label}\n              </Button>\n            ))}\n          </div>\n        </div>\n\n        {align !== \"center\" && image?.mode === \"inline\" && image?.url && (\n          <div\n            style={{\n              backgroundImage: `url('${image?.url}')`,\n              backgroundSize: \"cover\",\n              backgroundRepeat: \"no-repeat\",\n              backgroundPosition: \"center\",\n              borderRadius: 24,\n              height: 356,\n              marginLeft: \"auto\",\n              width: \"100%\",\n            }}\n          />\n        )}\n\n        {align !== \"center\" && image?.mode === \"custom\" && image.content && (\n          <image.content\n            style={{\n              height: 356,\n              marginLeft: \"auto\",\n              width: \"100%\",\n            }}\n          />\n        )}\n      </div>\n    </Section>\n  );\n};\n\nexport default Hero;\n"
  },
  {
    "path": "apps/demo/config/blocks/Hero/client.tsx",
    "content": "/* eslint-disable @next/next/no-img-element */\nimport React from \"react\";\nimport { ComponentConfig } from \"@/core/types\";\nimport { quotes } from \"./quotes\";\nimport { AutoField, FieldLabel, RichTextMenu } from \"@/core\";\nimport { Link2, Quote } from \"lucide-react\";\nimport HeroComponent, { HeroProps } from \"./Hero\";\n\nexport const Hero: ComponentConfig<{\n  props: HeroProps;\n  fields: {\n    userField: {\n      type: \"userField\";\n      option: boolean;\n    };\n  };\n}> = {\n  fields: {\n    quote: {\n      type: \"external\",\n      placeholder: \"Select a quote\",\n      showSearch: false,\n      renderFooter: ({ items }) => {\n        return (\n          <div>\n            {items.length} result{items.length === 1 ? \"\" : \"s\"}\n          </div>\n        );\n      },\n      filterFields: {\n        author: {\n          type: \"select\",\n          options: [\n            { value: \"\", label: \"Select an author\" },\n            { value: \"Mark Twain\", label: \"Mark Twain\" },\n            { value: \"Henry Ford\", label: \"Henry Ford\" },\n            { value: \"Kurt Vonnegut\", label: \"Kurt Vonnegut\" },\n            { value: \"Andrew Carnegie\", label: \"Andrew Carnegie\" },\n            { value: \"C. S. Lewis\", label: \"C. S. Lewis\" },\n            { value: \"Confucius\", label: \"Confucius\" },\n            { value: \"Eleanor Roosevelt\", label: \"Eleanor Roosevelt\" },\n            { value: \"Samuel Ullman\", label: \"Samuel Ullman\" },\n          ],\n        },\n      },\n      fetchList: async ({ query, filters }) => {\n        // Simulate delay\n        await new Promise((res) => setTimeout(res, 500));\n\n        return quotes\n          .map((quote, idx) => ({\n            index: idx,\n            title: quote.author,\n            description: quote.content,\n          }))\n          .filter((item) => {\n            if (filters?.author && item.title !== filters?.author) {\n              return false;\n            }\n\n            if (!query) return true;\n\n            const queryLowercase = query.toLowerCase();\n\n            if (item.title.toLowerCase().indexOf(queryLowercase) > -1) {\n              return true;\n            }\n\n            if (item.description.toLowerCase().indexOf(queryLowercase) > -1) {\n              return true;\n            }\n          });\n      },\n      mapRow: (item) => ({\n        title: item.title,\n        description: <span>{item.description}</span>,\n      }),\n      mapProp: (result) => {\n        return { index: result.index, label: result.description };\n      },\n      getItemSummary: (item) => item.label,\n    },\n    title: { type: \"text\", contentEditable: true },\n    description: {\n      type: \"richtext\",\n      contentEditable: true,\n      options: {\n        heading: false,\n        textAlign: false,\n      },\n      renderMenu: ({ editor, editorState }) => {\n        return (\n          <RichTextMenu>\n            <RichTextMenu.Group>\n              <RichTextMenu.Bold />\n              <RichTextMenu.Italic />\n              <RichTextMenu.Underline />\n            </RichTextMenu.Group>\n            <RichTextMenu.Group>\n              <RichTextMenu.ListSelect />\n              <RichTextMenu.Control\n                icon={<Quote />}\n                title=\"Quote\"\n                active={editorState?.isBlockquote}\n                onClick={() => {\n                  editor?.chain().focus().toggleBlockquote().run();\n                }}\n              />\n            </RichTextMenu.Group>\n          </RichTextMenu>\n        );\n      },\n    },\n    buttons: {\n      type: \"array\",\n      min: 1,\n      max: 4,\n      getItemSummary: (item) => item.label || \"Button\",\n      arrayFields: {\n        label: { type: \"text\", contentEditable: true },\n        href: { type: \"text\" },\n        variant: {\n          type: \"select\",\n          options: [\n            { label: \"primary\", value: \"primary\" },\n            { label: \"secondary\", value: \"secondary\" },\n          ],\n        },\n      },\n      defaultItemProps: {\n        label: \"Button\",\n        href: \"#\",\n      },\n    },\n    align: {\n      type: \"radio\",\n      options: [\n        { label: \"left\", value: \"left\" },\n        { label: \"center\", value: \"center\" },\n      ],\n    },\n    image: {\n      type: \"object\",\n      objectFields: {\n        content: { type: \"slot\" },\n        url: {\n          type: \"custom\",\n          render: ({ value, field, name, onChange, readOnly }) => (\n            <FieldLabel\n              label={field.label || name}\n              readOnly={readOnly}\n              icon={<Link2 size=\"16\" />}\n            >\n              <AutoField\n                field={{ type: \"text\" }}\n                value={value}\n                onChange={onChange}\n                readOnly={readOnly}\n              />\n            </FieldLabel>\n          ),\n        },\n        mode: {\n          type: \"radio\",\n          options: [\n            { label: \"inline\", value: \"inline\" },\n            { label: \"bg\", value: \"background\" },\n            { label: \"custom\", value: \"custom\" },\n          ],\n        },\n      },\n    },\n    padding: { type: \"userField\", option: true },\n  },\n  defaultProps: {\n    title: \"Hero\",\n    align: \"left\",\n    description: \"<p>Description</p>\",\n    buttons: [{ label: \"Learn more\", href: \"#\" }],\n    padding: \"64px\",\n  },\n  /**\n   * The resolveData method allows us to modify component data after being\n   * set by the user.\n   *\n   * It is called after the page data is changed, but before a component\n   * is rendered. This allows us to make dynamic changes to the props\n   * without storing the data in Puck.\n   *\n   * For example, requesting a third-party API for the latest content.\n   */\n  resolveData: async ({ props }, { changed }) => {\n    if (!props.quote)\n      return { props, readOnly: { title: false, description: false } };\n\n    if (!changed.quote) {\n      return { props };\n    }\n\n    // Simulate a delay\n    await new Promise((resolve) => setTimeout(resolve, 500));\n\n    return {\n      props: {\n        title: quotes[props.quote.index].author,\n        description: `<p>${quotes[props.quote.index].content}</p>`,\n      },\n      readOnly: { title: true, description: true },\n    };\n  },\n  resolveFields: async (data, { fields }) => {\n    if (data.props.align === \"center\") {\n      return {\n        ...fields,\n        image: undefined,\n      };\n    }\n\n    return fields;\n  },\n  resolvePermissions: async (data, params) => {\n    if (!params.changed.quote) return params.lastPermissions;\n\n    // Simulate delay\n    await new Promise((resolve) => setTimeout(resolve, 500));\n\n    return {\n      ...params.permissions,\n      // Disable delete if quote 7 is selected\n      delete: data.props.quote?.index !== 7,\n    };\n  },\n  render: HeroComponent,\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Hero/index.tsx",
    "content": "export * from \"./client\";\nexport { type HeroProps } from \"./Hero\";\n"
  },
  {
    "path": "apps/demo/config/blocks/Hero/quotes.ts",
    "content": "export const quotes = [\n  {\n    content:\n      \"Age is an issue of mind over matter. If you don't mind, it doesn't matter.\",\n    author: \"Mark Twain\",\n  },\n  {\n    content:\n      \"Anyone who stops learning is old, whether at twenty or eighty. Anyone who keeps learning stays young. The greatest thing in life is to keep your mind young.\",\n    author: \"Henry Ford\",\n  },\n  {\n    content: \"Wrinkles should merely indicate where smiles have been.\",\n    author: \"Mark Twain\",\n  },\n  {\n    content:\n      \"True terror is to wake up one morning and discover that your high school class is running the country.\",\n    author: \"Kurt Vonnegut\",\n  },\n  {\n    content:\n      \"As I grow older, I pay less attention to what men say. I just watch what they do.\",\n    author: \"Andrew Carnegie\",\n  },\n  {\n    content:\n      \"How incessant and great are the ills with which a prolonged old age is replete.\",\n    author: \"C. S. Lewis\",\n  },\n  {\n    content:\n      \"Old age, believe me, is a good and pleasant thing. It is true you are gently shouldered off the stage, but then you are given such a comfortable front stall as spectator.\",\n    author: \"Confucius\",\n  },\n  {\n    content:\n      \"Old age has deformities enough of its own. It should never add to them the deformity of vice.\",\n    author: \"Eleanor Roosevelt\",\n  },\n  {\n    content:\n      \"Nobody grows old merely by living a number of years. We grow old by deserting our ideals. Years may wrinkle the skin, but to give up enthusiasm wrinkles the soul.\",\n    author: \"Samuel Ullman\",\n  },\n];\n"
  },
  {
    "path": "apps/demo/config/blocks/Hero/server.tsx",
    "content": "/* eslint-disable @next/next/no-img-element */\nimport { ComponentConfig } from \"@/core/types\";\nimport HeroComponent, { HeroProps } from \"./Hero\";\n\nexport const Hero: ComponentConfig<HeroProps> = {\n  render: HeroComponent,\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Hero/styles.module.css",
    "content": ".Hero {\n  background-image: linear-gradient(\n    rgba(255, 255, 255, 0),\n    rgb(247, 250, 255) 100%\n  );\n  display: flex;\n  align-items: center;\n  position: relative;\n}\n\n.Hero-inner {\n  display: flex;\n  align-items: center;\n  position: relative;\n  gap: 48px;\n  flex-wrap: wrap;\n  z-index: 1;\n}\n\n@media (min-width: 1024px) {\n  .Hero-inner {\n    flex-wrap: nowrap;\n  }\n}\n\n.Hero h1 {\n  line-height: 1.1;\n  font-size: 48px;\n  margin: 0;\n}\n\n@media (min-width: 768px) {\n  .Hero h1 {\n    font-size: 64px;\n  }\n}\n\n.Hero-subtitle {\n  color: var(--puck-color-grey-05);\n  font-size: var(--puck-font-size-m);\n  line-height: 1.5;\n  margin: 0;\n  margin-bottom: 8px;\n  font-weight: 300;\n}\n\n.Hero-subtitle strong {\n  font-weight: 500;\n}\n\n.Hero--hasImageBackground .Hero-subtitle {\n  color: var(--puck-color-grey-03);\n}\n\n.Hero-image {\n  background-repeat: no-repeat;\n  background-size: cover;\n  background-position: center;\n  position: absolute;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  left: 0;\n}\n\n.Hero-imageOverlay {\n  background-image: linear-gradient(\n    -90deg,\n    rgb(247, 250, 255, 0.7) 0%,\n    rgb(247, 250, 255, 0.7) 80%\n  );\n  position: absolute;\n  right: 0;\n  top: 0;\n  bottom: 0;\n  left: 0;\n}\n\n@media (min-width: 768px) {\n  .Hero--left .Hero-imageOverlay {\n    background-image: linear-gradient(\n      -90deg,\n      rgba(255, 255, 255, 0) 0%,\n      rgb(247, 250, 255) 70%\n    );\n  }\n}\n\n.Hero-bg img {\n  height: 100%;\n}\n\n.Hero-content {\n  display: flex;\n  flex-direction: column;\n  gap: 16px;\n  width: 100%;\n}\n\n@media (min-width: 768px) {\n  .Hero--hasImageBackground .Hero-content {\n    max-width: 50%;\n  }\n}\n\n.Hero--center .Hero-inner {\n  justify-content: center;\n  text-align: center;\n}\n\n.Hero--center .Hero-content {\n  align-items: center;\n  justify-content: center;\n  max-width: 100%;\n}\n\n.Hero-actions {\n  display: flex;\n  gap: 16px;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Logos/index.tsx",
    "content": "/* eslint-disable @next/next/no-img-element */\nimport React from \"react\";\nimport { ComponentConfig } from \"@/core\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport { Section } from \"../../components/Section\";\n\nconst getClassName = getClassNameFactory(\"Logos\", styles);\n\nexport type LogosProps = {\n  logos: {\n    alt: string;\n    imageUrl: string;\n  }[];\n};\n\nexport const Logos: ComponentConfig<LogosProps> = {\n  fields: {\n    logos: {\n      type: \"array\",\n      getItemSummary: (item, i) => item.alt || `Feature #${i}`,\n      defaultItemProps: {\n        alt: \"\",\n        imageUrl: \"\",\n      },\n      arrayFields: {\n        alt: { type: \"text\" },\n        imageUrl: { type: \"text\" },\n      },\n    },\n  },\n  defaultProps: {\n    logos: [\n      {\n        alt: \"google\",\n        imageUrl:\n          \"https://logolook.net/wp-content/uploads/2021/06/Google-Logo.png\",\n      },\n      {\n        alt: \"google\",\n        imageUrl:\n          \"https://logolook.net/wp-content/uploads/2021/06/Google-Logo.png\",\n      },\n      {\n        alt: \"google\",\n        imageUrl:\n          \"https://logolook.net/wp-content/uploads/2021/06/Google-Logo.png\",\n      },\n      {\n        alt: \"google\",\n        imageUrl:\n          \"https://logolook.net/wp-content/uploads/2021/06/Google-Logo.png\",\n      },\n      {\n        alt: \"google\",\n        imageUrl:\n          \"https://logolook.net/wp-content/uploads/2021/06/Google-Logo.png\",\n      },\n    ],\n  },\n  render: ({ logos }) => {\n    return (\n      <Section className={getClassName()}>\n        <div className={getClassName(\"items\")}>\n          {logos.map((item, i) => (\n            <div key={i} className={getClassName(\"item\")}>\n              <img\n                className={getClassName(\"image\")}\n                alt={item.alt}\n                src={item.imageUrl}\n                height={64}\n              ></img>\n            </div>\n          ))}\n        </div>\n      </Section>\n    );\n  },\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Logos/styles.module.css",
    "content": ".Logos {\n  background-color: var(--puck-color-grey-02);\n}\n\n.Logos-items {\n  display: flex;\n  justify-content: space-between;\n  padding-bottom: 64px;\n  padding-top: 64px;\n  gap: 24px;\n  filter: grayscale(1) brightness(100);\n  opacity: 0.8;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/RichText/index.tsx",
    "content": "import React from \"react\";\nimport { ComponentConfig } from \"@/core/types\";\nimport { WithLayout, withLayout } from \"../../components/Layout\";\nimport { Section } from \"../../components/Section\";\n\nexport type RichTextProps = WithLayout<{\n  richtext?: string;\n}>;\n\nconst RichTextInner: ComponentConfig<RichTextProps> = {\n  fields: {\n    richtext: {\n      type: \"richtext\",\n    },\n  },\n  render: ({ richtext }) => {\n    return <Section>{richtext}</Section>;\n  },\n  defaultProps: {\n    richtext: \"<h2>Heading</h2><p>Body</p>\",\n  },\n};\n\nexport const RichText = withLayout(RichTextInner);\n"
  },
  {
    "path": "apps/demo/config/blocks/Space/index.tsx",
    "content": "import React from \"react\";\n\nimport { ComponentConfig } from \"@/core\";\nimport { spacingOptions } from \"../../options\";\nimport { getClassNameFactory } from \"@/core/lib\";\n\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"Space\", styles);\n\nexport type SpaceProps = {\n  direction?: \"\" | \"vertical\" | \"horizontal\";\n  size: string;\n};\n\nexport const Space: ComponentConfig<SpaceProps> = {\n  label: \"Space\",\n  fields: {\n    size: {\n      type: \"select\",\n      options: spacingOptions,\n    },\n    direction: {\n      type: \"radio\",\n      options: [\n        { value: \"vertical\", label: \"Vertical\" },\n        { value: \"horizontal\", label: \"Horizontal\" },\n        { value: \"\", label: \"Both\" },\n      ],\n    },\n  },\n  defaultProps: {\n    direction: \"\",\n    size: \"24px\",\n  },\n  inline: true,\n  render: ({ direction, size, puck }) => {\n    return (\n      <div\n        ref={puck.dragRef}\n        className={getClassName(direction ? { [direction]: direction } : {})}\n        style={{ \"--size\": size } as any}\n      />\n    );\n  },\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Space/styles.module.css",
    "content": ".Space {\n  display: block;\n  height: var(--size);\n  width: var(--size);\n}\n\n.Space--vertical {\n  width: 100%;\n}\n\n.Space--horizontal {\n  width: var(--size);\n  height: 100%;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Stats/index.tsx",
    "content": "/* eslint-disable @next/next/no-img-element */\nimport React from \"react\";\nimport { ComponentConfig } from \"@/core\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport { Section } from \"../../components/Section\";\n\nconst getClassName = getClassNameFactory(\"Stats\", styles);\n\nexport type StatsProps = {\n  items: {\n    title: string;\n    description: string;\n  }[];\n};\n\nexport const Stats: ComponentConfig<StatsProps> = {\n  fields: {\n    items: {\n      type: \"array\",\n      getItemSummary: (item, i) =>\n        item.title && item.description ? (\n          <>\n            {item.title} ({item.description})\n          </>\n        ) : (\n          `Feature #${i}`\n        ),\n      defaultItemProps: {\n        title: \"Stat\",\n        description: \"1,000\",\n      },\n      arrayFields: {\n        title: {\n          type: \"text\",\n          contentEditable: true,\n        },\n        description: {\n          type: \"text\",\n          contentEditable: true,\n        },\n      },\n    },\n  },\n  defaultProps: {\n    items: [\n      {\n        title: \"Stat\",\n        description: \"1,000\",\n      },\n    ],\n  },\n  render: ({ items }) => {\n    return (\n      <Section className={getClassName()} maxWidth={\"916px\"}>\n        <div className={getClassName(\"items\")}>\n          {items.map((item, i) => (\n            <div key={i} className={getClassName(\"item\")}>\n              <div className={getClassName(\"label\")}>{item.title}</div>\n              <div className={getClassName(\"value\")}>{item.description}</div>\n            </div>\n          ))}\n        </div>\n      </Section>\n    );\n  },\n};\n"
  },
  {
    "path": "apps/demo/config/blocks/Stats/styles.module.css",
    "content": ".Stats-items {\n  background-image: linear-gradient(\n    120deg,\n    var(--puck-color-azure-03) 0%,\n    var(--puck-color-azure-05) 100%\n  );\n  border-radius: 24px;\n  display: grid;\n  grid-template-columns: 1fr;\n  grid-gap: 72px;\n  align-items: center;\n  justify-content: space-between;\n  margin: 0 auto;\n  max-width: 768px;\n  padding: 64px 16px;\n}\n\n@media (min-width: 768px) {\n  .Stats-items {\n    padding: 64px 24px;\n  }\n}\n\n@media (min-width: 1024px) {\n  .Stats-items {\n    grid-template-columns: 1fr 1fr;\n    padding: 128px 24px;\n    max-width: 100%;\n  }\n}\n\n.Stats-item {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  color: white;\n  gap: 8px;\n  width: 100%;\n  text-align: center;\n}\n\n.Stats-icon {\n  border-radius: 256px;\n  background: var(--puck-color-azure-09);\n  color: var(--puck-color-azure-06);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  width: 64px;\n  height: 64px;\n}\n\n.Stats-label {\n  font-size: 22px;\n  text-align: center;\n  font-weight: 600;\n  opacity: 0.8;\n}\n\n.Stats-value {\n  font-size: 72px;\n  line-height: 1;\n  font-weight: 700;\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Template/Template.tsx",
    "content": "import React from \"react\";\nimport { Slot } from \"@/core/types\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport { Section } from \"../../components/Section\";\nimport { PuckComponent } from \"@/core/types\";\n\nconst getClassName = getClassNameFactory(\"Template\", styles);\n\nexport type TemplateProps = {\n  template: string;\n  children: Slot;\n};\n\nexport const Template: PuckComponent<TemplateProps> = ({\n  children: Children,\n}) => {\n  return (\n    <Section>\n      <Children className={getClassName()} />\n    </Section>\n  );\n};\n\nexport default Template;\n"
  },
  {
    "path": "apps/demo/config/blocks/Template/client.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport React, { useState } from \"react\";\nimport { AutoField, Button, createUsePuck, FieldLabel, walkTree } from \"@/core\";\nimport { ComponentConfig, ComponentDataOptionalId, Slot } from \"@/core/types\";\nimport { withLayout } from \"../../components/Layout\";\nimport { generateId } from \"@/core/lib/generate-id\";\nimport { componentKey } from \"../../index\";\nimport { type Components } from \"../../types\";\nimport TemplateComponent, { TemplateProps } from \"./Template\";\n\nconst usePuck = createUsePuck();\n\nasync function createComponent<T extends keyof Components>(\n  component: T,\n  props?: Partial<Components[T]>\n): Promise<ComponentDataOptionalId<Components[T]>> {\n  const { conf: config } = await import(\"../../index\");\n\n  return {\n    type: component,\n    props: {\n      ...config.components[component].defaultProps,\n      ...props,\n    },\n  } as ComponentDataOptionalId<Components[T]>;\n}\n\ntype TemplateData = Record<string, { label: string; data: Slot }>;\n\nexport const TemplateInternal: ComponentConfig<TemplateProps> = {\n  fields: {\n    template: {\n      type: \"custom\",\n      render: ({ name, value, onChange }) => {\n        const templateKey = `puck-demo-templates:${componentKey}`;\n\n        const props = usePuck((s) => s.selectedItem?.props) as\n          | TemplateProps\n          | undefined;\n\n        const [templates, setTemplates] = useState<TemplateData>(\n          JSON.parse(localStorage.getItem(templateKey) ?? \"{}\")\n        );\n\n        return (\n          <FieldLabel label={name}>\n            <AutoField\n              value={value}\n              onChange={onChange}\n              field={{\n                type: \"select\",\n                options: [\n                  { label: \"Blank\", value: \"blank\" },\n                  { label: \"Example 1\", value: \"example_1\" },\n                  { label: \"Example 2\", value: \"example_2\" },\n                  ...Object.entries(templates).map(([key, template]) => ({\n                    value: key,\n                    label: template.label,\n                  })),\n                ],\n              }}\n            />\n            <div style={{ marginLeft: \"auto\", marginTop: 16 }}>\n              <Button\n                variant=\"secondary\"\n                onClick={async () => {\n                  if (!props?.children) {\n                    return;\n                  }\n\n                  const templateId = generateId();\n\n                  const { conf: config } = await import(\"../../index\");\n\n                  const data = props.children.map((child) =>\n                    walkTree(\n                      {\n                        type: child.type,\n                        props: { ...child.props, id: generateId(child.type) },\n                      },\n                      config,\n                      (content) =>\n                        content.map((item) => ({\n                          ...item,\n                          props: { ...item.props, id: generateId(item.type) },\n                        }))\n                    )\n                  );\n\n                  const templateData = {\n                    ...templates,\n                    [templateId]: {\n                      label: new Date().toLocaleString(),\n                      data,\n                    },\n                  };\n\n                  localStorage.setItem(\n                    templateKey,\n                    JSON.stringify(templateData)\n                  );\n\n                  setTemplates(templateData);\n\n                  onChange(templateId);\n                }}\n              >\n                Save new template\n              </Button>\n            </div>\n          </FieldLabel>\n        );\n      },\n    },\n    children: {\n      type: \"slot\",\n    },\n  },\n  defaultProps: {\n    template: \"example_1\",\n    children: [],\n  },\n  resolveData: async (data, { changed, trigger }) => {\n    if (!changed.template || trigger === \"load\") return data;\n\n    const templateKey = `puck-demo-templates:${componentKey}`;\n\n    const templates: TemplateData = {\n      ...JSON.parse(localStorage.getItem(templateKey) ?? \"{}\"),\n      blank: {\n        label: \"Blank\",\n        data: [],\n      },\n      example_1: {\n        label: \"Example 1\",\n        data: [\n          await createComponent(\"Heading\", {\n            text: \"Template example.\",\n            size: \"xl\",\n          }),\n          await createComponent(\"Text\", {\n            text: \"This component uses the slots API. Try changing template, or saving a new one via the template field.\",\n          }),\n        ],\n      },\n      example_2: {\n        label: \"Example 2\",\n        data: [\n          await createComponent(\"Grid\", {\n            numColumns: 2,\n            items: [\n              await createComponent(\"Card\", { title: \"A card\", mode: \"card\" }),\n              await createComponent(\"Flex\", {\n                direction: \"column\",\n                gap: 0,\n                items: [\n                  await createComponent(\"Space\", {\n                    size: \"32px\",\n                  }),\n                  await createComponent(\"Heading\", {\n                    text: \"Template example\",\n                    size: \"xl\",\n                  }),\n                  await createComponent(\"Text\", {\n                    text: \"Dynamically create components using the new slots API.\",\n                  }),\n                  await createComponent(\"Space\", {\n                    size: \"16px\",\n                  }),\n                  await createComponent(\"Button\", {\n                    variant: \"secondary\",\n                    label: \"Learn more\",\n                  }),\n                  await createComponent(\"Space\", {\n                    size: \"32px\",\n                  }),\n                ],\n              }),\n            ],\n          }),\n        ],\n      },\n    };\n\n    const children =\n      templates[data.props.template]?.data || templates[\"example_1\"].data;\n\n    return {\n      ...data,\n      props: {\n        ...data.props,\n        children,\n      },\n    };\n  },\n  render: TemplateComponent,\n};\n\nexport const Template = withLayout(TemplateInternal);\n"
  },
  {
    "path": "apps/demo/config/blocks/Template/index.tsx",
    "content": "export * from \"./client\";\nexport { type TemplateProps } from \"./Template\";\n"
  },
  {
    "path": "apps/demo/config/blocks/Template/server.tsx",
    "content": "import { ComponentConfig } from \"@/core/types\";\nimport { withLayout } from \"../../components/Layout\";\nimport TemplateComponent, { TemplateProps } from \"./Template\";\n\nexport const TemplateInternal: ComponentConfig<TemplateProps> = {\n  render: TemplateComponent,\n};\n\nexport const Template = withLayout(TemplateInternal);\n"
  },
  {
    "path": "apps/demo/config/blocks/Template/styles.module.css",
    "content": ".Template {\n  display: flex;\n  flex-direction: column;\n  width: auto;\n}\n\n@media (min-width: 768px) {\n  .Template {\n    display: grid;\n  }\n}\n"
  },
  {
    "path": "apps/demo/config/blocks/Text/index.tsx",
    "content": "import React from \"react\";\nimport { ALargeSmall, AlignLeft } from \"lucide-react\";\n\nimport { ComponentConfig } from \"@/core/types\";\nimport { Section } from \"../../components/Section\";\nimport { WithLayout, withLayout } from \"../../components/Layout\";\n\nexport type TextProps = WithLayout<{\n  align: \"left\" | \"center\" | \"right\";\n  text?: string;\n  padding?: string;\n  size?: \"s\" | \"m\";\n  color: \"default\" | \"muted\";\n  maxWidth?: string;\n}>;\n\nconst TextInner: ComponentConfig<TextProps> = {\n  fields: {\n    text: {\n      type: \"textarea\",\n      contentEditable: true,\n    },\n    size: {\n      type: \"select\",\n      labelIcon: <ALargeSmall size={16} />,\n      options: [\n        { label: \"S\", value: \"s\" },\n        { label: \"M\", value: \"m\" },\n      ],\n    },\n    align: {\n      type: \"radio\",\n      labelIcon: <AlignLeft size={16} />,\n      options: [\n        { label: \"Left\", value: \"left\" },\n        { label: \"Center\", value: \"center\" },\n        { label: \"Right\", value: \"right\" },\n      ],\n    },\n    color: {\n      type: \"radio\",\n      options: [\n        { label: \"Default\", value: \"default\" },\n        { label: \"Muted\", value: \"muted\" },\n      ],\n    },\n    maxWidth: { type: \"text\" },\n  },\n  defaultProps: {\n    align: \"left\",\n    text: \"Text\",\n    size: \"m\",\n    color: \"default\",\n  },\n  render: ({ align, color, text, size, maxWidth }) => {\n    return (\n      <Section maxWidth={maxWidth}>\n        <span\n          style={{\n            color:\n              color === \"default\" ? \"inherit\" : \"var(--puck-color-grey-05)\",\n            display: \"flex\",\n            textAlign: align,\n            width: \"100%\",\n            fontSize: size === \"m\" ? \"20px\" : \"16px\",\n            fontWeight: 300,\n            maxWidth,\n            justifyContent:\n              align === \"center\"\n                ? \"center\"\n                : align === \"right\"\n                ? \"flex-end\"\n                : \"flex-start\",\n          }}\n        >\n          {text}\n        </span>\n      </Section>\n    );\n  },\n};\n\nexport const Text = withLayout(TextInner);\n"
  },
  {
    "path": "apps/demo/config/blocks/Text/styles.module.css",
    "content": ".Text {\n  line-height: 1.5;\n  padding: 0px;\n}\n"
  },
  {
    "path": "apps/demo/config/components/Footer/index.tsx",
    "content": "import { ReactNode } from \"react\";\nimport { Section } from \"../Section\";\n\nconst FooterLink = ({ children, href }: { children: string; href: string }) => {\n  const El = href ? \"a\" : \"span\";\n\n  return (\n    <li style={{ paddingBottom: 8 }}>\n      <El\n        href={href}\n        style={{\n          textDecoration: \"none\",\n          fontSize: \"14px\",\n          color: \"var(--puck-color-grey-05)\",\n        }}\n      >\n        {children}\n      </El>\n    </li>\n  );\n};\n\nconst FooterList = ({\n  children,\n  title,\n}: {\n  children: ReactNode;\n  title: string;\n}) => {\n  return (\n    <div>\n      <h3\n        style={{\n          margin: 0,\n          padding: 0,\n          fontSize: \"inherit\",\n          fontWeight: \"600\",\n          color: \"var(--puck-color-grey-03)\",\n        }}\n      >\n        {title}\n      </h3>\n      <ul\n        style={{\n          listStyle: \"none\",\n          margin: 0,\n          padding: 0,\n          paddingTop: 12,\n        }}\n      >\n        {children}\n      </ul>\n    </div>\n  );\n};\n\nconst Footer = ({ children }: { children: ReactNode }) => {\n  return (\n    <footer style={{ background: \"var(--puck-color-grey-12)\" }}>\n      <h2 style={{ visibility: \"hidden\", height: 0, margin: 0 }}>Footer</h2>\n      <div style={{ padding: 32 }}>\n        <Section>\n          <div\n            style={{\n              display: \"grid\",\n              gridGap: 24,\n              gridTemplateColumns: \"repeat(auto-fit, minmax(200px, 1fr))\",\n              paddingTop: 24,\n              paddingBottom: 24,\n            }}\n          >\n            {children}\n          </div>\n        </Section>\n      </div>\n      <div\n        style={{\n          padding: 64,\n          textAlign: \"center\",\n          color: \"var(--puck-color-grey-03)\",\n          background: \"var(--puck-color-grey-11)\",\n        }}\n      >\n        Made by{\" \"}\n        <a\n          href=\"https://github.com/chrisvxd\"\n          target=\"_blank\"\n          style={{ color: \"inherit\", textDecoration: \"none\", fontWeight: 600 }}\n        >\n          Chris Villa\n        </a>\n      </div>\n    </footer>\n  );\n};\n\nFooter.List = FooterList;\nFooter.Link = FooterLink;\n\nexport { Footer };\n"
  },
  {
    "path": "apps/demo/config/components/Header/index.tsx",
    "content": "import { getClassNameFactory } from \"@/core/lib\";\n\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"Header\", styles);\n\nconst NavItem = ({ label, href }: { label: string; href: string }) => {\n  const navPath =\n    typeof window !== \"undefined\"\n      ? window.location.pathname.replace(\"/edit\", \"\") || \"/\"\n      : \"/\";\n\n  const isActive = navPath === (href.replace(\"/edit\", \"\") || \"/\");\n\n  const El = href ? \"a\" : \"span\";\n\n  return (\n    <El\n      href={href || \"/\"}\n      style={{\n        textDecoration: \"none\",\n        color: isActive\n          ? \"var(--puck-color-grey-02)\"\n          : \"var(--puck-color-grey-06)\",\n        fontWeight: isActive ? \"600\" : \"400\",\n      }}\n    >\n      {label}\n    </El>\n  );\n};\n\nconst Header = ({ editMode }: { editMode: boolean }) => (\n  <div className={getClassName()}>\n    <header className={getClassName(\"inner\")}>\n      <div className={getClassName(\"logo\")}>LOGO</div>\n      <nav className={getClassName(\"items\")}>\n        <NavItem label=\"Home\" href={`${editMode ? \"\" : \"/\"}`} />\n        <NavItem label=\"Pricing\" href={editMode ? \"\" : \"/pricing\"} />\n        <NavItem label=\"About\" href={editMode ? \"\" : \"/about\"} />\n      </nav>\n    </header>\n  </div>\n);\n\nexport { Header };\n"
  },
  {
    "path": "apps/demo/config/components/Header/styles.module.css",
    "content": ".Header {\n  background-color: white;\n  position: sticky;\n  top: 0;\n  z-index: 2;\n}\n\n.Header-inner {\n  align-items: center;\n  display: flex;\n  margin-inline-start: auto;\n  margin-inline-end: auto;\n  max-width: 1280px;\n  padding: 24px 16px;\n}\n\n@media (min-width: 768px) {\n  .Header-inner {\n    padding: 24px;\n  }\n}\n\n.Header-logo {\n  font-size: 24px;\n  font-weight: 800;\n  letter-spacing: 1.4;\n  opacity: 0.8;\n}\n\n.Header-items {\n  display: flex;\n  gap: 24px;\n  margin-inline-start: auto;\n}\n\n@media (min-width: 1024px) {\n  .Header-items {\n    gap: 32px;\n  }\n}\n"
  },
  {
    "path": "apps/demo/config/components/Layout/index.tsx",
    "content": "import { CSSProperties, forwardRef, ReactNode } from \"react\";\nimport {\n  ComponentConfig,\n  DefaultComponentProps,\n  ObjectField,\n} from \"@/core/types\";\nimport { spacingOptions } from \"../../options\";\nimport { getClassNameFactory } from \"@/core/lib\";\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"Layout\", styles);\n\ntype LayoutFieldProps = {\n  padding?: string;\n  spanCol?: number;\n  spanRow?: number;\n  grow?: boolean;\n};\n\nexport type WithLayout<Props extends DefaultComponentProps> = Props & {\n  layout?: LayoutFieldProps;\n};\n\ntype LayoutProps = WithLayout<{\n  children: ReactNode;\n  className?: string;\n  style?: CSSProperties;\n}>;\n\nexport const layoutField: ObjectField<LayoutFieldProps> = {\n  type: \"object\",\n  objectFields: {\n    spanCol: {\n      label: \"Grid Columns\",\n      type: \"number\",\n      min: 1,\n      max: 12,\n    },\n    spanRow: {\n      label: \"Grid Rows\",\n      type: \"number\",\n      min: 1,\n      max: 12,\n    },\n    grow: {\n      label: \"Flex Grow\",\n      type: \"radio\",\n      options: [\n        { label: \"true\", value: true },\n        { label: \"false\", value: false },\n      ],\n    },\n    padding: {\n      type: \"select\",\n      label: \"Vertical Padding\",\n      options: [{ label: \"0px\", value: \"0px\" }, ...spacingOptions],\n    },\n  },\n};\n\nconst Layout = forwardRef<HTMLDivElement, LayoutProps>(\n  ({ children, className, layout, style }, ref) => {\n    return (\n      <div\n        className={className}\n        style={{\n          gridColumn: layout?.spanCol\n            ? `span ${Math.max(Math.min(layout.spanCol, 12), 1)}`\n            : undefined,\n          gridRow: layout?.spanRow\n            ? `span ${Math.max(Math.min(layout.spanRow, 12), 1)}`\n            : undefined,\n          paddingTop: layout?.padding,\n          paddingBottom: layout?.padding,\n          flex: layout?.grow ? \"1 1 0\" : undefined,\n          ...style,\n        }}\n        ref={ref}\n      >\n        {children}\n      </div>\n    );\n  }\n);\n\nLayout.displayName = \"Layout\";\n\nexport { Layout };\n\nexport function withLayout<\n  ThisComponentConfig extends ComponentConfig<any> = ComponentConfig\n>(componentConfig: ThisComponentConfig): ThisComponentConfig {\n  return {\n    ...componentConfig,\n    fields: {\n      ...componentConfig.fields,\n      layout: layoutField,\n    },\n    defaultProps: {\n      ...componentConfig.defaultProps,\n      layout: {\n        spanCol: 1,\n        spanRow: 1,\n        padding: \"0px\",\n        grow: false,\n        ...componentConfig.defaultProps?.layout,\n      },\n    },\n    resolveFields: (_, params) => {\n      if (params.parent?.type === \"Grid\") {\n        return {\n          ...componentConfig.fields,\n          layout: {\n            ...layoutField,\n            objectFields: {\n              spanCol: layoutField.objectFields.spanCol,\n              spanRow: layoutField.objectFields.spanRow,\n              padding: layoutField.objectFields.padding,\n            },\n          },\n        };\n      }\n      if (params.parent?.type === \"Flex\") {\n        return {\n          ...componentConfig.fields,\n          layout: {\n            ...layoutField,\n            objectFields: {\n              grow: layoutField.objectFields.grow,\n              padding: layoutField.objectFields.padding,\n            },\n          },\n        };\n      }\n\n      return {\n        ...componentConfig.fields,\n        layout: {\n          ...layoutField,\n          objectFields: {\n            padding: layoutField.objectFields.padding,\n          },\n        },\n      };\n    },\n    inline: true,\n    render: (props) => (\n      <Layout\n        className={getClassName()}\n        layout={props.layout as LayoutFieldProps}\n        ref={props.puck.dragRef}\n      >\n        {componentConfig.render(props)}\n      </Layout>\n    ),\n  };\n}\n"
  },
  {
    "path": "apps/demo/config/components/Layout/styles.module.css",
    "content": ".Layout {\n  display: block;\n}\n"
  },
  {
    "path": "apps/demo/config/components/Section/index.tsx",
    "content": "import { CSSProperties, forwardRef, ReactNode } from \"react\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"@/core/lib\";\n\nconst getClassName = getClassNameFactory(\"Section\", styles);\n\nexport type SectionProps = {\n  className?: string;\n  children: ReactNode;\n  maxWidth?: string;\n  style?: CSSProperties;\n};\n\nexport const Section = forwardRef<HTMLDivElement, SectionProps>(\n  ({ children, className, maxWidth = \"1280px\", style = {} }, ref) => {\n    return (\n      <div\n        className={`${getClassName()}${className ? ` ${className}` : \"\"}`}\n        style={{\n          ...style,\n        }}\n        ref={ref}\n      >\n        <div className={getClassName(\"inner\")} style={{ maxWidth }}>\n          {children}\n        </div>\n      </div>\n    );\n  }\n);\n"
  },
  {
    "path": "apps/demo/config/components/Section/styles.module.css",
    "content": ".Section:not(.Section .Section) {\n  padding-inline-start: 16px;\n  padding-inline-end: 16px;\n}\n\n@media (min-width: 768px) {\n  .Section:not(.Section .Section) {\n    padding-inline-start: 24px;\n    padding-inline-end: 24px;\n  }\n}\n\n.Section-inner {\n  margin-inline-start: auto;\n  margin-inline-end: auto;\n  height: 100%;\n  width: 100%;\n}\n\n.Section .Section .Section-inner {\n  margin-inline-start: 0;\n  margin-inline-end: 0;\n}\n"
  },
  {
    "path": "apps/demo/config/index.tsx",
    "content": "import { Button } from \"./blocks/Button\";\nimport { Card } from \"./blocks/Card\";\nimport { Grid } from \"./blocks/Grid\";\nimport { Hero } from \"./blocks/Hero\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Flex } from \"./blocks/Flex\";\nimport { Logos } from \"./blocks/Logos\";\nimport { Stats } from \"./blocks/Stats\";\nimport { Template } from \"./blocks/Template\";\nimport { Text } from \"./blocks/Text\";\nimport { Space } from \"./blocks/Space\";\nimport { RichText } from \"./blocks/RichText\";\n\nimport Root from \"./root\";\nimport { UserConfig } from \"./types\";\nimport { initialData } from \"./initial-data\";\n\n// We avoid the name config as next gets confused\nexport const conf: UserConfig = {\n  root: Root,\n  categories: {\n    layout: {\n      components: [\"Grid\", \"Flex\", \"Space\"],\n    },\n    typography: {\n      components: [\"Heading\", \"Text\", \"RichText\"],\n    },\n    interactive: {\n      title: \"Actions\",\n      components: [\"Button\"],\n    },\n    other: {\n      title: \"Other\",\n      components: [\"Card\", \"Hero\", \"Logos\", \"Stats\", \"Template\"],\n    },\n  },\n  components: {\n    Button,\n    Card,\n    Grid,\n    Hero,\n    Heading,\n    Flex,\n    Logos,\n    Stats,\n    Template,\n    Text,\n    Space,\n    RichText,\n  },\n};\n\nexport const componentKey = Buffer.from(\n  `${Object.keys(conf.components).join(\"-\")}-${JSON.stringify(initialData)}`\n).toString(\"base64\");\n\nexport default conf;\n"
  },
  {
    "path": "apps/demo/config/initial-data.ts",
    "content": "import { UserData } from \"./types\";\n\nexport const initialData: Record<string, UserData> = {\n  \"/\": {\n    content: [\n      {\n        type: \"Hero\",\n        props: {\n          title: \"This page was built with Puck\",\n          description:\n            \"<p>Puck is the self-hosted visual editor for React. Bring your own components and make site changes instantly, without a deploy.</p>\",\n          buttons: [\n            {\n              label: \"Visit GitHub\",\n              href: \"https://github.com/puckeditor/puck\",\n            },\n            { label: \"Edit this page\", href: \"/edit\", variant: \"secondary\" },\n          ],\n          id: \"Hero-1687283596554\",\n          image: {\n            url: \"https://images.unsplash.com/photo-1687204209659-3bded6aecd79?ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D&auto=format&fit=crop&w=2670&q=80\",\n            mode: \"inline\",\n            content: [],\n          },\n          padding: \"128px\",\n          align: \"left\",\n        },\n        readOnly: { title: false, description: false },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687298109536\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Heading\",\n        props: {\n          align: \"center\",\n          level: \"2\",\n          text: \"Drag-and-drop your own React components\",\n          layout: { padding: \"0px\" },\n          size: \"xxl\",\n          id: \"Heading-1687297593514\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"8px\",\n          id: \"Space-1687284122744\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Text\",\n        props: {\n          align: \"center\",\n          text: \"Configure Puck with your own components to make change for your marketing pages without a developer.\",\n          layout: { padding: \"0px\" },\n          size: \"m\",\n          id: \"Text-1687297621556\",\n          color: \"muted\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"40px\",\n          id: \"Space-1687296179388\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Grid\",\n        props: {\n          id: \"Grid-c4cd99ae-8c5e-4cdb-87d2-35a639f5163e\",\n          gap: 24,\n          numColumns: 3,\n          items: [\n            {\n              type: \"Card\",\n              props: {\n                title: \"Built for content teams\",\n                description:\n                  \"Puck enables content teams to make changes to their content without a developer or breaking the UI.\",\n                icon: \"pen-tool\",\n                mode: \"flat\",\n                layout: { grow: true, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-66ab42c9-d1da-4c44-9dba-5d7d72f2178d\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"Easy to integrate\",\n                description:\n                  \"Front-end developers can easily integrate their own components using a familiar React API.\",\n                icon: \"git-merge\",\n                mode: \"flat\",\n                layout: { grow: true, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-0012a293-8ef3-4e7c-9d7c-7da0a03d97ae\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"No vendor lock-in\",\n                description:\n                  \"Completely open-source, Puck is designed to be integrated into your existing React application.\",\n                icon: \"github\",\n                mode: \"flat\",\n                layout: { grow: true, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-09efb3f3-f58d-4e07-a481-7238d7e57ad6\",\n              },\n            },\n          ],\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687287070296\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687298110602\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Heading\",\n        props: {\n          align: \"center\",\n          level: \"2\",\n          text: \"The numbers\",\n          layout: { padding: \"0px\" },\n          size: \"xxl\",\n          id: \"Heading-1687296574110\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"16px\",\n          id: \"Space-1687284283005\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Text\",\n        props: {\n          align: \"center\",\n          text: 'This page demonstrates Puck configured with a custom component library. This component is called \"Stats\", and contains some made-up numbers. You can configure any page by adding \"/edit\" onto the URL.',\n          layout: { padding: \"0px\" },\n          size: \"m\",\n          id: \"Text-1687284565722\",\n          color: \"muted\",\n          maxWidth: \"916px\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687297618253\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Stats\",\n        props: {\n          items: [\n            { title: \"Users reached\", description: \"20M+\" },\n            { title: \"Cost savings\", description: \"$1.5M\" },\n            { title: \"Another stat\", description: \"5M kg\" },\n            { title: \"Final fake stat\", description: \"15K\" },\n          ],\n          id: \"Stats-1687297239724\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"120px\",\n          id: \"Space-1687297589663\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Heading\",\n        props: {\n          align: \"center\",\n          level: \"2\",\n          text: \"Extending Puck\",\n          layout: { padding: \"0px\" },\n          size: \"xxl\",\n          id: \"Heading-1687296184321\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"8px\",\n          id: \"Space-1687296602860\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Text\",\n        props: {\n          align: \"center\",\n          text: \"Puck can also be extended with plugins and headless CMS content fields, transforming Puck into the perfect tool for your Content Ops.\",\n          layout: { padding: \"0px\" },\n          size: \"m\",\n          id: \"Text-1687296579834\",\n          color: \"muted\",\n          maxWidth: \"916px\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687299311382\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Grid\",\n        props: {\n          gap: 24,\n          numColumns: 3,\n          id: \"Grid-2da28e88-7b7b-4152-9da0-9f93f41213b6\",\n          items: [\n            {\n              type: \"Card\",\n              props: {\n                title: \"plugin-heading-analyzer\",\n                description:\n                  \"Analyze the document structure and identify WCAG 2.1 issues with your heading hierarchy.\",\n                icon: \"align-left\",\n                mode: \"card\",\n                layout: { grow: false, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-b0e8407d-9fbb-4e76-aa32-d32f655c11d3\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"External data\",\n                description:\n                  \"Connect your components with an existing data source, like Strapi.js.\",\n                icon: \"feather\",\n                mode: \"card\",\n                layout: { grow: false, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-f8ebd568-3a30-4099-a068-22cabae4691b\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"Custom plugins\",\n                description:\n                  \"Create your own plugin to extend Puck for your use case using React.\",\n                icon: \"plug\",\n                mode: \"card\",\n                layout: { grow: false, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-9c3b0acc-ee42-4a4a-8cc7-1b22d98493f1\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"Title\",\n                description: \"Description\",\n                icon: \"Feather\",\n                mode: \"card\",\n                layout: { grow: false, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-dbec4ae9-8208-49bf-8910-3347ff13d957\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"Title\",\n                description: \"Description\",\n                icon: \"Feather\",\n                mode: \"card\",\n                layout: { grow: false, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-e807464c-4974-4dbb-b1c9-989deabce58d\",\n              },\n            },\n            {\n              type: \"Card\",\n              props: {\n                title: \"Title\",\n                description: \"Description\",\n                icon: \"Feather\",\n                mode: \"card\",\n                layout: { grow: false, spanCol: 1, spanRow: 1, padding: \"0px\" },\n                id: \"Card-3b4b7d53-2124-4d7a-a67e-36b24fd765b4\",\n              },\n            },\n          ],\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687299315421\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Heading\",\n        props: {\n          align: \"center\",\n          level: \"2\",\n          text: \"Get started\",\n          layout: { padding: \"0px\" },\n          size: \"xxl\",\n          id: \"Heading-1687299303766\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"16px\",\n          id: \"Space-1687299318902\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Text\",\n        props: {\n          align: \"center\",\n          text: \"Browse the Puck GitHub to get started, or try editing this page\",\n          layout: { padding: \"0px\" },\n          size: \"m\",\n          id: \"Text-1687299305686\",\n          color: \"muted\",\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"24px\",\n          id: \"Space-1687299335149\",\n          direction: \"vertical\",\n        },\n      },\n      {\n        type: \"Flex\",\n        props: {\n          justifyContent: \"center\",\n          direction: \"row\",\n          gap: 24,\n          wrap: \"wrap\",\n          layout: { spanCol: 1, spanRow: 1, padding: \"0px\" },\n          id: \"Flex-7d63d5ff-bd42-4354-b05d-681b16436fd6\",\n          items: [\n            {\n              type: \"Button\",\n              props: {\n                label: \"Visit GitHub\",\n                href: \"https://github.com/puckeditor/puck\",\n                variant: \"primary\",\n                id: \"Button-bd41007c-6627-414d-839a-e261d470d8f9\",\n              },\n            },\n            {\n              type: \"Button\",\n              props: {\n                label: \"Edit this page\",\n                href: \"/edit\",\n                variant: \"secondary\",\n                id: \"Button-6a5fa26c-8a2d-4b08-a756-c46079877127\",\n              },\n            },\n          ],\n        },\n      },\n      {\n        type: \"Space\",\n        props: {\n          size: \"96px\",\n          id: \"Space-1687284290127\",\n          direction: \"vertical\",\n        },\n      },\n    ],\n    root: { props: { title: \"Puck Example\" } },\n    zones: {},\n  },\n  \"/pricing\": {\n    content: [],\n    root: { props: { title: \"Pricing\" } },\n  },\n  \"/about\": {\n    content: [],\n    root: { props: { title: \"About Us\" } },\n  },\n};\n"
  },
  {
    "path": "apps/demo/config/options.ts",
    "content": "export const spacingOptions = [\n  { label: \"8px\", value: \"8px\" },\n  { label: \"16px\", value: \"16px\" },\n  { label: \"24px\", value: \"24px\" },\n  { label: \"32px\", value: \"32px\" },\n  { label: \"40px\", value: \"40px\" },\n  { label: \"48px\", value: \"48px\" },\n  { label: \"56px\", value: \"56px\" },\n  { label: \"64px\", value: \"64px\" },\n  { label: \"72px\", value: \"72px\" },\n  { label: \"80px\", value: \"80px\" },\n  { label: \"88px\", value: \"88px\" },\n  { label: \"96px\", value: \"96px\" },\n  { label: \"104px\", value: \"104px\" },\n  { label: \"112px\", value: \"112px\" },\n  { label: \"120px\", value: \"120px\" },\n  { label: \"128px\", value: \"128px\" },\n  { label: \"136px\", value: \"136px\" },\n  { label: \"144px\", value: \"144px\" },\n  { label: \"152px\", value: \"152px\" },\n  { label: \"160px\", value: \"160px\" },\n];\n"
  },
  {
    "path": "apps/demo/config/root.tsx",
    "content": "import { DefaultRootProps, RootConfig } from \"@/core\";\nimport { Header } from \"./components/Header\";\nimport { Footer } from \"./components/Footer\";\n\nexport type RootProps = DefaultRootProps;\n\nexport const Root: RootConfig<{\n  props: RootProps;\n  fields: {\n    userField: { type: \"userField\"; option: boolean };\n  };\n}> = {\n  defaultProps: {\n    title: \"My Page\",\n  },\n  render: ({ puck: { isEditing, renderDropZone: DropZone } }) => {\n    return (\n      <div\n        style={{ display: \"flex\", flexDirection: \"column\", minHeight: \"100vh\" }}\n      >\n        <Header editMode={isEditing} />\n        <DropZone zone=\"default-zone\" style={{ flexGrow: 1 }} />\n\n        <Footer>\n          <Footer.List title=\"Section\">\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n          </Footer.List>\n          <Footer.List title=\"Section\">\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n          </Footer.List>\n          <Footer.List title=\"Section\">\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n          </Footer.List>\n          <Footer.List title=\"Section\">\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n            <Footer.Link href=\"#\">Label</Footer.Link>\n          </Footer.List>\n        </Footer>\n      </div>\n    );\n  },\n};\n\nexport default Root;\n"
  },
  {
    "path": "apps/demo/config/server.tsx",
    "content": "import { Button } from \"./blocks/Button\";\nimport { Card } from \"./blocks/Card\";\nimport { Grid } from \"./blocks/Grid\";\nimport { Hero } from \"./blocks/Hero/server\";\nimport { Heading } from \"./blocks/Heading\";\nimport { Flex } from \"./blocks/Flex\";\nimport { Logos } from \"./blocks/Logos\";\nimport { Stats } from \"./blocks/Stats\";\nimport { Template } from \"./blocks/Template/server\";\nimport { Text } from \"./blocks/Text\";\nimport { Space } from \"./blocks/Space\";\nimport { RichText } from \"./blocks/RichText\";\nimport Root from \"./root\";\nimport { UserConfig } from \"./types\";\n\n// We avoid the name config as next gets confused\nconst conf: UserConfig = {\n  root: Root,\n  categories: {\n    layout: {\n      components: [\"Grid\", \"Flex\", \"Space\"],\n    },\n    typography: {\n      components: [\"Heading\", \"Text\"],\n    },\n    interactive: {\n      title: \"Actions\",\n      components: [\"Button\"],\n    },\n    other: {\n      title: \"Other\",\n      components: [\"Card\", \"Hero\", \"Logos\", \"Stats\", \"Template\"],\n    },\n  },\n  components: {\n    Button,\n    Card,\n    Grid,\n    Hero,\n    Heading,\n    Flex,\n    Logos,\n    Stats,\n    Template,\n    Text,\n    Space,\n    RichText,\n  },\n};\n\nexport default conf;\n"
  },
  {
    "path": "apps/demo/config/types.ts",
    "content": "import { Config, Data } from \"@/core\";\nimport { ButtonProps } from \"./blocks/Button\";\nimport { CardProps } from \"./blocks/Card\";\nimport { GridProps } from \"./blocks/Grid\";\nimport { HeroProps } from \"./blocks/Hero\";\nimport { HeadingProps } from \"./blocks/Heading\";\nimport { FlexProps } from \"./blocks/Flex\";\nimport { LogosProps } from \"./blocks/Logos\";\nimport { StatsProps } from \"./blocks/Stats\";\nimport { TemplateProps } from \"./blocks/Template\";\nimport { TextProps } from \"./blocks/Text\";\nimport { SpaceProps } from \"./blocks/Space\";\n\nimport { RootProps } from \"./root\";\nimport { RichTextProps } from \"./blocks/RichText\";\n\nexport type { RootProps } from \"./root\";\n\nexport type Components = {\n  Button: ButtonProps;\n  Card: CardProps;\n  Grid: GridProps;\n  Hero: HeroProps;\n  Heading: HeadingProps;\n  Flex: FlexProps;\n  Logos: LogosProps;\n  Stats: StatsProps;\n  Template: TemplateProps;\n  Text: TextProps;\n  Space: SpaceProps;\n  RichText: RichTextProps;\n};\n\nexport type UserConfig = Config<{\n  components: Components;\n  root: RootProps;\n  categories: [\"layout\", \"typography\", \"interactive\"];\n  fields: {\n    userField: {\n      type: \"userField\";\n      option: boolean;\n    };\n  };\n}>;\n\nexport type UserData = Data<Components, RootProps>;\n"
  },
  {
    "path": "apps/demo/lib/resolve-puck-path.ts",
    "content": "const resolvePuckPath = (puckPath: string[] = []) => {\n  const hasPath = puckPath.length > 0;\n\n  const isEdit = hasPath ? puckPath[puckPath.length - 1] === \"edit\" : false;\n\n  return {\n    isEdit,\n    path: `/${(isEdit\n      ? [...puckPath].slice(0, puckPath.length - 1)\n      : [...puckPath]\n    ).join(\"/\")}`,\n  };\n};\n\nexport default resolvePuckPath;\n"
  },
  {
    "path": "apps/demo/lib/use-demo-data.ts",
    "content": "import { useEffect, useState } from \"react\";\nimport config, { componentKey } from \"../config\";\nimport { initialData } from \"../config/initial-data\";\nimport { Metadata, resolveAllData } from \"@/core\";\nimport { Components, UserData } from \"../config/types\";\nimport { RootProps } from \"../config/root\";\n\nconst isBrowser = typeof window !== \"undefined\";\n\nexport const useDemoData = ({\n  path,\n  isEdit,\n  metadata = {},\n}: {\n  path: string;\n  isEdit: boolean;\n  metadata?: Metadata;\n}) => {\n  // unique b64 key that updates each time we add / remove components\n\n  const key = `puck-demo:${componentKey}:${path}`;\n\n  const [data] = useState<Partial<UserData>>(() => {\n    if (isBrowser) {\n      const dataStr = localStorage.getItem(key);\n\n      if (dataStr) {\n        return JSON.parse(dataStr);\n      }\n\n      return initialData[path] || {};\n    }\n  });\n\n  // Normally this would happen on the server, but we can't\n  // do that because we're using local storage as a database\n  const [resolvedData, setResolvedData] = useState<Partial<UserData>>(data);\n\n  useEffect(() => {\n    if (data && !isEdit) {\n      resolveAllData<Components, RootProps>(data, config, metadata).then(\n        setResolvedData\n      );\n    }\n  }, [data, isEdit]);\n\n  useEffect(() => {\n    if (!isEdit) {\n      const title = data?.root?.props?.title || data?.root?.title;\n      document.title = title || \"\";\n    }\n  }, [data, isEdit]);\n\n  return { data, resolvedData, key };\n};\n"
  },
  {
    "path": "apps/demo/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "apps/demo/next.config.js",
    "content": "module.exports = {\n  reactStrictMode: true,\n  transpilePackages: [\"@puckeditor/core\", \"lucide-react\"],\n};\n"
  },
  {
    "path": "apps/demo/package.json",
    "content": "{\n  \"name\": \"demo\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"eslint\"\n  },\n  \"dependencies\": {\n    \"classnames\": \"^2.3.2\",\n    \"lucide-react\": \"^0.468.0\",\n    \"next\": \"^16.0.8\",\n    \"rc-footer\": \"^0.6.8\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"eslint-config-custom\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  }\n}\n"
  },
  {
    "path": "apps/demo/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Default\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"inlineSources\": false,\n    \"isolatedModules\": true,\n    \"moduleResolution\": \"node\",\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"preserveWatchOutput\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/demo/tsconfig/nextjs.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Next.js\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"allowJs\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"incremental\": true,\n    \"jsx\": \"preserve\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"strict\": false,\n    \"target\": \"es6\"\n  },\n  \"include\": [\"src\", \"next-env.d.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/demo/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig/nextjs.json\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"paths\": {\n      \"@/core\": [\"../../packages/core\"],\n      \"@/core/*\": [\"../../packages/core/*\"],\n      \"@puckeditor/core\": [\"../../packages/core\"],\n      \"@/plugin-heading-analyzer\": [\"../../packages/plugin-heading-analyzer\"],\n      \"@/plugin-heading-analyzer/*\": [\n        \"../../packages/plugin-heading-analyzer/*\"\n      ]\n    }\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/docs/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: [\"custom\"],\n};\n"
  },
  {
    "path": "apps/docs/.gitignore",
    "content": "/public/sitemap.xml\n"
  },
  {
    "path": "apps/docs/components/CtaCard/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport getClassNameFactory from \"@/core/lib/get-class-name-factory\";\nimport { Button } from \"@/core/components/Button\";\nimport { DiscoveryButton } from \"../DiscoveryButton\";\n\nconst getClassName = getClassNameFactory(\"CtaCard\", styles);\n\nexport const CtaCard = () => (\n  <div className={getClassName()}>\n    <h2 className={getClassName(\"heading\")} id=\"support\">\n      Stuck with Puck?\n    </h2>\n    <p>We provide Puck support, design system builds, and consultancy.</p>\n    <div className={getClassName(\"actions\")}>\n      <DiscoveryButton />\n      <Button href=\"https://discord.gg/D9e4E3MQVZ\" variant=\"secondary\" newTab>\n        Join Discord — Free\n      </Button>\n    </div>\n  </div>\n);\n"
  },
  {
    "path": "apps/docs/components/CtaCard/styles.module.css",
    "content": ".CtaCard {\n  background-color: var(--puck-color-azure-01);\n  background-image: url(https://res.cloudinary.com/measuredco/image/upload/v1732634892/site/site-background-top_v8ll2o.png),\n    url(https://res.cloudinary.com/measuredco/image/upload/v1732635074/site/site-background-repeat_kjbjx5.png);\n  background-position: center -1rem, top;\n  background-repeat: no-repeat, repeat-y;\n  background-size: auto;\n  color: white;\n  display: flex;\n  flex-direction: column;\n  gap: 16px;\n  max-width: 544px;\n  padding: 32px;\n  text-align: left;\n  border: 1px solid var(--puck-color-grey-03);\n  border-radius: 8px;\n  margin-top: 32px;\n}\n\n.CtaCard p a {\n  color: #6db5f8 !important;\n}\n\n.CtaCard p a:hover {\n  color: #93c5fa !important;\n  opacity: 0.8;\n}\n\n.CtaCard-heading {\n  font-size: 24px;\n  font-weight: 700;\n  line-height: 1.2;\n}\n\n.CtaCard-actions {\n  display: flex;\n  gap: 12px;\n  flex-wrap: wrap;\n}\n"
  },
  {
    "path": "apps/docs/components/DiscoveryButton/index.tsx",
    "content": "import { Button } from \"@/core/components/Button\";\n\nexport function DiscoveryButton() {\n  return (\n    <>\n      <script\n        type=\"text/javascript\"\n        dangerouslySetInnerHTML={{\n          __html: `\n    (function (C, A, L) { let p = function (a, ar) { a.q.push(ar); }; let d = C.document; C.Cal = C.Cal || function () { let cal = C.Cal; let ar = arguments; if (!cal.loaded) { cal.ns = {}; cal.q = cal.q || []; d.head.appendChild(d.createElement(\"script\")).src = A; cal.loaded = true; } if (ar[0] === L) { const api = function () { p(api, arguments); }; const namespace = ar[1]; api.q = api.q || []; if(typeof namespace === \"string\"){cal.ns[namespace] = cal.ns[namespace] || api;p(cal.ns[namespace], ar);p(cal, [\"initNamespace\", namespace]);} else p(cal, ar); return;} p(cal, ar); }; })(window, \"https://app.cal.com/embed/embed.js\", \"init\");\nCal(\"init\", \"puck-enquiry\", {origin:\"https://cal.com\"});\n  Cal.ns[\"puck-enquiry\"](\"ui\", {\"hideEventTypeDetails\":false,\"layout\":\"month_view\"});\n  `,\n        }}\n      />\n\n      <Button\n        data-cal-link=\"chrisvxd/puck-enquiry\"\n        data-cal-namespace=\"puck-enquiry\"\n        data-cal-config='{\"layout\":\"month_view\"}'\n        variant=\"primary\"\n      >\n        Book discovery call\n      </Button>\n    </>\n  );\n}\n"
  },
  {
    "path": "apps/docs/components/FooterActions/index.tsx",
    "content": "import { getClassNameFactory } from \"@/core/lib\";\n\nimport styles from \"./styles.module.css\";\nimport { ThemeSwitch } from \"nextra-theme-docs\";\nimport { ReleaseSwitcher } from \"../ReleaseSwitcher\";\n\nconst getClassName = getClassNameFactory(\"FooterActions\", styles);\n\nexport const FooterActions = () => {\n  return (\n    <div className={getClassName()}>\n      <div className={getClassName(\"themeSwitch\")}>\n        <ThemeSwitch />\n      </div>\n      <div className={getClassName(\"releaseSwitcher\")}>\n        <ReleaseSwitcher variant=\"light\" />\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/docs/components/FooterActions/styles.module.css",
    "content": ".FooterActions {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 16px;\n  width: 100%;\n}\n\n.FooterActions-releaseSwitcher {\n  display: block;\n  margin-inline-start: auto;\n}\n\n@media (min-width: 768px) {\n  .FooterActions-releaseSwitcher {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "apps/docs/components/Home/index.tsx",
    "content": "import React from \"react\";\n\nimport styles from \"./styles.module.css\";\nimport getClassNameFactory from \"@/core/lib/get-class-name-factory\";\nimport { Button } from \"@/core/components/Button\";\nimport Link from \"next/link\";\nimport { CtaCard } from \"../CtaCard\";\n\nconst getClassName = getClassNameFactory(\"Home\", styles);\n\nexport const Home = () => {\n  return (\n    <div className={getClassName()}>\n      <div className={getClassName(\"title\")}>\n        <h1 style={{ visibility: \"hidden\" }}>Puck</h1>\n\n        <span>Open-source under MIT</span>\n        <h2 className={getClassName(\"heading\")}>The visual editor for React</h2>\n      </div>\n      <div style={{ paddingTop: 24 }} />\n      <div className={getClassName(\"description\")}>\n        <p style={{ fontSize: 18, lineHeight: 1.5, opacity: 0.7 }}>\n          Puck empowers developers to build amazing visual editing experiences\n          into their own React applications, powering the next generation of\n          content tools, no-code builders and WYSIWYG editors.\n        </p>\n      </div>\n      <div style={{ paddingTop: 32 }} />\n      <div className={getClassName(\"ctas\")}>\n        <div className={getClassName(\"actions\")}>\n          <Link href=\"/docs\" style={{ display: \"flex\" }}>\n            <Button>Read docs</Button>\n          </Link>\n          <Button href=\"https://demo.puckeditor.com/edit\" variant=\"secondary\">\n            View demo\n          </Button>\n        </div>\n        <div style={{ paddingTop: 32 }} />\n        <pre style={{ padding: 0, margin: 0 }}>\n          <span style={{ userSelect: \"none\" }}>~ </span>npm i @puckeditor/core\n          --save\n        </pre>\n      </div>\n      <div className={getClassName(\"peakWrapper\")}>\n        <div>\n          <div className={getClassName(\"dot\")} />\n          <div className={getClassName(\"dot\")} />\n          <div className={getClassName(\"dot\")} />\n        </div>\n\n        <div className={getClassName(\"peak\")}>\n          <CtaCard />\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/docs/components/Home/styles.module.css",
    "content": ".Home {\n  display: flex;\n  padding-bottom: 48px;\n  padding-top: 48px;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n  text-align: center;\n  margin-inline-start: auto;\n  margin-inline-end: auto;\n  height: 100%;\n  max-height: 1280px;\n}\n\n@media (min-width: 768px) {\n  .Home {\n    padding-bottom: 96px;\n    padding-top: 96px;\n  }\n}\n\n.Home-title {\n  max-width: 1156px;\n}\n\n.Home-description {\n  max-width: 896px;\n}\n\n.Home-heading {\n  font-size: 40px;\n  font-weight: 700;\n  line-height: 1.2;\n}\n\n@media (min-width: 768px) {\n  .Home-heading {\n    font-size: 64px;\n  }\n}\n\n.Home-actions {\n  display: flex;\n  justify-content: center;\n  gap: 16px;\n}\n\n.Home-builtBy {\n  color: currentColor;\n  display: flex;\n  gap: 16px;\n  flex-direction: column;\n}\n\n.Home-builtBy a {\n  color: #0158ad;\n}\n\n:global(html.dark) .Home-builtBy a {\n  color: currentColor;\n}\n\n.Home-builtBy a:hover {\n  opacity: 0.8;\n}\n\n.Home-dot {\n  border-radius: 8px;\n  background-color: currentColor;\n  padding: 2px;\n  margin: 28px;\n  opacity: 0.5;\n}\n\n.Home-peakWrapper {\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  margin-top: 32px;\n  text-align: center;\n  width: 100%;\n}\n"
  },
  {
    "path": "apps/docs/components/Preview/index.tsx",
    "content": "import React, {\n  createContext,\n  CSSProperties,\n  useContext,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\n\nexport { AutoField } from \"@/core/components/AutoField\";\n\nimport { ReactNode } from \"react\";\nimport \"@/core/styles.css\";\nimport { Puck } from \"@/core/components/Puck\";\n\nimport { AppState, ComponentConfig } from \"@/core/types\";\nimport { getClassNameFactory } from \"@/core/lib\";\n\nimport styles from \"./styles.module.css\";\nimport { createUsePuck } from \"@/core/lib/use-puck\";\n\nimport { ChevronUp, ChevronDown } from \"lucide-react\";\n\nimport { codeToHtml } from \"shiki\";\n\nimport { createStore } from \"zustand\";\nimport { useContextStore } from \"@/core/lib/use-context-store\";\nimport { Button, registerOverlayPortal } from \"@/core\";\n\nexport const PreviewStoreContext = createContext(\n  createStore(() => ({ drawerVisible: false }))\n);\n\nconst getClassNamePreview = getClassNameFactory(\"PreviewFrame\", styles);\n\nconst DrawerButton = () => {\n  const drawerVisible = useContextStore(\n    PreviewStoreContext,\n    (s) => s.drawerVisible\n  );\n\n  const previewStore = useContext(PreviewStoreContext);\n\n  return (\n    <button\n      className={getClassNamePreview(\"drawerButton\")}\n      onClick={(e) => {\n        e.preventDefault();\n        e.stopPropagation();\n        previewStore.setState((s) => ({ drawerVisible: !s.drawerVisible }));\n      }}\n    >\n      {drawerVisible ? \"Hide\" : \"Show\"} output{\" \"}\n      <div className={getClassNamePreview(\"drawerButtonIcon\")}>\n        {drawerVisible ? (\n          <ChevronUp size=\"14px\" />\n        ) : (\n          <ChevronDown size=\"14px\" />\n        )}\n      </div>\n    </button>\n  );\n};\n\nconst Drawer = ({ renderDrawer }: { renderDrawer: () => ReactNode }) => {\n  const drawerVisible = useContextStore(\n    PreviewStoreContext,\n    (s) => s.drawerVisible\n  );\n\n  return drawerVisible ? (\n    <div className={getClassNamePreview(\"drawer\")}>{renderDrawer()}</div>\n  ) : (\n    <div />\n  );\n};\n\nconst usePuck = createUsePuck();\n\nexport const PreviewFrame = ({\n  children,\n  label,\n  style = {},\n  disableOnClick = false,\n  renderInfo,\n  renderDrawer,\n}: {\n  children?: ReactNode;\n  label?: string;\n  style?: CSSProperties;\n  disableOnClick?: boolean;\n  renderInfo?: () => ReactNode;\n  renderDrawer?: () => ReactNode;\n}) => {\n  const dispatch = usePuck((s) => s.dispatch);\n\n  return (\n    <div\n      className={getClassNamePreview()}\n      onClick={() => {\n        if (disableOnClick) return;\n\n        dispatch({ type: \"setUi\", ui: { itemSelector: null } });\n      }}\n    >\n      <div className={getClassNamePreview(\"header\")}>\n        <div className={getClassNamePreview(\"annotation\")}>\n          Interactive Demo\n        </div>\n        {label && <div className={getClassNamePreview(\"label\")}>{label}</div>}\n      </div>\n      <div className={getClassNamePreview(\"contents\")}>\n        {renderInfo && (\n          <div className={getClassNamePreview(\"info\")} style={style}>\n            {renderInfo()}\n          </div>\n        )}\n        <div className={getClassNamePreview(\"body\")} style={style}>\n          {children}\n        </div>\n        {renderDrawer && <DrawerButton />}\n      </div>\n      {renderDrawer && <Drawer renderDrawer={renderDrawer} />}\n    </div>\n  );\n};\n\nexport const CodeBlock = ({ code }: { code: string | object }) => {\n  const [html, setHtml] = useState(\"<span />\");\n\n  useEffect(() => {\n    (async () => {\n      const html = await codeToHtml(JSON.stringify(code, null, 2), {\n        lang: \"javascript\",\n        theme: \"github-dark\",\n      });\n\n      setHtml(html);\n    })();\n  }, [code]);\n\n  return (\n    <div\n      className={getClassNamePreview(\"codeblock\")}\n      dangerouslySetInnerHTML={{ __html: html }}\n    />\n  );\n};\n\nexport const PuckPreview = ({\n  label,\n  children = <Puck.Preview />,\n  style = {},\n  disableOnClick,\n  renderInfo,\n  renderDrawer,\n  ...puckProps\n}: React.ComponentProps<typeof Puck> & {\n  label: string;\n  disableOnClick: boolean;\n  children?: ReactNode;\n  style?: CSSProperties;\n  renderInfo?: () => ReactNode;\n  renderDrawer?: () => ReactNode;\n}) => {\n  const [store] = useState(createStore(() => ({ drawerVisible: false })));\n\n  return (\n    <Puck config={{}} data={{}} {...puckProps} iframe={{ enabled: false }}>\n      <PreviewStoreContext value={store}>\n        <PreviewFrame\n          label={label}\n          style={style}\n          renderInfo={renderInfo}\n          renderDrawer={renderDrawer}\n          disableOnClick={disableOnClick}\n        >\n          {children}\n        </PreviewFrame>\n      </PreviewStoreContext>\n    </Puck>\n  );\n};\n\nconst ConfigPreviewInner = ({\n  children,\n  componentConfig,\n}: {\n  children?: ReactNode;\n  componentConfig: ComponentConfig;\n}) => {\n  const appState = usePuck((s) => s.appState);\n\n  return (\n    <div>\n      {componentConfig.render && (\n        <div className={getClassNamePreview(\"preview\")}>\n          {children ??\n            componentConfig.render({\n              ...appState.data[\"content\"][0]?.props,\n              puck: {\n                renderDropZone: () => <div />,\n                isEditing: false,\n                metadata: {},\n                dragRef: null,\n              },\n            })}\n        </div>\n      )}\n    </div>\n  );\n};\n\nexport const CodeBlockDrawer = ({\n  getCode,\n}: {\n  getCode?: (appState: AppState) => object | string;\n}) => {\n  const appState = usePuck((s) => s.appState);\n  const code = getCode?.(appState) ?? \"\";\n\n  return <CodeBlock code={code} />;\n};\n\nexport const ConfigPreview = ({\n  children,\n  componentConfig,\n  label,\n}: {\n  children?: ReactNode;\n  componentConfig: ComponentConfig;\n  label: string;\n}) => {\n  return (\n    <PuckPreview\n      label={label}\n      config={{ components: { Example: componentConfig } }}\n      data={{\n        content: [\n          {\n            type: \"Example\",\n            props: { ...componentConfig.defaultProps, id: \"example\" },\n          },\n        ],\n        root: { props: {} },\n      }}\n      onPublish={() => {}}\n      ui={{ itemSelector: { index: 0 } }}\n      disableOnClick\n      renderInfo={() => (\n        <div onClick={(e) => e.stopPropagation()}>\n          <Puck.Fields />\n        </div>\n      )}\n      permissions={{ drag: false }}\n      renderDrawer={() => (\n        <CodeBlockDrawer\n          getCode={(appState) => {\n            const { id, ...otherProps } = appState.data.content[0].props;\n\n            return otherProps;\n          }}\n        />\n      )}\n      overrides={{ actionBar: () => null, componentOverlay: () => null }}\n      style={{ padding: 0 }}\n    >\n      <ConfigPreviewInner componentConfig={componentConfig}>\n        {children}\n      </ConfigPreviewInner>\n    </PuckPreview>\n  );\n};\n\nexport const OverlayPortalPreview = () => {\n  const ref = useRef<HTMLSpanElement>(null);\n\n  useEffect(() => registerOverlayPortal(ref.current), [ref.current]);\n\n  return (\n    <span ref={ref} style={{ display: \"inline-block\" }}>\n      <Button onClick={() => alert(\"Click\")}>Clickable</Button>\n    </span>\n  );\n};\n\nexport const OverlayPortalTabsPreview = () => {\n  const ref = useRef<HTMLDivElement>(null);\n\n  const [selected, setSelected] = useState(1);\n\n  useEffect(() => registerOverlayPortal(ref.current), [ref.current]);\n\n  return (\n    <div>\n      <div ref={ref} style={{ display: \"inline-flex\", gap: 8 }}>\n        <Button\n          onClick={() => setSelected(1)}\n          variant={selected === 1 ? \"primary\" : \"secondary\"}\n        >\n          Tab 1\n        </Button>\n        <Button\n          onClick={() => setSelected(2)}\n          variant={selected === 2 ? \"primary\" : \"secondary\"}\n        >\n          Tab 2\n        </Button>\n      </div>\n      <div\n        style={{\n          background: \"#eee\",\n          display: \"flex\",\n          padding: 64,\n          justifyContent: \"center\",\n          alignItems: \"center\",\n          gap: 8,\n          marginTop: 8,\n          fontSize: 32,\n        }}\n      >\n        {selected === 1 && <div>Tab 1</div>}\n        {selected === 2 && <div>Tab 2</div>}\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "apps/docs/components/Preview/styles.module.css",
    "content": ".PreviewFrame {\n  background: white;\n  border: 1px solid var(--puck-color-grey-09);\n  color: black;\n  border-radius: 16px;\n  margin-top: 32px;\n  overflow: hidden;\n}\n\n.PreviewFrame-header {\n  background: var(--puck-color-azure-11);\n  gap: 8px;\n  color: var(--puck-color-azure-05);\n  font-weight: 0;\n  padding: 12px 16px;\n  border-top-left-radius: 15px;\n  border-top-right-radius: 15px;\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  align-items: center;\n}\n\n.PreviewFrame-annotation {\n  text-transform: uppercase;\n  font-size: 12px;\n  font-weight: 700;\n}\n\n.PreviewFrame-label {\n  margin-inline-start: auto;\n}\n\n.PreviewFrame-contents {\n  align-items: stretch;\n  background-color: var(--puck-color-grey-09);\n  display: flex;\n  gap: 1px;\n  flex-wrap: wrap;\n  position: relative;\n}\n\n.PreviewFrame-body {\n  background-color: white;\n  flex-grow: 1;\n  padding: 32px;\n  flex-basis: 49%;\n}\n\n.PreviewFrame-info {\n  background-color: white;\n  flex-grow: 1;\n  flex-basis: 49%;\n}\n\n.PreviewFrame-codeblock {\n  height: 100%;\n  overflow: auto;\n}\n\n.PreviewFrame-codeblock pre {\n  white-space: break-spaces;\n  height: 100%;\n  margin: 0;\n  padding: 24px;\n}\n\n.PreviewFrame-drawerButton {\n  align-items: center;\n  border-left: 1px solid var(--puck-color-grey-10);\n  border-top: 1px solid var(--puck-color-grey-10);\n  display: flex;\n  gap: 4px;\n  border-top-left-radius: 4px;\n  font-size: var(--puck-font-size-xxxs);\n  font-weight: 500;\n  position: absolute;\n  padding: 4px 12px;\n  padding-right: 4px;\n  bottom: 0px;\n  right: 0px;\n}\n\n.PreviewFrame-drawerButton:hover {\n  background: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-02);\n}\n\n.PreviewFrame-drawerButtonIcon {\n  color: var(--puck-color-grey-07);\n}\n\n.PreviewFrame-drawer {\n  background: var(--puck-color-grey-12);\n  border-top: 1px solid var(--puck-color-grey-09);\n}\n\n.PreviewFrame-preview {\n  color: var(--puck-color-grey-03);\n  padding: 16px;\n}\n\n.PreviewFrame-preview > * {\n  color: var(\n    --puck-color-grey-03\n  ) !important; /* Override any injected Nextra/Tailwind styles */\n}\n\n.PreviewFrame :global(.rich-text) > * {\n  margin-block: 12px;\n}\n\n.PreviewFrame :global(.rich-text) > *:first-child {\n  margin-top: 0px;\n}\n\n.PreviewFrame :global(.rich-text) > *:last-child {\n  margin-bottom: 0px;\n}\n\n.PreviewFrame h1 {\n  font-size: var(--puck-font-size-xxxl);\n  font-weight: 600;\n}\n\n.PreviewFrame h2 {\n  font-size: var(--puck-font-size-xxl);\n  font-weight: 600;\n}\n\n.PreviewFrame h3 {\n  font-size: var(--puck-font-size-xl);\n  font-weight: 600;\n}\n\n.PreviewFrame h4 {\n  font-size: var(--puck-font-size-l);\n  font-weight: 600;\n}\n\n.PreviewFrame h5 {\n  font-size: var(--puck-font-size-m);\n  font-weight: 600;\n}\n\n.PreviewFrame h6 {\n  font-size: var(--puck-font-size-s);\n  font-weight: 600;\n}\n\n.PreviewFrame ul {\n  padding-left: 24px;\n  list-style-type: disc;\n}\n\n.PreviewFrame ol {\n  padding-left: 24px;\n  list-style-type: decimal;\n}\n\n.PreviewFrame p,\n.PreviewFrame ul,\n.PreviewFrame ol,\n.PreviewFrame blockquote,\n.PreviewFrame pre {\n  margin-block: 12px;\n}\n"
  },
  {
    "path": "apps/docs/components/ReleaseSwitcher/index.tsx",
    "content": "import { useEffect, useState } from \"react\";\n\nimport packageJson from \"../../package.json\";\nimport { getClassNameFactory } from \"@/core/lib\";\n\nimport styles from \"./styles.module.css\";\n\nconst BASE_URL = process.env.NEXT_PUBLIC_BASE_URL || \"https://puckeditor.com\";\n\nconst { version } = packageJson;\n\nconst getClassName = getClassNameFactory(\"ReleaseSwitcher\", styles);\n\nexport const ReleaseSwitcher = ({\n  variant = \"default\",\n}: {\n  variant?: \"light\" | \"default\";\n}) => {\n  const isCanary = process.env.NEXT_PUBLIC_IS_CANARY === \"true\" || false;\n  const isLatest = process.env.NEXT_PUBLIC_IS_LATEST === \"true\" || false;\n\n  const currentValue = isCanary ? \"canary\" : isLatest ? \"\" : version;\n\n  const [options, setOptions] = useState<{ value: string; label: string }[]>([\n    {\n      label: \"canary\",\n      value: \"canary\",\n    },\n    ...(isCanary\n      ? []\n      : [\n          {\n            label: isLatest ? `${version} (latest)` : version,\n            value: isLatest ? \"\" : version,\n          },\n        ]),\n  ]);\n\n  useEffect(() => {\n    fetch(`${BASE_URL}/api/releases`)\n      .then(async (res) => {\n        const { releases } = await res.json();\n        const releaseOptions = Object.keys(releases).map((key) => ({\n          label: key,\n          value: key,\n        }));\n\n        releaseOptions[1].label = `${releaseOptions[1].label} (latest)`;\n        releaseOptions[1].value = \"\"; // Okay to set to \"\" because isLatest will be true for this release option\n\n        setOptions(releaseOptions);\n      })\n      .catch((e) => {\n        console.error(`Could not load releases: ${e}`);\n      });\n  }, []);\n\n  return (\n    <select\n      className={getClassName({ [variant]: true })}\n      value={currentValue}\n      onChange={(e) => {\n        const newHref = e.currentTarget.value\n          ? `/v/${e.currentTarget.value}`\n          : \"https://puckeditor.com\";\n\n        if (window.parent) {\n          window.parent.location.href = newHref;\n        } else {\n          window.location.href = newHref;\n        }\n      }}\n    >\n      {options.map((option) => (\n        <option key={option.value} value={option.value}>\n          {option.label}\n        </option>\n      ))}\n    </select>\n  );\n};\n"
  },
  {
    "path": "apps/docs/components/ReleaseSwitcher/styles.module.css",
    "content": ".ReleaseSwitcher {\n  appearance: none; /* Safari */\n  background: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23c3c3c3'><polygon points='0,0 100,0 50,50'/></svg>\")\n    no-repeat;\n  background-size: 12px;\n  background-position: calc(100% - 12px) calc(50% + 3px);\n  background-repeat: no-repeat;\n  background-color: var(--puck-color-grey-11);\n  border-radius: 100px;\n  color: black;\n  padding-inline-start: 16px;\n  padding-inline-end: 16px;\n  height: 33px; /* Magic number to align with Nextra search */\n  width: 156px;\n}\n\n.ReleaseSwitcher--light {\n  background-color: white;\n  border: 1px solid var(--puck-color-grey-10);\n}\n"
  },
  {
    "path": "apps/docs/components/Viewport/index.tsx",
    "content": "import { getClassNameFactory } from \"@/core/lib\";\n\nimport styles from \"./styles.module.css\";\nimport { ReactNode } from \"react\";\n\nconst getClassName = getClassNameFactory(\"Viewport\", styles);\n\nexport const Viewport = ({\n  children,\n  mobile,\n  desktop,\n}: {\n  children: ReactNode;\n  mobile?: boolean;\n  desktop?: boolean;\n}) => {\n  return <div className={getClassName({ mobile, desktop })}>{children}</div>;\n};\n"
  },
  {
    "path": "apps/docs/components/Viewport/styles.module.css",
    "content": ".Viewport {\n  display: none;\n}\n\n.Viewport--mobile {\n  display: block;\n}\n\n@media (min-width: 768px) {\n  .Viewport--desktop {\n    display: block;\n  }\n\n  .Viewport--mobile:not(.Viewport--desktop) {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "apps/docs/middleware.ts",
    "content": "import { NextResponse } from \"next/server\";\nimport type { NextRequest } from \"next/server\";\n\nimport releases from \"./releases.json\";\n\nconst versionPattern = /\\/v\\/([\\d+|\\.]+|canary)/;\n\nexport function middleware(request: NextRequest) {\n  const path = `${request.nextUrl.pathname}${request.nextUrl.search}`;\n\n  const urlMatch = versionPattern.exec(request.url);\n\n  if (urlMatch) {\n    const version = urlMatch[1];\n    const newUrl = `${releases[version]}${path}`;\n\n    return NextResponse.rewrite(new URL(newUrl));\n  }\n}\n\n// See \"Matching Paths\" below to learn more\nexport const config = {\n  matcher: [\"/v/:path*\"],\n};\n"
  },
  {
    "path": "apps/docs/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/pages/building-your-application/configuring/typescript for more information.\n"
  },
  {
    "path": "apps/docs/next-sitemap.config.js",
    "content": "/** @type {import('next-sitemap').IConfig} */\nmodule.exports = {\n  siteUrl: \"https://puckeditor.com\",\n  generateIndexSitemap: false,\n};\n"
  },
  {
    "path": "apps/docs/next.config.mjs",
    "content": "import packageJson from \"./package.json\" assert { type: \"json\" };\nimport nextra from \"nextra\";\n\nconst withNextra = nextra({\n  theme: \"nextra-theme-docs\",\n  themeConfig: \"./theme.config.tsx\",\n});\n\nconst BRANCH_NAME = process.env.VERCEL_GIT_COMMIT_REF || \"\";\nconst IS_RELEASE_BRANCH = BRANCH_NAME.startsWith(\"releases/\");\n\nexport default withNextra({\n  async redirects() {\n    return [\n      {\n        source: \"/docs/api-reference/configuration/fields/:path*\",\n        destination: \"/docs/api-reference/fields/:path*\",\n        permanent: true,\n      },\n      {\n        source: \"/docs/api-reference/plugins\",\n        destination: \"/docs/api-reference/plugin\",\n        permanent: true,\n      },\n      {\n        source: \"/docs/api-reference/overrides/component-list\",\n        destination: \"/docs/api-reference/overrides/components\",\n        permanent: true,\n      },\n      {\n        source: \"/docs/api-reference/data\",\n        destination: \"/docs/api-reference/data-model/data\",\n        permanent: true,\n      },\n      {\n        source: \"/docs/api-reference/app-state\",\n        destination: \"/docs/api-reference/data-model/app-state\",\n        permanent: true,\n      },\n      {\n        source: \"/docs/extending-puck/custom-interfaces\",\n        destination: \"/docs/extending-puck/composition\",\n        permanent: true,\n      },\n    ];\n  },\n  transpilePackages: [\"@puckeditor/core\"],\n  basePath: IS_RELEASE_BRANCH\n    ? `/v/${packageJson.version}`\n    : process.env.NEXT_PUBLIC_IS_CANARY\n    ? \"/v/canary\"\n    : \"\",\n});\n"
  },
  {
    "path": "apps/docs/package.json",
    "content": "{\n  \"name\": \"docs\",\n  \"version\": \"0.21.1\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"postbuild\": \"next-sitemap\",\n    \"start\": \"next start\",\n    \"lint\": \"next lint\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"eslint-config-custom\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  },\n  \"dependencies\": {\n    \"next\": \"^14.2.11\",\n    \"next-sitemap\": \"^4.2.3\",\n    \"nextra\": \"^3.2.5\",\n    \"nextra-theme-docs\": \"^3.2.5\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\"\n  }\n}\n"
  },
  {
    "path": "apps/docs/pages/_app.tsx",
    "content": "import type { AppProps } from \"next/app\";\nimport \"../styles.css\";\n\nexport default function DocsApp({ Component, pageProps }: AppProps) {\n  return <Component {...pageProps} />;\n}\n"
  },
  {
    "path": "apps/docs/pages/_document.tsx",
    "content": "import { Html, Head, Main, NextScript } from \"next/document\";\n\nexport default function Document() {\n  return (\n    <Html lang=\"en\">\n      <Head>\n        {process.env.NEXT_PUBLIC_PLAUSIBLE_DATA_DOMAIN && (\n          <script\n            defer\n            data-domain={process.env.NEXT_PUBLIC_PLAUSIBLE_DATA_DOMAIN}\n            src=\"https://plausible.io/js/plausible.js\"\n          ></script>\n        )}\n      </Head>\n      <body>\n        <Main />\n        <NextScript />\n      </body>\n    </Html>\n  );\n}\n"
  },
  {
    "path": "apps/docs/pages/_meta.js",
    "content": "const menu = {\n  index: {\n    type: \"page\",\n    title: \"Puck\",\n    display: \"hidden\",\n    theme: {\n      layout: \"full\",\n    },\n  },\n  docs: {\n    type: \"page\",\n    title: \"Docs\",\n  },\n  measured: {\n    type: \"page\",\n    href: \"/#support\",\n    title: \"Support\",\n    newWindow: false,\n  },\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/api/releases.ts",
    "content": "import type { NextApiRequest, NextApiResponse } from \"next\";\n\nimport releases from \"../../releases.json\";\n\n/**\n * Proxy GitHub and rely on Next.js cache to prevent rate limiting\n */\nexport default async function handler(\n  req: NextApiRequest,\n  res: NextApiResponse\n) {\n  res.setHeader(\"Access-Control-Allow-Credentials\", \"true\");\n  res.setHeader(\"Access-Control-Allow-Origin\", \"*\");\n  res.setHeader(\"Access-Control-Allow-Methods\", \"GET\");\n  res.setHeader(\n    \"Access-Control-Allow-Headers\",\n    \"X-CSRF-Token, X-Requested-With, Accept, Accept-Version, Content-Length, Content-MD5, Content-Type, Date, X-Api-Version\"\n  );\n\n  res.status(200).json({ releases });\n}\n"
  },
  {
    "path": "apps/docs/pages/docs/_meta.js",
    "content": "const menu = {\n  index: {\n    title: \"Introduction\",\n  },\n  \"getting-started\": {\n    title: \"Getting Started\",\n  },\n  \"integrating-puck\": {\n    title: \"Integrating Puck\",\n  },\n  \"extending-puck\": {\n    title: \"Extending Puck\",\n  },\n  \"api-reference\": {\n    title: \"API Reference\",\n  },\n  guides: {\n    title: \"Guides\",\n  },\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/_meta.js",
    "content": "const menu = {\n  components: {},\n  configuration: {},\n  \"data-model\": {},\n  fields: {},\n  functions: {},\n  overrides: {},\n  plugins: { title: \"Plugins\" },\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/actions.mdx",
    "content": "# Actions\n\nActions enable you to make changes to the internal [AppState](/docs/api-reference/data-model/app-state) via the [dispatcher](/docs/api-reference/puck-api#dispatch).\n\nThis is a partial reference of the most useful actions. A full reference is available in [the codebase](https://github.com/puckeditor/puck/blob/main/packages/core/reducer/actions.tsx).\n\n## Types\n\n### `setData`\n\nModify the [data payload](/docs/api-reference/data-model/data) currently managed by Puck.\n\n| Param  | Example           | Type                                        | Status   |\n| ------ | ----------------- | ------------------------------------------- | -------- |\n| `type` | `type: \"setData\"` | \"setData\"                                   | Required |\n| `data` | `data: {}`        | [Data](/docs/api-reference/data-model/data) | Required |\n\n#### Example\n\n```tsx\ndispatch({\n  type: \"setData\",\n  data: {},\n});\n```\n\n### `setUi`\n\nChange a value on Puck's [UI state](/docs/api-reference/data-model/app-state#ui).\n\n| Param  | Example                             | Type                                         | Status   |\n| ------ | ----------------------------------- | -------------------------------------------- | -------- |\n| `type` | `type: \"setUi\"`                     | \"setUi\"                                      | Required |\n| `ui`   | `ui: { leftSideBarVisible: false }` | [UiState](/docs/api-reference/app-state/#ui) | Required |\n\n#### Example\n\n```tsx\ndispatch({\n  type: \"setUi\",\n  ui: {\n    leftSideBarVisible: false,\n  },\n});\n```\n\n### `set`\n\nChange the entire [AppState](/docs/api-reference/data-model/app-state) in a single action.\n\n| Param   | Example                       | Type                                                 | Status   |\n| ------- | ----------------------------- | ---------------------------------------------------- | -------- |\n| `type`  | `type: \"set\"`                 | \"set\"                                                | Required |\n| `state` | `state: { data: {}, ui: {} }` | [AppState](/docs/api-reference/data-model/app-state) | Required |\n\n#### Example\n\n```tsx\ndispatch({\n  type: \"set\",\n  state: { data: {}, ui: {} },\n});\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/_meta.js",
    "content": "const menu = {\n  \"action-bar\": {},\n  \"action-bar-action\": {},\n  \"action-bar-group\": {},\n  \"action-bar-label\": {},\n  \"action-bar-separator\": {},\n  \"auto-field\": {},\n  drawer: {},\n  \"drawer-item\": {},\n  \"drop-zone\": {},\n  \"field-label\": {},\n  puck: {},\n  \"puck-components\": {},\n  \"puck-fields\": {},\n  \"puck-layout\": {},\n  \"puck-outline\": {},\n  \"puck-preview\": {},\n  render: {},\n  \"rich-text-menu\": {},\n  \"rich-text-menu-group\": {},\n  \"rich-text-menu-control\": {},\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/action-bar-action.mdx",
    "content": "---\ntitle: <ActionBar.Action>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { ActionBar } from \"@/puck\";\n\n# \\<ActionBar.Action\\>\n\nRender an action button in the [`<ActionBar>`](action-bar). Often used inside an [`<ActionBar.Group>`](action-bar-group).\n\n```tsx showLineNumbers {2} copy\n<ActionBar>\n  <ActionBar.Action onClick={() => console.log(\"Clicked!\")}>★</ActionBar.Action>\n</ActionBar>\n```\n\n<PuckPreview>\n  <div style={{ display: \"flex\" }}>\n    <ActionBar>\n      <ActionBar.Action onClick={() => console.log(\"Clicked!\")}>\n        ★\n      </ActionBar.Action>\n    </ActionBar>\n  </div>\n</PuckPreview>\n\n## Props\n\n| Prop                    | Example      | Type      | Status   |\n| ----------------------- | ------------ | --------- | -------- |\n| [`children`](#children) | `<svg />`    | ReactNode | Required |\n| [`onClick`](#on-clicke) | `() => void` | Function  | -        |\n| [`label`](#label)       | `\"Label\"`    | String    | -        |\n\n## Required Props\n\n### `children`\n\nA node to render as the children of the action. Should be a string or an icon.\n\nPuck uses [Lucide icons](https://lucide.dev/icons/). You can use [lucide-react](https://lucide.dev/guide/packages/lucide-react) to choose a similar icon, if desired.\n\n### `onClick(e)`\n\nAn [onClick callback](https://react.dev/learn/responding-to-events) triggered when the user clicks the action.\n\n## Optional Props\n\n### `label`\n\nA label to provide an accessible label when using icon.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/action-bar-group.mdx",
    "content": "---\ntitle: <ActionBar.Group>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { ActionBar } from \"@/puck\";\n\n# \\<ActionBar.Group\\>\n\nRender an action group in the [`<ActionBar>`](action-bar). Use [`<ActionBar.Separator>`](action-bar-separator) instead to avoid wrapping elements.\n\n```tsx showLineNumbers {2-3} copy\n<ActionBar label=\"Actions\">\n  <ActionBar.Group>Group 1</ActionBar.Group>\n  <ActionBar.Group>Group 2</ActionBar.Group>\n</ActionBar>\n```\n\n<PuckPreview>\n  <div style={{ display: \"flex\" }}>\n    <ActionBar label=\"Actions\">\n      <ActionBar.Group>Group 1</ActionBar.Group>\n      <ActionBar.Group>Group 2</ActionBar.Group>\n    </ActionBar>\n  </div>\n</PuckPreview>\n\n## Props\n\n| Prop                    | Example   | Type      | Status   |\n| ----------------------- | --------- | --------- | -------- |\n| [`children`](#children) | `<div />` | ReactNode | Required |\n\n## Required Props\n\n### `children`\n\nA node to render as the children of the action. If a fragment, the items will be rendered in a flex row.\n\nNormally contains [`<ActionBar.Action>`](action-bar-action)\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/action-bar-label.mdx",
    "content": "---\ntitle: <ActionBar.Label>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { ActionBar } from \"@/puck\";\n\n# \\<ActionBar.Label\\>\n\nRender a label in the [`<ActionBar>`](action-bar) or an [`<ActionBar.Group>`](action-bar-group).\n\n```tsx showLineNumbers {2,4} copy\n<ActionBar>\n  <ActionBar.Label label=\"Label 1\" />\n  <ActionBar.Group>\n    <ActionBar.Label label=\"Label 2\" />\n    <ActionBar.Action>★</ActionBar.Action>\n  </ActionBar.Group>\n</ActionBar>\n```\n\n<PuckPreview>\n  <div style={{ display: \"flex\" }}>\n    <ActionBar>\n      <ActionBar.Label label=\"Label 1\" />\n      <ActionBar.Group>\n        <ActionBar.Label label=\"Label 2\" />\n        <ActionBar.Action>★</ActionBar.Action>\n      </ActionBar.Group>\n    </ActionBar>\n  </div>\n</PuckPreview>\n\n## Props\n\n| Prop              | Example   | Type   | Status   |\n| ----------------- | --------- | ------ | -------- |\n| [`label`](#label) | `\"Label\"` | String | Required |\n\n## Required Props\n\n### `label`\n\nThe label text.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/action-bar-separator.mdx",
    "content": "---\ntitle: <ActionBar.Separator>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { ActionBar } from \"@/puck\";\n\n# \\<ActionBar.Separator\\>\n\nRender a separator in the [`<ActionBar>`](action-bar). An alternative to [`<ActionBar.Group>`](action-bar-group) that avoids wrapping elements.\n\n```tsx showLineNumbers {3} copy\n<ActionBar>\n  <ActionBar.Label label=\"Label 1\" />\n  <ActionBar.Separator />\n  <ActionBar.Label label=\"Label 2\" />\n  <ActionBar.Action>★</ActionBar.Action>\n</ActionBar>\n```\n\n<PuckPreview>\n  <div style={{ display: \"flex\" }}>\n    <ActionBar>\n      <ActionBar.Label label=\"Label 1\" />\n      <ActionBar.Separator />\n      <ActionBar.Label label=\"Label 2\" />\n      <ActionBar.Action>★</ActionBar.Action>\n    </ActionBar>\n  </div>\n</PuckPreview>\n\n## Props\n\nThis component doesn't accept any props.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/action-bar.mdx",
    "content": "---\ntitle: <ActionBar>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { ActionBar } from \"@/puck\";\n\n# \\<ActionBar\\>\n\nRender the Puck ActionBar. Use this when overriding the [actionBar](/docs/api-reference/overrides/action-bar).\n\n```tsx showLineNumbers copy\n<ActionBar label=\"Actions\">\n  <ActionBar.Group>\n    <ActionBar.Action onClick={() => console.log(\"Clicked!\")}>\n      ★\n    </ActionBar.Action>\n  </ActionBar.Group>\n</ActionBar>\n```\n\n<PuckPreview>\n  <div style={{ display: \"flex\" }}>\n    <ActionBar label=\"Actions\">\n      <ActionBar.Group>\n        <ActionBar.Action>★</ActionBar.Action>\n      </ActionBar.Group>\n    </ActionBar>\n  </div>\n</PuckPreview>\n\n## Props\n\n| Prop                    | Example   | Type      | Status   |\n| ----------------------- | --------- | --------- | -------- |\n| [`children`](#children) | `<div />` | ReactNode | Required |\n| [`label`](#label)       | `\"Label\"` | String    | Required |\n\n## Required Props\n\n### `children`\n\nThe children for the ActionBar. Normally a fragment of [`<ActionBar.Action>` components](/docs/api-reference/components/action-bar-action).\n\nIf this is a fragment, it will be rendered in a flex row.\n\n### `label`\n\nThe label for the ActionBar.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/auto-field.mdx",
    "content": "---\ntitle: <AutoField>\n---\n\nimport { ConfigPreview } from \"@/docs/components/Preview\";\nimport { AutoField } from \"@/puck\";\n\n# \\<AutoField\\>\n\nRender a Puck field based on a [Field](/docs/api-reference/fields) object. Use this when building [custom fields](/docs/extending-puck/custom-fields) that need to use Puck-style fields internally.\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        render: ({ onChange, value }) => {\n          return (\n            <AutoField\n              field={{ type: \"text\" }}\n              onChange={onChange}\n              value={value}\n            />\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n```tsx {1,4} copy\nimport { Autofield } from \"@puckeditor/core\";\n\nconst CustomField = ({ onChange, value }) => (\n  <AutoField field={{ type: \"text\" }} onChange={onChange} value={value} />\n);\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: MyCustomField,\n        },\n      },\n    },\n  },\n};\n```\n\n## Props\n\n| Prop                    | Example                      | Type                                | Status   |\n| ----------------------- | ---------------------------- | ----------------------------------- | -------- |\n| [`field`](#field)       | `{ type: \"text\" }`           | [Field](/docs/api-reference/fields) | Required |\n| [`onChange`](#onchange) | `onChange(\"Goodbye, world\")` | Function                            | Required |\n| [`value`](#value)       | `\"Hello, world\"`             | Any                                 | Required |\n| [`id`](#id)             | `\"my-input\"`                 | String                              | -        |\n| [`readOnly`](#readonly) | `true`                       | Boolean                             | -        |\n\n## Required Props\n\n### `field`\n\nAn object containing the user defined [Field](/docs/api-reference/fields) configuration.\n\n### `onChange`\n\nA callback that triggers when the value changes.\n\n### `value`\n\nThe current value for the field.\n\n## Optional Props\n\n### `id`\n\nAn optional ID for this field. Will be generated if not specified.\n\n### `readOnly`\n\nA boolean describing whether or not this field is `readOnly`.\n\n## Further reading\n\n- [Custom fields](/docs/extending-puck/custom-fields)\n- [The `<FieldLabel>` API reference](/docs/api-reference/components/field-label)\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/drawer-item.mdx",
    "content": "---\ntitle: <Drawer.Item>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Drawer } from \"@/puck\";\n\n# \\<Drawer.Item\\>\n\nAn item that can be dragged from a [`<Drawer>`](drawer).\n\n```tsx {7} copy\nimport { Puck, Drawer } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Drawer>\n        <Drawer.Item name=\"Orange\" />\n      </Drawer>\n    </Puck>\n  );\n}\n```\n\n## Props\n\n| Prop                                | Example                   | Type     | Status   |\n| ----------------------------------- | ------------------------- | -------- | -------- |\n| [`name`](#name)                     | `name: \"Orange\"`          | String   | Required |\n| [`children`](#children)             | `children: () => <div />` | Function | -        |\n| [`id`](#id)                         | `id: \"OrangeComponent\"`   | String   | -        |\n| [`isDragDisabled`](#isdragdisabled) | `isDragDisabled: false`   | Boolean  | -        |\n\n## Required props\n\n### `name`\n\nThe name of this drawer item.\n\n- This will be rendered on the item by default.\n- Will be used as the `id`, unless otherwise specified\n\n## Optional props\n\n### `children`\n\nA custom render function to render inside the component.\n\n```tsx {8} copy\nimport { Puck, Drawer } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Drawer>\n        <Drawer.Item name=\"Orange\">{() => <div>Orange 🍊</div>}</Drawer.Item>\n      </Drawer>\n    </Puck>\n  );\n}\n```\n\n<PuckPreview config={{}} data={{ root: { props: {} }, content: [] }}>\n  <Drawer>\n    <Drawer.Item name=\"Orange\">{() => <div>Orange 🍊</div>}</Drawer.Item>\n  </Drawer>\n</PuckPreview>\n\n#### Render Props\n\n| Prop                      | Example             | Type   |\n| ------------------------- | ------------------- | ------ |\n| [`children`](#children-1) | `children: <div />` | String |\n\n##### `children`\n\nThe original node for the drawer item.\n\n### `id`\n\nA unique id for this drawer item. Defaults to the value of [`name`](#name).\n\nIf using the `<Drawer>` as a component list to be dragged into `<Puck.Preview>`, this should be the key of a component defined in the [Config](/docs/api-reference/configuration/config).\n\n### `isDragDisabled`\n\nWhether or not this item is disabled.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/drawer.mdx",
    "content": "---\ntitle: <Drawer>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck, Drawer } from \"@/puck\";\n\n# \\<Drawer\\>\n\nA list of items that can be dragged into a [`<Puck.Preview>`](puck-preview). Used for composing custom Puck UIs.\n\n<PuckPreview\n  config={{ components: { Orange: { render: () => <div>Orange</div> } } }}\n  data={{ root: { props: {} }, content: [] }}\n>\n  <Drawer>\n    <Drawer.Item name=\"Orange\" />\n  </Drawer>\n</PuckPreview>\n\n```tsx {6-8} /Drawer/1 copy\nimport { Puck, Drawer } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Drawer>\n        <Drawer.Item name=\"Orange\" />\n      </Drawer>\n    </Puck>\n  );\n}\n```\n\n## Props\n\n| Param                   | Example                     | Type      | Status   |\n| ----------------------- | --------------------------- | --------- | -------- |\n| [`children`](#children) | `children: <Drawer.Item />` | ReactNode | Required |\n\n## Required props\n\n### `children`\n\nA React node representing the contents of the `<Drawer>`. Will likely contain [`<Drawer.Item>`](drawer-item) nodes.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/drop-zone.mdx",
    "content": "---\ntitle: <DropZone>\n---\n\nimport { Callout } from \"nextra/components\";\n\n# \\<DropZone\\>\n\n<Callout>\nThe [`<DropZone>` component](/docs/api-reference/components/drop-zone) component is being replaced by the [`slot` field](/docs/api-reference/fields/slot), and will soon be deprecated and removed. For migration notes, see [these docs](/docs/guides/migrations/dropzones-to-slots).\n</Callout>\n\nPlace droppable regions (zones) inside other components to enable nested components.\n\n```tsx {1,9} copy\nimport { DropZone } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n## Props\n\n| Param                               | Example                      | Type                                 | Status   |\n| ----------------------------------- | ---------------------------- | ------------------------------------ | -------- |\n| [`zone`](#zone)                     | `zone: \"my-zone\"`            | String                               | Required |\n| [`allow`](#allow)                   | `allow: [\"HeadingBlock\"]`    | Array                                | -        |\n| [`className`](#classname)           | `className: \"MyClass\"`       | String                               | -        |\n| [`collisionAxis`](#collisionaxis)   | `collisionAxis: \"x\"`         | String                               | -        |\n| [`disallow`](#disallow)             | `disallow: [\"HeadingBlock\"]` | Array                                | -        |\n| [`minEmptyHeight`](#minemptyheight) | `minEmptyHeight: \"256px\"`    | CSSProperties[\"minHeight\"] \\| number | -        |\n| [`ref`](#ref)                       | `ref: ref`                   | Ref                                  | -        |\n| [`style`](#style)                   | `style: {display: \"flex\"}`   | CSSProperties                        | -        |\n\n## Required props\n\n### `zone`\n\nSet the zone identifier for the given DropZone.\n\nMust be unique within this component, but two different components can both define DropZones with the same `zone` value.\n\n```tsx /zone=\"my-content\"/ copy\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n## Optional props\n\n### `allow`\n\nOnly allow specific components to be dragged into the DropZone:\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" allow={[\"HeadingBlock\"]} />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n### `className`\n\nProvide a className to the DropZone component. The default DropZone styles will still be applied.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" className=\"MyComponent\" />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n### `collisionAxis`\n\nConfigure which axis Puck will use for overlap collision detection.\n\nOptions:\n\n- `x` - detect collisions based their x-axis overlap\n- `y` - detect collisions based their y-axis overlap\n- `dynamic` - automatically choose an axis based on the direction of travel\n\nThe defaults are set based on the CSS layout of the parent:\n\n- grid: `dynamic`\n- flex (row): `x`\n- inline/inline-block: `x`\n- Everything else: `y`\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" collisionAxis=\"dynamic\" />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n### `disallow`\n\nAllow all but specific components to be dragged into the DropZone. Any items in `allow` will override `disallow`.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" disallow={[\"HeadingBlock\"]} />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n### `minEmptyHeight`\n\nThe minimum height of the DropZone when empty. Defaults to `128px`.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" minEmptyHeight=\"256px\" />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n### `ref`\n\nA [React ref](https://react.dev/learn/manipulating-the-dom-with-refs), assigned to the root node of the DropZone.\n\n```tsx copy {9}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        const ref = useRef();\n\n        return (\n          <div>\n            <DropZone zone=\"my-content\" ref={ref} />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n### `style`\n\nProvide a style attribute to the DropZone. The default DropZone styles will still be applied.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return (\n          <div>\n            <DropZone zone=\"my-content\" style={{ display: \"flex\" }} />\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\n## React server components\n\nBy default, DropZones don't work with React server components as they rely on context.\n\nInstead, you can use the [`renderDropZone` method](/docs/api-reference/configuration/component-config#propspuckrenderdropzone) passed to your component render function.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/field-label.mdx",
    "content": "---\ntitle: <FieldLabel>\n---\n\nimport { ConfigPreview } from \"../../../../components/Preview\";\nimport { FieldLabel } from \"@/puck\";\nimport { Globe } from \"lucide-react\";\n\n# \\<FieldLabel\\>\n\nRender a styled `label` when creating [`custom` fields](/docs/api-reference/fields/custom).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        render: () => {\n          return (\n            <FieldLabel label=\"Title\">\n              <input\n                style={{ background: \"white\", border: \"1px solid black\" }}\n              />\n            </FieldLabel>\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n  }}\n/>\n\n```tsx {1,4-6} copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst CustomField = () => (\n  <FieldLabel label=\"Title\">\n    <input />\n  </FieldLabel>\n);\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: MyCustomField,\n        },\n      },\n    },\n  },\n};\n```\n\n## Props\n\n| Param                     | Example                | Type             | Status   |\n| ------------------------- | ---------------------- | ---------------- | -------- |\n| [`label`](#label)         | `label: \"Title\"`       | String           | Required |\n| [`children`](#children)   | `children: <div />`    | ReactNode        | -        |\n| [`className`](#classname) | `className: \"MyLabel\"` | String           | -        |\n| [`el`](#el)               | `el: false`            | \"label\" \\| \"div\" | -        |\n| [`icon`](#icon)           | `icon: <svg />`        | ReactNode        | -        |\n| [`readOnly`](#readonly)   | `readOnly: false`      | Boolean          | -        |\n\n## Required props\n\n### `label`\n\nThe label string for the fields.\n\n```tsx /label=\"Title\"/ copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst CustomField = () => (\n  <FieldLabel label=\"Title\">\n    <input />\n  </FieldLabel>\n);\n\n// ...\n```\n\n## Optional props\n\n### `children`\n\nA node to render inside the FieldLabel's internal `<label>` element. You can also define your input element as a sibling.\n\n```tsx {5} copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst CustomField = () => (\n  <FieldLabel label=\"Title\">\n    <input />\n  </FieldLabel>\n);\n\n// ...\n```\n\n### `className`\n\nDefine a custom class for the field label.\n\n```tsx /className=\"MyClass\"/ copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst CustomField = () => (\n  <FieldLabel className=\"MyClass\" label=\"Title\">\n    <input />\n  </FieldLabel>\n);\n\n// ...\n```\n\n### `el`\n\nSpecify whether to render a `label` or `div`. **Defaults to `\"label\"`**.\n\n```tsx /el=\"div\"/ copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst CustomField = () => (\n  <FieldLabel el=\"div\" label=\"Title\">\n    <input />\n  </FieldLabel>\n);\n\n// ...\n```\n\n### `icon`\n\nRender an icon before the label text. Puck uses [lucide-react](https://lucide.dev/guide/packages/lucide-react) internally.\n\n```tsx /icon={<Globe size=\"16\" />}/ copy\nimport { FieldLabel } from \"@puckeditor/core\";\nimport { Globe } from \"lucide-react\";\n\nconst CustomField = () => (\n  <FieldLabel icon={<Globe size=\"16\" />} label=\"Title\">\n    <input />\n  </FieldLabel>\n);\n\n// ...\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        render: () => {\n          return (\n            <FieldLabel label=\"Title\" icon={<Globe size=\"16\" />}>\n              <input style={{ border: \"1px solid black\" }} />\n            </FieldLabel>\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n  }}\n/>\n\n### `readOnly`\n\nIndicate to the user that this field is in a read-only state by showing a padlock icon to the right of the text.\n\n```tsx /readOnly/1 copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst CustomField = () => (\n  <FieldLabel label=\"Title\" readOnly>\n    <input readOnly />\n  </FieldLabel>\n);\n\n// ...\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        render: () => {\n          return (\n            <div style={{ maxWidth: \"max-content\" }}>\n              <FieldLabel label=\"Title\" readOnly>\n                <input style={{ border: \"1px solid black\" }} readOnly />\n              </FieldLabel>\n            </div>\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n  }}\n/>\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/puck-components.mdx",
    "content": "---\ntitle: <Puck.Components>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# \\<Puck.Components\\>\n\nRender a draggable list of components based on the [user-defined components](/docs/api-reference/configuration/config#components) when composing a custom Puck UI. Respects the [`categories` API](/docs/api-reference/configuration/config#categories).\n\n<PuckPreview\n  config={{\n    components: {\n      HeadingBlock: {},\n      ParagraphBlock: {},\n    },\n  }}\n  data={{ root: { props: {} }, content: [] }}\n>\n  <Puck.Components />\n</PuckPreview>\n\n```tsx {} showLineNumbers copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Puck.Components />\n    </Puck>\n  );\n}\n```\n\n## Props\n\nThis component doesn't accept any props.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/puck-fields.mdx",
    "content": "---\ntitle: <Puck.Fields>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# \\<Puck.Fields\\>\n\nRender the fields for the currently selected item in [`<Puck.Preview>`](/docs/api-reference/components/puck-preview) when composing a custom Puck UI.\n\n<PuckPreview\n  config={{\n    components: {\n      HeadingBlock: {},\n    },\n  }}\n  data={{ root: { props: {} }, content: [] }}\n  style={{ padding: 4 }}\n>\n  <Puck.Fields />\n</PuckPreview>\n\n```tsx {14} showLineNumbers copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Puck.Fields />\n    </Puck>\n  );\n}\n```\n\n## Props\n\n| Param                       | Example             | Type    | Status |\n| --------------------------- | ------------------- | ------- | ------ |\n| [`wrapFields`](#wrapfields) | `wrapFields: false` | boolean | -      |\n\n## Optional props\n\n### `wrapFields`\n\nWhether or not the top-level fields should be padded and separated by a a border. Defaults to `true`.\n\n```tsx {6} copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Puck.Fields wrapFields={false} />\n    </Puck>\n  );\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/puck-layout.mdx",
    "content": "---\ntitle: <Puck.Layout>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# \\<Puck.Layout\\>\n\nRender the standard Puck layout. This is useful for implementing behavior using the [internal Puck API](/docs/extending-puck/internal-puck-api) without changing the default UI.\n\n```tsx {13} showLineNumbers copy\nimport { Puck } from \"@puckeditor/core\";\n\nconst Example = ({ children }) => {\n  useEffect(() => console.log(\"Hello, world\"), []);\n\n  return <>{children}</>;\n};\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Example>\n        <Puck.Layout />\n      </Example>\n    </Puck>\n  );\n}\n```\n\n## Props\n\nThis component doesn't accept any props.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/puck-outline.mdx",
    "content": "---\ntitle: <Puck.Outline>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# \\<Puck.Outline\\>\n\nRender an interactive outline of the current data payload when composing a custom Puck UI.\n\n<PuckPreview\n  config={{\n    components: {\n      HeadingBlock: { render: () => <div>Hello world</div> },\n      ParagraphBlock: {},\n    },\n  }}\n  data={{\n    root: { props: {} },\n    content: [{ type: \"HeadingBlock\", props: { id: \"HeadingBlock-123\" } }],\n  }}\n>\n  <Puck.Outline />\n  {/* Outline doesn't render unless Preview is rendered due to zone flushing */}\n  <div style={{ display: \"none\" }}>\n    <Puck.Preview />\n  </div>\n</PuckPreview>\n\n```tsx {12} showLineNumbers copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Puck.Outline />\n    </Puck>\n  );\n}\n```\n\n## Props\n\nThis component doesn't accept any props.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/puck-preview.mdx",
    "content": "---\ntitle: <Puck.Preview>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# \\<Puck.Preview\\>\n\nRender a drag-and-drop preview for the current data when composing a custom Puck UI.\n\n<PuckPreview\n  config={{\n    components: {\n      HeadingBlock: {\n        fields: {title: {type: 'text'}},\n        render: ({title}) => <div>{title}</div>\n      }\n    }\n  }}\n  data={{\n    root: { props: {} },\n    content: [{ type: \"HeadingBlock\", props: { id: \"HeadingBlock-123\", title: 'Hello, world' } }],\n  }}\n>\n\n  <Puck.Preview />\n</PuckPreview>\n\n```tsx {17} showLineNumbers copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Puck.Preview />\n    </Puck>\n  );\n}\n```\n\n## Props\n\n| Param       | Example                    | Type   | Status |\n| ----------- | -------------------------- | ------ | ------ |\n| [`id`](#id) | `id: \"my-preview-content\"` | String | -      |\n\n## Optional props\n\n### `id`\n\nA unique identifier for the preview frame. Default: `puck-preview`.\n\n```tsx {6} copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <Puck.Preview id=\"my-frame\" />\n    </Puck>\n  );\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/puck.mdx",
    "content": "---\ntitle: <Puck>\n---\n\n# \\<Puck\\>\n\nRender the Puck editor.\n\n```tsx copy\nimport { Puck } from \"@puckeditor/core\";\n\nconst config = {\n  components: {},\n};\n\nconst initialData = {\n  content: [],\n  root: {},\n};\n\nexport function Editor() {\n  return <Puck config={config} data={initialData} />;\n}\n```\n\n## Props\n\n| Param                                                             | Example                                            | Type                                                                     | Status       |\n| ----------------------------------------------------------------- | -------------------------------------------------- | ------------------------------------------------------------------------ | ------------ |\n| [`config`](#config)                                               | `config: { components: {} }`                       | [Config](/docs/api-reference/configuration/config)                       | Required     |\n| [`data`](#data)                                                   | `data: {}`                                         | [Data](/docs/api-reference/data-model/data)                              | Required     |\n| [`dnd`](#dnd)                                                     | `dnd: {}`                                          | [DndConfig](#dnd-params)                                                 | -            |\n| [`children`](#children)                                           | `children: <Puck.Preview />`                       | ReactNode                                                                | -            |\n| [`fieldTransforms`](#fieldtransforms)                             | `fieldTransforms: {text: () => <div />}`           | [FieldTransforms](/docs/api-reference/field-transforms)                  | -            |\n| [`headerPath`](#headerpath)                                       | `headerPath: \"/my-page\"`                           | String                                                                   | -            |\n| [`headerTitle`](#headertitle)                                     | `headerTitle: \"My Page\"`                           | String                                                                   | -            |\n| [`height`](#height)                                               | `height: \"100%\"`                                   | String \\| Number                                                         | -            |\n| [`iframe`](#iframe)                                               | `iframe: {}`                                       | [IframeConfig](#iframe-params)                                           | -            |\n| [`initialHistory`](#initialhistory)                               | `initialHistory: {}`                               | [InitialHistory](#initialhistory-params)                                 | -            |\n| [`metadata`](#metadata)                                           | `metadata: {}`                                     | Object                                                                   | -            |\n| [`onAction()`](#onactionaction-appstate-prevappstate)             | `onAction: (action, appState, prevAppState) => {}` | Function                                                                 | -            |\n| [`onChange()`](#onchangedata)                                     | `onChange: (data) => {}`                           | Function                                                                 | -            |\n| [`onPublish()`](#onpublishdata)                                   | `onPublish: async (data) => {}`                    | Function                                                                 | -            |\n| [`overrides`](#overrides)                                         | `overrides: { header: () => <div /> }`             | [Overrides](/docs/api-reference/overrides)                               | Experimental |\n| [`permissions`](#permissions)                                     | `permissions: {}`                                  | [Permissions\\[\\]](/docs/api-reference/permissions#supported-permissions) | -            |\n| [`plugins`](#plugins)                                             | `plugins: [myPlugin]`                              | [Plugin\\[\\]](/docs/api-reference/plugin)                                 | Experimental |\n| [`ui`](#ui)                                                       | `ui: {leftSideBarVisible: false}`                  | [AppState.ui](/docs/api-reference/data-model/app-state#ui)               | -            |\n| [`viewports`](#viewports)                                         | `viewports: [{ width: 1440 }]`                     | [Viewport\\[\\]](#viewport-params)                                         | -            |\n| [`_experimentalFullScreenCanvas`](#_experimentalfullscreencanvas) | `_experimentalFullScreenCanvas: true`              | Boolean                                                                  | Experimental |\n\n## Required props\n\n### `config`\n\nAn object describing the available components, fields and more. See the [`Config` docs](/docs/api-reference/configuration/config) for a full reference.\n\n```tsx {4-17} copy\nexport function Editor() {\n  return (\n    <Puck\n      config={{\n        components: {\n          HeadingBlock: {\n            fields: {\n              children: {\n                type: \"text\",\n              },\n            },\n            render: ({ children }) => {\n              return <h1>{children}</h1>;\n            },\n          },\n        },\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `data`\n\nThe initial data to render. Cannot be changed once `<Puck>` has been mounted. See the [`Data` docs](/docs/api-reference/data-model/data) for a full reference.\n\n```tsx {4-12} copy\nexport function Editor() {\n  return (\n    <Puck\n      data={{\n        content: [\n          {\n            props: { children: \"Hello, world\", id: \"id\" },\n            type: \"HeadingBlock\",\n          },\n        ],\n        root: {},\n      }}\n      // ...\n    />\n  );\n}\n```\n\n## Optional props\n\n### `children`\n\nRender custom nodes to create [compositional interfaces](/docs/extending-puck/composition).\n\n```tsx {4} copy\nexport function Editor() {\n  return (\n    <Puck /*...*/>\n      <Puck.Preview />\n    </Puck>\n  );\n}\n```\n\n### `dnd`\n\nConfigure drag-and-drop behavior.\n\n#### dnd params\n\n| Param                                     | Example                   | Type    | Status |\n| ----------------------------------------- | ------------------------- | ------- | ------ |\n| [`disableAutoScroll`](#disableautoscroll) | `disableAutoScroll: true` | boolean | -      |\n\n##### `disableAutoScroll`\n\nDisable auto-scroll when the user drags an item near the edge of the preview area.\n\n### `fieldTransforms`\n\nSpecify transforms to modify field values before being passed to the editor canvas. Implements the [Field Transforms API](/docs/api-reference/field-transforms).\n\n```tsx {4} copy\nexport function Editor() {\n  return (\n    <Puck\n      fieldTransforms={{\n        text: ({ value }) => <div>{value}</div>, // Wrap all text field values in a div\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `headerPath`\n\nSet a path to show after the header title\n\n```tsx {4} copy\nexport function Editor() {\n  return (\n    <Puck\n      headerPath=\"/my-page\"\n      // ...\n    />\n  );\n}\n```\n\n### `headerTitle`\n\nSet the title shown in the header\n\n```tsx {4} copy\nexport function Editor() {\n  return (\n    <Puck\n      headerTitle=\"My page\"\n      // ...\n    />\n  );\n}\n```\n\n### `height`\n\nModify the height of the Puck editor when using the standard layout. Defaults to `100dvh`.\n\n```tsx {4} copy\nexport function Editor() {\n  return (\n    <Puck\n      height=\"100%\"\n      // ...\n    />\n  );\n}\n```\n\n### `iframe`\n\nConfigure the iframe behaviour.\n\n```tsx {4} copy\nexport function Editor() {\n  return (\n    <Puck\n      iframe={{ enabled: false }}\n      // ...\n    />\n  );\n}\n```\n\n#### iframe params\n\n| Param                           | Example                | Type    | Status |\n| ------------------------------- | ---------------------- | ------- | ------ |\n| [`enabled`](#enabled)           | `enabled: false`       | boolean | -      |\n| [`waitForStyles`](#deferrender) | `waitForStyles: false` | boolean | -      |\n\n##### `enabled`\n\nRender the Puck preview within iframe. Defaults to `true`.\n\nDisabling iframes will also disable [viewports](#viewports).\n\n##### `waitForStyles`\n\nDefer rendering of the Puck preview until the iframe styles have loaded, showing a spinner. Defaults to `true`.\n\n### `initialHistory`\n\nSets the undo/redo Puck history state when using the `usePuck` [history API](/docs/api-reference/puck-api#history).\n\n```tsx showLineNumbers copy {12-15}\nconst historyState = {\n  data: {\n    root: {\n      props: { title: \"My History\" },\n    },\n  },\n};\n\nexport function Editor() {\n  return (\n    <Puck\n      initialHistory={{\n        histories: [{ state: historyState }],\n        index: 0,\n      }}\n      // ...\n    />\n  );\n}\n```\n\n#### `initialHistory` params\n\n| Param                       | Example             | Type                                                       | Status   |\n| --------------------------- | ------------------- | ---------------------------------------------------------- | -------- |\n| [`histories`](#histories)   | `histories: []`     | [History](/docs/api-reference/puck-api#history-params)\\[\\] | Required |\n| [`index`](#index)           | `index: 2`          | Number                                                     | Required |\n| [`appendData`](#appenddata) | `appendData: false` | Boolean                                                    | -        |\n\n##### `histories`\n\nAn array of histories to reset the Puck state history state to.\n\n##### `index`\n\nThe index of the histories to set the user to.\n\n##### `appendData`\n\nAppend the Puck [`data`](#data) prop onto the end of [`histories`](#histories). Defaults to `true`.\n\nWhen `false`, the Puck `data` prop will be ignored but you must specify at least one item in the `histories` array.\n\n### `onAction(action, appState, prevAppState)`\n\nCallback that triggers when Puck dispatches an [action](https://puckeditor.com/docs/api-reference/actions), like `insert` or `set`. Use this to track changes, perform side effects, or sync with external systems.\n\nReceives three arguments:\n\n1. `action`: The action that was dispatched\n2. `appState`: The new [`AppState`](/docs/api-reference/data-model/app-state) after the action was applied\n3. `prevAppState`: The previous [`AppState`](/docs/api-reference/data-model/app-state) before the action was applied\n\n```tsx {4-8} copy\nexport function Editor() {\n  return (\n    <Puck\n      onAction={(action, appState, prevAppState) => {\n        if (action.type === \"insert\") {\n          console.log(\"New component was inserted\", appState);\n        }\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `metadata`\n\nAn object containing additional data provided to each component's [`render`](/docs/api-reference/configuration/component-config#renderprops) and [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) functions.\n\n```tsx {4,8} copy\nexport function Editor() {\n  return (\n    <Puck\n      metadata={{ title: \"Hello, world\" }}\n      config={{\n        HeadingBlock: {\n          render: ({ puck }) => {\n            return <h1>{puck.metadata.title}</h1>; // \"Hello, world\"\n          },\n        },\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `onChange(data)`\n\nCallback that triggers when the user makes a change.\n\nReceives a single [`Data`](/docs/api-reference/data-model/data) arg.\n\n```tsx {4-6} copy\nexport function Editor() {\n  return (\n    <Puck\n      onChange={(data) => {\n        console.log(\"Puck data was updated\", data);\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `onPublish(data)`\n\nCallback that triggers when the user hits the \"Publish\" button. Use this to save the Puck data to your database.\n\nReceives a single [`Data`](/docs/api-reference/data-model/data) arg.\n\n```tsx {4-9} copy\nexport function Editor() {\n  return (\n    <Puck\n      onPublish={async (data) => {\n        await fetch(\"/my-api\", {\n          method: \"post\",\n          body: JSON.stringify({ data }),\n        });\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `overrides`\n\nAn [`Overrides`](/docs/api-reference/overrides) object defining custom render methods for various parts of the Puck UI.\n\n```tsx {4-6} copy\nexport function Editor() {\n  return (\n    <Puck\n      overrides={{\n        header: () => <div />,\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `permissions`\n\nSet the global [permissions](/docs/api-reference/permissions) for the Puck instance to toggle Puck functionality.\n\n```tsx {4-6} copy\nexport function Editor() {\n  return (\n    <Puck\n      permissions={{\n        delete: false, // Prevent deletion of all components\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `plugins`\n\nAn array of plugins to enhance Puck's behaviour. See the [Plugin API reference](/docs/api-reference/plugin).\n\n```tsx {6} copy\nimport headingAnalyzer from \"@puckeditor/plugin-heading-analyzer\";\n\nexport function Editor() {\n  return (\n    <Puck\n      plugins={[headingAnalyzer]}\n      // ...\n    />\n  );\n}\n```\n\n### `ui`\n\nSet the initial application UI state. See [`AppState.ui`](/docs/api-reference/data-model/app-state#ui).\n\n```tsx {5} copy\nexport function Editor() {\n  return (\n    <Puck\n      // Hide the left side bar by default\n      ui={{ leftSideBarVisible: false }}\n      // ...\n    />\n  );\n}\n```\n\n### `viewports`\n\nConfigure the viewports available to the user, rendered as an iframe. Puck will select the most appropriate initial viewport based on the user's window size, unless otherwise specified via the [`ui`](#ui) prop.\n\n```tsx {4-8} copy\nexport function Editor() {\n  return (\n    <Puck\n      viewports={[\n        {\n          width: 1440,\n        },\n      ]}\n      // ...\n    />\n  );\n}\n```\n\n#### Viewport params\n\n| Param               | Example           | Type                                                     | Status   |\n| ------------------- | ----------------- | -------------------------------------------------------- | -------- |\n| [`width`](#width)   | `width: 1440`     | number \\| `\"100%\"`                                       | Required |\n| [`height`](#height) | `height: 968`     | number \\| `\"auto\"`                                       | -        |\n| [`icon`](#icon)     | `icon: \"Monitor\"` | `\"Smartphone\"` \\| `\"Tablet\"` \\| `\"Monitor\"` \\| ReactNode | -        |\n| [`label`](#label)   | `label: \"iPhone\"` | string                                                   | -        |\n\n##### `width`\n\nThe width of the viewport.\n\n##### `height`\n\nAn optional height for the viewport. Defaults to `auto`, which will fit to the window.\n\n##### `label`\n\nAn optional label for the viewport. This is used for browser tooltip.\n\n##### `icon`\n\nThe icon to show in the viewport switcher. Can be:\n\n- `\"Smartphone\"`\n- `\"Tablet\"`\n- `\"Monitor\"`\n- ReactNode\n\nPuck uses [Lucide icons](https://lucide.dev/icons/). You can use [lucide-react](https://lucide.dev/guide/packages/lucide-react) to choose a similar icon, if desired.\n\n#### Default viewports\n\nBy default, Puck exposes small, medium and large viewports based on common viewport sizes.\n\n```json\n[\n  {\n    \"width\": 360,\n    \"height\": \"auto\",\n    \"icon\": \"Smartphone\",\n    \"label\": \"Small\"\n  },\n  {\n    \"width\": 768,\n    \"height\": \"auto\",\n    \"icon\": \"Tablet\",\n    \"label\": \"Medium\"\n  },\n  {\n    \"width\": 1280,\n    \"height\": \"auto\",\n    \"icon\": \"Monitor\",\n    \"label\": \"Large\"\n  },\n  {\n    \"width\": \"100%\",\n    \"height\": \"auto\",\n    \"icon\": \"FullWidth\",\n    \"label\": \"Full-width\"\n  }\n]\n```\n\n### `_experimentalFullScreenCanvas`\n\nUse a full-screen mode for the Puck canvas, with a floating viewport switcher. **This param is experimental.**\n\n```tsx {4-8} copy\nexport function Editor() {\n  return (\n    <Puck\n      _experimentalFullScreenCanvas\n      // ...\n    />\n  );\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/render.mdx",
    "content": "---\ntitle: <Render>\n---\n\n# \\<Render\\>\n\nRender a [`Data`](/docs/api-reference/data-model/data) object for a given [`Config`](/docs/api-reference/configuration/config).\n\n```tsx copy showLineNumbers\nimport { Render } from \"@puckeditor/core\";\n\nexport function Example() {\n  return <Render config={config} data={data} />;\n}\n```\n\n## Props\n\n| Param                   | Example                      | Type                                        | Status   |\n| ----------------------- | ---------------------------- | ------------------------------------------- | -------- |\n| [`config`](#config)     | `config: { components: {} }` | [Config](/docs/api-reference/config)        | Required |\n| [`data`](#data)         | `data: {}`                   | [Data](/docs/api-reference/data-model/data) | Required |\n| [`metadata`](#metadata) | `metadata: {}`               | [Metadata](/docs/api-reference/metadata)    | -        |\n\n## Required props\n\n### `config`\n\nAn object describing the available components, fields and more. See the [`Config` docs](/docs/api-reference/configuration/config) for a full reference.\n\n```tsx {4-17} copy\nexport function Example() {\n  return (\n    <Render\n      config={{\n        components: {\n          HeadingBlock: {\n            fields: {\n              children: {\n                type: \"text\",\n              },\n            },\n            render: ({ children }) => {\n              return <h1>{children}</h1>;\n            },\n          },\n        },\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `data`\n\nThe data to render against the provided config. See the [`Data` docs](/docs/api-reference/data-model/data) for a full reference.\n\n```tsx {4-12} copy\nexport function Example() {\n  return (\n    <Render\n      data={{\n        content: [\n          {\n            props: { children: \"Hello, world\", id: \"id\" },\n            type: \"HeadingBlock\",\n          },\n        ],\n        root: {},\n      }}\n      // ...\n    />\n  );\n}\n```\n\n### `metadata`\n\nAn object containing additional data provided to each component's [`render`](/docs/api-reference/configuration/component-config#renderprops) and [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) functions.\n\n```tsx {4,8} copy\nexport function Example() {\n  return (\n    <Render\n      metadata={{ title: \"Hello, world\" }}\n      config={{\n        HeadingBlock: {\n          render: ({ puck }) => {\n            return <h1>{puck.metadata.title}</h1>; // \"Hello, world\"\n          },\n        },\n      }}\n      // ...\n    />\n  );\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/rich-text-menu-control.mdx",
    "content": "---\ntitle: <RichTextMenu.Control>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { RichTextMenu } from \"@/puck\";\nimport { Bold, Italic, Underline } from \"lucide-react\";\n\n# \\<RichTextMenu.Control\\>\n\nRender a custom rich text control. Always use inside [`<RichTextMenu.Group>`](rich-text-menu-group). See [included controls](rich-text-menu#included-controls).\n\n```tsx showLineNumbers copy\n<RichTextMenu.Control\n  icon={<Quote />}\n  title=\"Quote\"\n  onClick={() => editor.chain().focus().toggleBlockquote().run()}\n/>\n```\n\n## Control Props\n\n| Prop                    | Example     | Type      | Status   |\n| ----------------------- | ----------- | --------- | -------- |\n| [`icon`](#icon)         | `<Quote />` | ReactNode | Required |\n| [`title`](#title)       | `\"Quote\"`   | string    | Required |\n| [`onClick`](#onclick)   | `() => {}`  | Function  | Required |\n| [`active`](#active)     | `true`      | Boolean   | -        |\n| [`disabled`](#disabled) | `true`      | Boolean   | -        |\n\n## Required props\n\n### icon\n\nThe icon to render for the control, as a React node. Puck uses [lucide-react](https://lucide.dev/guide/packages/lucide-react) internally.\n\n```tsx showLineNumbers copy {2}\n<RichTextMenu.Control\n  icon={<Quote />}\n  title=\"Quote\"\n  onClick={() => editor.chain().focus().toggleBlockquote().run()}\n/>\n```\n\n### title\n\nAn title for the icon control, for accessibility.\n\n```tsx showLineNumbers copy {3}\n<RichTextMenu.Control\n  icon={<Quote />}\n  title=\"Quote\"\n  onClick={() => editor.chain().focus().toggleBlockquote().run()}\n/>\n```\n\n### onClick\n\nA callback to trigger when the control is clicked.\n\n```tsx showLineNumbers copy {4}\n<RichTextMenu.Control\n  icon={<Quote />}\n  title=\"Quote\"\n  onClick={() => editor.chain().focus().toggleBlockquote().run()}\n/>\n```\n\n## Optional props\n\n### active\n\nWhether or not this control is currently active.\n\n```tsx showLineNumbers copy {5}\n<RichTextMenu.Control\n  icon={<Quote />}\n  title=\"Quote\"\n  onClick={() => editor.chain().focus().toggleBlockquote().run()}\n  active={editorState?.isBlockquote}\n/>\n```\n\n### disabled\n\nWhether or not this control is currently disabled.\n\n```tsx showLineNumbers copy {5}\n<RichTextMenu.Control\n  icon={<Quote />}\n  title=\"Quote\"\n  onClick={() => editor.chain().focus().toggleBlockquote().run()}\n  disabled={editorState?.canBlockquote}\n/>\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/rich-text-menu-group.mdx",
    "content": "---\ntitle: <RichTextMenu.Group>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { RichTextMenu } from \"@/puck\";\nimport { Bold, Italic, Underline } from \"lucide-react\";\n\n# \\<RichTextMenu.Group\\>\n\nRender a group of rich text controls. Always use inside `<RichTextMenu>`.\n\n```tsx showLineNumbers copy\n<RichTextMenu.Group>\n  <RichTextMenu.Bold />\n  <RichTextMenu.Italic />\n  <RichTextMenu.Underline />\n</RichTextMenu.Group>\n```\n\n## Props\n\n| Prop                    | Example                 | Type      | Status   |\n| ----------------------- | ----------------------- | --------- | -------- |\n| [`children`](#children) | `<RichTextMenu.Bold />` | ReactNode | Required |\n\n## Required Props\n\n### children\n\nThe controls to render in this group, including:\n\n- [included controls](#included-controls)\n- custom controls using [`<RichTextMenu.Control>`](rich-text-menu-control)\n- additional custom UI\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components/rich-text-menu.mdx",
    "content": "---\ntitle: <RichTextMenu>\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { RichTextMenu } from \"@/puck\";\nimport { Bold, Italic, Underline } from \"lucide-react\";\n\n# \\<RichTextMenu\\>\n\nA menu of rich text controls for the [`richtext`](/docs/api-reference/fields/richtext) field.\n\n```tsx showLineNumbers copy\n<RichTextMenu>\n  <RichTextMenu.Group>\n    <RichTextMenu.Bold />\n    <RichTextMenu.Italic />\n    <RichTextMenu.Underline />\n  </RichTextMenu.Group>\n</RichTextMenu>\n```\n\n<PuckPreview>\n  <div style={{ display: \"flex\" }}>\n    <RichTextMenu>\n      <RichTextMenu.Group>\n        <RichTextMenu.Control icon={<Bold />} />\n        <RichTextMenu.Control icon={<Italic />} />\n        <RichTextMenu.Control icon={<Underline />} />\n      </RichTextMenu.Group>\n    </RichTextMenu>\n  </div>\n</PuckPreview>\n\n## Props\n\n| Prop                    | Example   | Type      | Status   |\n| ----------------------- | --------- | --------- | -------- |\n| [`children`](#children) | `<div />` | ReactNode | Required |\n\n## Required Props\n\n### `children`\n\nA fragment of [`<RichTextMenu.Group>`](/docs/api-reference/components/rich-text-group) components, each containing:\n\n- [included controls](#included-controls),\n- custom controls using [`<RichTextMenu.Control>`](rich-text-menu-control), or\n- additional custom UI\n\n## Included controls\n\n### \\<RichTextMenu.AlignCenter\\>\n\nAlign the text to the center. Configured by the [`textAlign`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.AlignJustify\\>\n\nJustify the text. Configured by the [`textAlign`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.AlignLeft\\>\n\nAlign the text to the left. Configured by the [`textAlign`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.AlignRight\\>\n\nAlign the text to the right. Configured by the [`textAlign`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.AlignSelect\\>\n\nA select dropdown with all text alignment options. Configured by the [`textAlign`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.Blockquote\\>\n\nWrap the text with `<blockquote>`. Configured by the [`blockquote`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.Bold\\>\n\nMark the text as <strong>bold</strong>. Configured by the [`bold`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.BulletList\\>\n\nWrap the text with an unordered list (`<ul>`). Configured by the [`bulletList`](/docs/api-reference/fields/richtext#options) and [`listItem`](/docs/api-reference/fields/richtext#options) options.\n\n### \\<RichTextMenu.CodeBlock\\>\n\nWrap the text with a code block (`<pre>` and `<code>`). Configured by the [`codeBlock`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.HeadingSelect\\>\n\nA select dropdown to set the text as a heading (`h1`, `h2`, `h3`, etc). Configured by the [`heading`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.HorizontalRule\\>\n\nInsert a horizontal rule (`<hr>`). Configured by the [`horizontalRule`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.InlineCode\\>\n\nWrap the text with `<code>`. Configured by the [`code`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.Italic\\>\n\nMark the text as <em>italic</em>. Configured by the [`italic`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.ListSelect\\>\n\nA select dropdown with all list options (`ul`, `ol`). Configured by the [`bulletList`](/docs/api-reference/fields/richtext#options), [`orderedList`](/docs/api-reference/fields/richtext#options) and [`listItem`](/docs/api-reference/fields/richtext#options) options.\n\n### \\<RichTextMenu.OrderedList\\>\n\nWrap the text with an ordered list (`<ol>`). Configured by the [`orderedList`](/docs/api-reference/fields/richtext#options) and [`listItem`](/docs/api-reference/fields/richtext#options) options.\n\n### \\<RichTextMenu.Strikethrough\\>\n\nMark the text as <s>struck-through</s>. Configured by the [`strike`](/docs/api-reference/fields/richtext#options) option.\n\n### \\<RichTextMenu.Underline\\>\n\nMark the text as <span style={{textDecoration: 'underline'}}>underlined</span>. Configured by the [`underline`](/docs/api-reference/fields/richtext#options) option.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/components.mdx",
    "content": "# Components\n\nPuck provides several components to support different integration approaches.\n\n## Core\n\n- [\\<DropZone\\>](components/drop-zone) - Place droppable regions (zones) inside other components to enable nested components.\n- [\\<Puck\\>](components/puck) - Render the Puck editor.\n- [\\<Render\\>](components/render) - Render a [`Data`](/docs/api-reference/data-model/data) object for a given [`Config`](/docs/api-reference/configuration/config).\n\n## Compositional\n\n- [\\<Puck.Components\\>](components/puck-components) - A draggable list of components when composing a custom Puck UI.\n- [\\<Puck.Fields\\>](components/puck-fields) - The fields for the currently selected item when composing a custom Puck UI.\n- [\\<Puck.Layout\\>](components/puck-layout) - The standard Puck layout.\n- [\\<Puck.Outline\\>](components/puck-outline) - An interactive outline when composing a custom Puck UI.\n- [\\<Puck.Preview\\>](components/puck-preview) - A drag-and-drop preview when composing a custom Puck UI.\n\n## Helper\n\n- [\\<ActionBar\\>](components/action-bar) - An action bar containing a series of actions, normally used with the [actionBar override](/docs/api-reference/overrides/action-bar).\n- [\\<ActionBar.Action\\>](components/action-bar-action) - An action for use within the ActionBar component.\n- [\\<ActionBar.Group\\>](components/action-bar-group) - A group of actions for use within the ActionBar component.\n- [\\<ActionBar.Label\\>](components/action-bar-label) - A label for use within the ActionBar component.\n- [\\<ActionBar.Separator\\>](components/action-bar-separator) - A separator to divide elements in the ActionBar component.\n- [\\<Drawer\\>](components/drawer) - A reference list of items that can be dragged into a droppable area, normally [`<Puck.Preview>`](components/puck-preview).\n- [\\<Drawer.Item\\>](components/drawer-item) - An item that can be dragged from a [`<Drawer>`](components/drawer).\n- [\\<FieldLabel\\>](components/field-label) - Render a styled `label` when creating [`custom` fields](/docs/api-reference/fields/custom).\n- [\\<RichTextMenu\\>](components/rich-text-menu) - A menu containing a series of controls for the rich text field.\n- [\\<RichTextMenu.Control\\>](components/rich-text-menu-control) - A control for custom behavior within the RichTextMenu component.\n- [\\<RichTextMenu.Group\\>](components/rich-text-menu-group) - A group of controls for use within the RichTextMenu component.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/configuration/_meta.js",
    "content": "const menu = {\n  config: {},\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/configuration/component-config.mdx",
    "content": "---\ntitle: ComponentConfig\n---\n\nimport { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Drawer } from \"@/puck\";\n\n# ComponentConfig\n\nThe configuration for each component defined in [`Config`](/docs/api-reference/configuration/config).\n\n```tsx {3-10} copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      render: ({ title }) => <h1>{title}</h1>,\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                                    | Example                                         | Type     | Status   |\n| -------------------------------------------------------- | ----------------------------------------------- | -------- | -------- |\n| [`render()`](#renderprops)                               | `render: () => <div />`                         | Function | Required |\n| [`fields`](#fields)                                      | `fields: { title: { type: \"text\"} }`            | Object   | -        |\n| [`defaultProps`](#defaultprops)                          | `defaultProps: { title: \"Hello, world\" }`       | Object   | -        |\n| [`inline`](#inline)                                      | `inline: true`                                  | Boolean  | -        |\n| [`label`](#label)                                        | `label: \"Heading Block\"`                        | String   | -        |\n| [`metadata`](#metadata)                                  | `metadata: {}`                                  | Object   | -        |\n| [`permissions()`](#permissions)                          | `permissions: { delete: false }`                | Object   | -        |\n| [`resolveData()`](#resolvedatadata-params)               | `resolveData: async ({ props }) => ({ props })` | Object   | -        |\n| [`resolveFields()`](#resolvefieldsdata-params)           | `resolveFields: async ({ props }) => ({})`      | Object   | -        |\n| [`resolvePermissions()`](#resolvepermissionsdata-params) | `resolvePermissions: async ({ props }) => ({})` | Object   | -        |\n\n## Required params\n\n### `render(props)`\n\nA render function to render your component. Receives props as defined in `fields`, and some internal Puck props.\n\n```tsx {4} copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      render: () => <h1>Hello, world</h1>,\n    },\n  },\n};\n```\n\n#### Render props\n\n| Arg                                             | Example       | Type     |\n| ----------------------------------------------- | ------------- | -------- |\n| [`id`](#id)                                     | `button-1234` | String   |\n| [`puck.dragRef`](#puckdragref)                  | `null`        | Function |\n| [`puck.isEditing`](#puckisediting)              | `false`       | Boolean  |\n| [`puck.metadata`](/docs/api-reference/metadata) | `{}`          | Object   |\n| [`puck.renderDropZone`](#puckrenderdropzone)    | `() => {}`    | Function |\n| [`...props`](#props)                            | `{}`          | Object   |\n\n##### `id`\n\nA unique identifier for the component. Auto-generated by default.\n\n##### `puck.dragRef`\n\nA `ref` that tells Puck which element is draggable. Apply this to your components when using the [`inline` parameter](#inline) for advanced CSS layouts.\n\n```tsx {5} /renderDropZone/1 copy\nconst config = {\n  components: {\n    Example: {\n      inline: true,\n      render: ({ puck: { dragRef } }) => {\n        return <div ref={dragRef}>Hello, world</div>;\n      },\n    },\n  },\n};\n```\n\n##### `puck.isEditing`\n\nA boolean describing whether or not this component is being rendered in the `<Puck>` component.\n\n##### `puck.metadata`\n\nAn object containing the global metadata provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, merged with any metadata defined in this [component's config](#metadata).\n\n```tsx {5} /renderDropZone/1 copy\nconst config = {\n  components: {\n    Example: {\n      inline: true,\n      render: ({ text, puck: { metadata } }) => {\n        return <div>Hello, {metadata.text || text}</div>;\n      },\n    },\n  },\n};\n```\n\n##### `puck.renderDropZone`\n\nA render method that creates a [`<DropZone>`](/docs/api-reference/components/drop-zone) for creating nested components. Use this method instead of the `<DropZone>` when implementing React server components.\n\n```tsx {5} /renderDropZone/1 copy\nconst config = {\n  components: {\n    Example: {\n      render: ({ puck: { renderDropZone } }) => {\n        return <div>{renderDropZone({ zone: \"my-content\" })}</div>;\n      },\n    },\n  },\n};\n```\n\n##### `...props`\n\nThe remaining props are provided by the user-defined [fields](#fields).\n\n## Optional params\n\n### `fields`\n\nAn object describing which [`Field`](/docs/api-reference/fields) to show for each prop passed to the component.\n\n```tsx {4-8} copy showLineNumbers\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      render: ({ title }) => <h1>{title}</h1>,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n### `defaultProps`\n\nDefault props to apply to a new instance of the component.\n\n```tsx {9} copy showLineNumbers\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      defaultProps: { title: \"Hello, world\" },\n      render: ({ title }) => <h1>{title}</h1>,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n### `inline`\n\nRender your component without a wrapping element. Use this to [create advanced CSS layouts](/docs/integrating-puck/multi-column-layouts#advanced-css-layouts). Defaults to `false`.\n\nWhen `true`, you must to specify which item is draggable via the [`puck.dragRef` prop](#puckdragref).\n\n```tsx {4-5} copy showLineNumbers\nconst config = {\n  components: {\n    HeadingBlock: {\n      inline: true,\n      render: ({ puck }) => <h1 ref={puck.dragRef}>Hello, World</h1>,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    inline: true,\n    render: ({ title, puck }) => {\n      return (\n        <p style={{ margin: 0 }} ref={puck.dragRef}>\n          {title}\n        </p>\n      );\n    },\n  }}\n/>\n\n### `label`\n\nA label to show when referring to your component within the Puck editor. Defaults to the key of your component.\n\n```tsx {4} copy showLineNumbers\nconst config = {\n  components: {\n    HeadingBlock: {\n      label: \"Heading Block\",\n      render: () => <h1>Hello, World</h1>,\n    },\n  },\n};\n```\n\n<PuckPreview config={{}} data={{ root: { props: {} }, content: [] }}>\n  <Drawer>\n    <Drawer.Item name=\"Heading Block\" />\n  </Drawer>\n</PuckPreview>\n\n### `metadata`\n\nAn object containing any additional values. Provided to their [`render`](#renderprops) and [`resolveData`](#resolvedatadata-params) functions.\n\nWill be merged with the global `metadata` prop provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, overriding any keys with the same name.\n\n```tsx {4-7}\nconst config = {\n  components: {\n    HeadingBlock: {\n      metadata: {\n        title: \"Hello, world\",\n      },\n      render: ({ puck }) => <h1>{puck.metadata.title}</h1>,\n    },\n  },\n};\n```\n\n### `permissions`\n\nSet the [permissions](/docs/api-reference/permissions) for all instances of a component to toggle functionality. Inherits [global permissions](/docs/api-reference/components/puck/#permissions).\n\n```tsx {4-6} copy showLineNumbers\nconst config = {\n  components: {\n    HeadingBlock: {\n      permissions: {\n        delete: false, // Disable deletion of all HeadingBlock instances\n      },\n      render: () => <h1>Hello, World</h1>,\n    },\n  },\n};\n```\n\n### `resolveData(data, params)`\n\nDynamically change the props and set fields as read-only. Supports asynchronous calls.\n\nThis function is triggered when [`<Puck>`](/docs/api-reference/components/puck) renders, when a field is changed, when the [`resolveAllData` function](/docs/api-reference/functions/resolve-all-data) is called, or when the component is moved to a different parent.\n\n```tsx {9-14} copy filename=\"Example mapping 'title' to 'resolvedTitle'\"\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      resolveData: async ({ props }) => {\n        return {\n          props: { resolvedTitle: props.title },\n          readOnly: { resolvedTitle: true },\n        };\n      },\n      render: ({ resolvedTitle }) => <h1>{resolvedTitle}</h1>,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label='Try changing the \"title\" field'\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n      resolvedTitle: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    resolveData: ({ props }) => {\n      return {\n        props: { resolvedTitle: props.title },\n        readOnly: { resolvedTitle: true },\n      };\n    },\n    render: ({ resolvedTitle }) => {\n      return <p style={{ margin: 0 }}>{resolvedTitle}</p>;\n    },\n\n}}\n/>\n\n#### Args\n\n| Prop     | Example                                                              | Type   |\n| -------- | -------------------------------------------------------------------- | ------ |\n| `data`   | `{ props: { title: \"Hello, world\" }, readOnly: {} }`                 | Object |\n| `params` | `{ changed: { title: true }, metadata: { foo: \"bar\" }, parent: {} }` | Object |\n\n##### `data.props`\n\nThe current props for the component.\n\n```tsx copy /props/1,3\nconst resolveData = async ({ props }) => {\n  return {\n    props: { resolvedTitle: props.title },\n  };\n};\n```\n\n##### `data.readOnly`\n\nThe fields currently set to read-only for this component.\n\n##### `params.changed`\n\nAn object describing which props have changed on this component since the last time `resolveData` was called.\n\n```tsx copy {2-4} /changed/1 filename=\"Example only updating 'resolvedTitle' when 'title' changes\"\nconst resolveData = async ({ props }, { changed }) => {\n  if (!changed.title) {\n    return { props };\n  }\n\n  return {\n    props: { resolvedTitle: props.title },\n  };\n};\n```\n\n##### `params.lastData`\n\nThe data object from the previous run of this function.\n\n##### `params.metadata`\n\nAn object containing the global metadata provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, merged with any metadata defined in this [component's config](#metadata).\n\n```tsx copy {2-4} /changed/1 filename=\"Example only updating 'resolvedTitle' when 'title' changes\"\nconst resolveData = async ({ props }, { metadata }) => {\n  return {\n    props: { title: metadata.title || props.title },\n  };\n};\n```\n\n##### `params.parent`\n\nThe [component data](/docs/api-reference/data-model/component-data) of the parent.\n\nIf the parent implements [resolveData](#resolvedatadata-params), this parameter will return the unresolved data on initial render, as resolvers are executed bottom-up.\n\n##### `params.trigger`\n\nThe event that triggered this resolveData call. One of:\n\n- `insert` - the component was inserted\n- `replace` - the component was replaced, such as when a field value is changed\n- `move` - the component moved between parents\n- `load` - the Puck editor was loaded\n- `force` - an execution was forced via the [`resolveAllData` function](/docs/api-reference/functions/resolve-all-data)\n\n#### Returns\n\n| Prop   | Example                                              | Type   |\n| ------ | ---------------------------------------------------- | ------ |\n| `data` | `{ props: { title: \"Hello, world\" }, readOnly: {} }` | Object |\n\n##### `data.props`\n\nA partial props object containing modified props. Will be spread into the other props.\n\n```tsx copy {3} filename=\"Example only updating resolvedTitle when title changes\"\nconst resolveData = async ({ props }) => {\n  return {\n    props: { resolvedTitle: props.title },\n  };\n};\n```\n\n##### `data.readOnly`\n\nA partial object describing fields to set as readonly. Will be spread into the existing readOnly state.\n\n```tsx copy {4} filename=\"Example only updating resolvedTitle when title changes\"\nconst resolveData = async ({ props }) => {\n  return {\n    props: { resolvedTitle: props.title },\n    readOnly: { resolvedTitle: true }, // Make the `resolvedTitle` field read-only\n  };\n};\n```\n\n### `resolveFields(data, params)`\n\nDynamically set the fields for this component. Supports asynchronous calls.\n\nThis function is triggered when the component data changes.\n\n```tsx {4-25} copy filename=\"Example changing one field based on another\"\nconst config = {\n  components: {\n    MyComponent: {\n      resolveFields: (data) => {\n        const fields = {\n          drink: {\n            type: \"radio\",\n            options: [\n              { label: \"Water\", value: \"water\" },\n              { label: \"Orange juice\", value: \"orange-juice\" },\n            ],\n          },\n        };\n\n        if (data.props.drink === \"water\") {\n          return {\n            ...fields,\n            waterType: {\n              // ... Define field\n            },\n          };\n        }\n\n        return fields;\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label='Try changing the \"drink\" field'\n  componentConfig={{\n    resolveFields: (data) => {\n      const fields = {\n        drink: {\n          type: \"radio\",\n          options: [\n            { label: \"Water\", value: \"water\" },\n            { label: \"Orange juice\", value: \"orange-juice\" },\n          ],\n        },\n      };\n\n      if (data.props.drink === \"water\") {\n        return {\n          ...fields,\n          waterType: {\n            type: \"radio\",\n            options: [\n              { label: \"Still\", value: \"still\" },\n              { label: \"Sparkling\", value: \"sparkling\" },\n            ],\n          },\n        };\n      }\n\n      return fields;\n    },\n    defaultProps: {\n      drink: \"water\",\n      waterType: \"still\",\n    },\n    render: ({ drink, waterType }) => (\n      <p>\n        {drink}\n        {drink === \"water\" ? ` (${waterType})` : \"\"}\n      </p>\n    ),\n\n}}\n/>\n\n#### Args\n\n| Prop     | Example                                                                               | Type   |\n| -------- | ------------------------------------------------------------------------------------- | ------ |\n| `data`   | `{ props: { title: \"Hello, world\" }, readOnly: {} }`                                  | Object |\n| `params` | `{ appState: {}, changed: {}, fields: {}, lastData: {}, lastFields: {}, parent: {} }` | Object |\n\n##### `data.props`\n\nThe current props for the selected component.\n\n##### `data.readOnly`\n\nThe fields currently set to read-only for this component.\n\n##### `params.appState`\n\nAn object describing the [AppState](/docs/api-reference/data-model/app-state).\n\n##### `params.changed`\n\nAn object describing which props have changed on this component since the last time this function was called.\n\n```tsx copy {2-4} /changed/1 filename=\"Example only updating the fields when 'fieldType' changes\"\nconst resolveFields = async ({ props }, { changed, lastFields }) => {\n  if (!changed.fieldType) {\n    return lastFields;\n  }\n\n  return {\n    title: {\n      type: fieldType,\n    },\n  };\n};\n```\n\n##### `params.fields`\n\nThe static fields for this component as defined by [`fields`](#fields).\n\n##### `params.lastData`\n\nThe data object from the previous run of this function.\n\n##### `params.lastFields`\n\nThe last fields object created by the previous run of this function.\n\n##### `params.metadata`\n\nAn object containing the global metadata provided to the [`<Puck>`](/docs/api-reference/components/puck#metadata) or [`<Render>`](/docs/api-reference/components/render#metadata) component, merged with any metadata defined in this [component's config](#metadata).\n\n##### `params.parent`\n\nThe parent data object if this item is within a [DropZone](/docs/api-reference/components/drop-zone).\n\n### `resolvePermissions(data, params)`\n\nDynamically set the [permissions](/docs/api-reference/permissions) for this component to toggle functionality. Can be used to control the permissions on a specific component instance. Inherits [`permissions`](#permissions). Supports asynchronous calls.\n\nThis function is triggered when [`<Puck>`](/docs/api-reference/components/puck) renders, when the component is inserted, when its data changes, or when it's moved to a different parent.\n\n```tsx {4-12} copy filename=\"Setting permissions for a specific instance\" showLineNumbers\nconst config = {\n  components: {\n    MyComponent: {\n      resolvePermissions: (data, { permissions }) => {\n        if (data.props.id === \"MyComponent-1234\") {\n          return {\n            delete: false, // Disable deletion on component with id MyComponent-1234\n          };\n        }\n\n        return { permissions }; // Fallback to inherited permissions\n      },\n      // ...\n    },\n  },\n};\n```\n\n#### Args\n\n| Prop     | Example                                                                                         | Type   |\n| -------- | ----------------------------------------------------------------------------------------------- | ------ |\n| `data`   | `{ props: { title: \"Hello, world\" }, readOnly: {} }`                                            | Object |\n| `params` | `{ appState: {}, changed: {}, permissions: {}, lastData: {}, lastPermissions: {}, parent: {} }` | Object |\n\n##### `data.props`\n\nThe current props for the selected component.\n\n##### `data.readOnly`\n\nThe fields currently set to read-only for this component.\n\n##### `params.appState`\n\nAn object describing the [AppState](/docs/api-reference/data-model/app-state).\n\n##### `params.changed`\n\nAn object describing which props have changed on this component since the last time this function was called. This helps prevent duplicate calls when making async operations.\n\n```tsx copy {2-4} /changed/1 filename=\"Example only updating the permissions when 'example' prop changes\"\nconst resolvePermissions = async ({ props }, { changed, lastPermissions }) => {\n  if (!changed.example) {\n    return lastPermissions; // Return the last permissions unless the `example` prop has changed\n  }\n\n  return await expensiveAsyncOperation();\n};\n```\n\n##### `params.permissions`\n\nThe static fields for this component as defined by [`permissions`](#permissions).\n\n##### `params.lastData`\n\nThe data object from the previous run of this function.\n\n##### `params.lastPermissions`\n\nThe last permissions object created by the previous run of this function.\n\n##### `params.parent`\n\nThe [component data](/docs/api-reference/data-model/component-data) of the parent.\n\nIf the parent implements [resolveData](#resolvedatadata-params), this parameter will return the unresolved data on initial render.\n\n#### Returns\n\nA [`fields`](#fields) object.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/configuration/config.mdx",
    "content": "# Config\n\nThe main configuration object describing what Puck can render.\n\n```tsx copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        children: {\n          type: \"text\",\n        },\n      },\n      render: ({ children }) => {\n        return <h1>{children}</h1>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                       | Example                                                        | Type                                                                    | Status   |\n| --------------------------- | -------------------------------------------------------------- | ----------------------------------------------------------------------- | -------- |\n| [`components`](#components) | `components: { HeadingBlock: {{ render: () => <h1 /> } }`      | Object                                                                  | Required |\n| [`root`](#root)             | `root: { render: () => <div /> }`                              | [`ComponentConfig`](/docs/api-reference/configuration/component-config) | -        |\n| [`categories`](#categories) | `categories: { typography: { components: [\"HeadingBlock\"] } }` | Object                                                                  | -        |\n\n## Required params\n\n### `components`\n\nAn object describing the components available to Puck. Each component definition implements the [`ComponentConfig` API](/docs/api-reference/configuration/component-config).\n\n```tsx {2-13} copy showLineNumbers\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        children: {\n          type: \"text\",\n        },\n      },\n      render: ({ children }) => {\n        return <h1>{children}</h1>;\n      },\n    },\n  },\n};\n```\n\n## Optional params\n\n### `root`\n\nAn object describing the root of your component tree. This component wraps the rest of your components. Implements the [`ComponentConfig` API](/docs/api-reference/configuration/component-config).\n\n- You must return children to render the default content.\n\n```tsx {2-6} copy showLineNumbers\nconst config = {\n  root: {\n    render: ({ children }) => {\n      return <div>{children}</div>;\n    },\n  },\n  // ...\n};\n```\n\n### `categories`\n\nAn object describing categories for your components. Will be used to group the components in the left side-bar.\n\n```tsx {2-6} copy showLineNumbers\nconst config = {\n  categories: {\n    typography: {\n      components: [\"HeadingBlock\"],\n    },\n  },\n  // ...\n};\n```\n\n#### `categories[key].components`\n\nAn array of components in this category.\n\n- Must use names of [`components`](#components).\n- A component can appear in more than one category.\n\n#### `categories[key].title`\n\nThe user-facing title for the category. Will use the `category` key if not provided.\n\n#### `categories[key].visible`\n\nA boolean describing whether or not the category should be visible in the side bar. **Defaults to `true`**.\n\n#### `categories[key].defaultExpanded`\n\nA boolean describing whether or not the category should be expanded by default in the side bar. **Defaults to `true`**.\n\n#### `categories[\"other\"]`\n\nThe default category for all uncategorized components. Receives all other categories params.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/configuration.mdx",
    "content": "# Configuration\n\n- [Config](configuration/config) - The main configuration object describing what Puck can render.\n- [ComponentConfig](configuration/component-config) - The configuration of each component defined in the main config.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/data-model/app-state.mdx",
    "content": "---\ntitle: AppState\n---\n\nimport { Callout } from \"nextra/components\";\n\n# `AppState`\n\n<Callout>\n  The application state is unstable and is likely to experience breaking\n  changes.\n</Callout>\n\nThe internal state of the [`<Puck>`](/docs/api-reference/components/puck) component.\n\n## `data`\n\nThe current [`Data`](/docs/api-reference/data-model/data) payload being managed by Puck.\n\n## `ui`\n\nThe current state of the Puck editor interface.\n\n| Param                                           | Example                                               | Type                          |\n| ----------------------------------------------- | ----------------------------------------------------- | ----------------------------- |\n| [`arrayState`](#uiarraystate)                   | `{}`                                                  | Object                        |\n| [`componentList`](#uicomponentlist)             | `{ typography: { components: [ \"HeadingBlock\" ] } }`  | Object                        |\n| [`field.focus`](#fieldfocus)                    | `\"title\"`                                             | String                        |\n| [`isDragging`](#isdragging)                     | `false`                                               | Boolean                       |\n| [`itemSelector`](#uiitemselector)               | `{ index: 0, zone: \"my-content\" }`                    | [ItemSelector](item-selector) |\n| [`leftSideBarVisible`](#uileftsidebarvisible)   | `false`                                               | Boolean                       |\n| [`leftSideBarWidth`](#uileftsidebarwidth)       | `200`                                                 | Number                        |\n| [`mobilePanelExpanded`](#uimobilepanelexpanded) | `true`                                                | Boolean                       |\n| [`plugin`](#uiplugin)                           | `{ current: \"blocks\" }`                               | Object                        |\n| [`previewMode`](#uipreviewmode)                 | `\"edit\"`                                              | String                        |\n| [`rightSideBarVisible`](#uirightsidebarvisible) | `false`                                               | Boolean                       |\n| [`rightSideBarWidth`](#uirightsidebarwidth)     | `200`                                                 | Number                        |\n| [`viewports`](#uiviewports)                     | `{ controlsVisible: true, current: {}, options: [] }` | Object                        |\n\n---\n\n### `ui.arrayState`\n\nAn object describing the internal state of array items\n\n---\n\n### `ui.componentList`\n\nAn object describing the component drawer. Similar shape to the [categories API](/docs/api-reference/configuration/config#categories)\n\n#### `ui.componentList[key].components`\n\nArray containing the names of components in this category.\n\n#### `ui.componentList[key].title`\n\nTitle of the category.\n\n#### `ui.componentList[key].visible`\n\nWhether or not the category is visible in the side bar.\n\n#### `ui.componentList[key].expanded`\n\nWhether or not the category is expanded in the side bar.\n\n---\n\n### `ui.field.focus`\n\nThe name of the currently focused field.\n\n### `ui.field.metadata`\n\nMetadata for the the currently focused field.\n\n---\n\n### `ui.isDragging`\n\nA boolean stating whether or not the user is currently dragging a component.\n\n---\n\n### `ui.itemSelector`\n\nAn [`ItemSelector`](item-selector) for the currently selected item.\n\n---\n\n### `ui.leftSideBarVisible`\n\nWhether or not the left side bar is visible.\n\n---\n\n### `ui.leftSideBarWidth`\n\nCurrent width of the left side bar in pixels.\n\n---\n\n### `ui.mobilePanelExpanded`\n\nIndicates whether the mobile plugin panel is expanded when the [`mobilePanelHeight` parameter](/docs/api-reference/plugin#mobilepanelheight) is set to `\"toggle\"`. Users toggle this value via the maximize/minimize button that appears on smaller viewports.\n\n---\n\n### `ui.plugin`\n\n| Param                         | Example    | Type   |\n| ----------------------------- | ---------- | ------ |\n| [`current`](#uiplugincurrent) | `\"blocks\"` | String |\n\n#### `ui.plugin.current`\n\nThe currently selected plugin in the plugin rail. Matches the plugin [`name` parameter](/docs/api-reference/plugin#name).\n\n---\n\n### `ui.previewMode`\n\nThe mode for the preview area, controlling whether or not the user can interact with the underlying component. Accepts the following values:\n\n- **`\"edit\"` (default)**: Components can be dragged and modified. An overlay prevents interaction with the underlying component.\n- **`\"interactive\"`**: Editing functionality is disabled. The user can interact with the underlying component.\n\nPuck does not currently provide the UI to control this value, but it can be toggled via the `cmd+i` or `ctrl+i` hotkeys.\n\n---\n\n### `ui.rightSideBarVisible`\n\nWhether or not the right side bar is visible.\n\n---\n\n### `ui.rightSideBarWidth`\n\nCurrent width of the right side bar in pixels.\n\n---\n\n### `ui.viewports`\n\n| Param                                            | Example                           | Type                                                                 |\n| ------------------------------------------------ | --------------------------------- | -------------------------------------------------------------------- |\n| [`controlsVisible`](#uiviewportscontrolsvisible) | `false`                           | Boolean                                                              |\n| [`current`](#uiviewportscurrent)                 | `{ width: 1440, height: \"auto\" }` | Object                                                               |\n| [`options`](#uiviewportsoptions)                 | `[]`                              | [Viewports\\[\\]](/docs/api-reference/components/puck#viewport-params) |\n\n#### `ui.viewports.controlsVisible`\n\nWhether or not the viewport controls are visible.\n\n#### `ui.viewports.current`\n\nThe currently selected viewport.\n\n| Param                                 | Example  | Type               |\n| ------------------------------------- | -------- | ------------------ |\n| [`width`](#uiviewportscurrentwidth)   | `1440`   | Number             |\n| [`height`](#uiviewportscurrentheight) | `\"auto\"` | Number \\| `\"auto\"` |\n\n##### `ui.viewports.current.width`\n\nThe width of the current viewport.\n\n##### `ui.viewports.current.height`\n\nThe height of the current viewport.\n\n#### `ui.viewports.options`\n\nThe available viewport options, as provided via the [`viewports` API](/docs/api-reference/components/puck#viewports).\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/data-model/component-data.mdx",
    "content": "---\ntitle: ComponentData\n---\n\n# `ComponentData`\n\nAn object representing an instance of a component.\n\n```json\n{\n  \"type\": \"HeadingBlock\",\n  \"props\": {\n    \"id\": \"HeadingBlock-1234\",\n    \"title\": \"Hello, world\"\n  }\n}\n```\n\n## Params\n\n| Param                   | Example                                         | Type   | Status   |\n| ----------------------- | ----------------------------------------------- | ------ | -------- |\n| [`type`](#type)         | `type: \"HeadingBlock\"`                          | String | Required |\n| [`props`](#props)       | `props: { id: \"12345\", title: \"Hello, world\" }` | Object | Required |\n| [`readOnly`](#readonly) | `readOnly: { title: true }`                     | Object | -        |\n\n### Required params\n\n#### `type`\n\nThe type of the component, which tells Puck to run the [`render()`](/docs/api-reference/configuration/component-config#renderprops) method for the component of the [same key](/docs/api-reference/configuration/config#components).\n\n#### `props`\n\nThe props stored based on the [`component config`](/docs/api-reference/configuration/component-config) that Puck will pass to the [`render()`](/docs/api-reference/configuration/component-config#renderprops) method for the component.\n\n```json {3-6} copy\n{\n  \"type\": \"HeadingBlock\",\n  \"props\": {\n    \"id\": \"HeadingBlock-1234\", // Auto-generated\n    \"title\": \"Hello, world\"\n  }\n}\n```\n\nRequires a unique `id` prop to be defined.\n\n### Optional params\n\n#### `readOnly`\n\nAn object describing which fields are set to [read-only](/docs/api-reference/configuration/component-config#datareadonly-1).\n\n```json {7-9} copy\n{\n  \"type\": \"HeadingBlock\",\n  \"props\": {\n    \"id\": \"HeadingBlock-1234\",\n    \"title\": \"Hello, world\"\n  },\n  \"readOnly\": {\n    \"title\": true\n  }\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/data-model/data.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# `Data`\n\nAn object produced by Puck describing the shape of content.\n\n```json copy\n{\n  \"content\": [\n    {\n      \"type\": \"HeadingBlock\",\n      \"props\": {\n        \"id\": \"HeadingBlock-1234\",\n        \"title\": \"Hello, world\"\n      }\n    }\n  ],\n  \"root\": { \"props\": { \"title\": \"Puck Example\" } },\n  \"zones\": {}\n}\n```\n\n## Params\n\n| Param                 | Example                                  | Type                                                             | Status   |\n| --------------------- | ---------------------------------------- | ---------------------------------------------------------------- | -------- |\n| [`content`](#content) | `content: []`                            | [ComponentData](/docs/api-reference/data-model/component-data)[] | Required |\n| [`root`](#root)       | `root: { props: { title: \"My page\" } }`  | [RootData](/docs/api-reference/root-data)                        | Required |\n| [`zones`](#zones)     | `zones: { \"HeadingBlock-123:zone\": [] }` | Object                                                           | -        |\n\n### `content`\n\nAn array of [ComponentData](/docs/api-reference/data-model/component-data) objects representing the component instances in the default content region.\n\n```json {2-9} copy\n{\n  \"content\": [\n    {\n      \"type\": \"HeadingBlock\",\n      \"props\": {\n        \"id\": \"HeadingBlock-1234\",\n        \"title\": \"Hello, world\"\n      }\n    }\n  ],\n  \"root\": {},\n  \"zones\": {}\n}\n```\n\n### `root`\n\nAn object describing data for the [`root` config](/docs/api-reference/configuration/config#root). An instance of [`RootData`](/docs/api-reference/root-data).\n\n```json {3-7} copy\n{\n  \"content\": [],\n  \"root\": {\n    \"props\": {\n      \"title\": \"My page\"\n    }\n  },\n  \"zones\": {}\n}\n```\n\n### `zones`\n\n<Callout>\n  This parameter will soon be deprecated, as DropZones have been replaced by\n  [`slot` fields](/docs/api-reference/fields/slot). For migration notes, see\n  [these docs](/docs/guides/migrations/dropzones-to-slots).\n</Callout>\n\nAn object describing nested content regions for each [DropZone](/docs/api-reference/components/drop-zone).\n\n#### `zones[zoneKey]`\n\nAn array of [ComponentData](/docs/api-reference/data-model/component-data) objects representing the components instances in a particular DropZone.\n\n`zoneKey` is a compound of the component `id` and [DropZone `zone`](/docs/api-reference/components/drop-zone#zone).\n\n```json {5-13} copy showLineNumbers\n{\n  \"content\": [],\n  \"root\": {},\n  \"zones\": {\n    \"HeadingBlock-1234:my-content\": [\n      {\n        \"type\": \"HeadingBlock\",\n        \"props\": {\n          \"id\": \"HeadingBlock-1234\",\n          \"title\": \"Hello, world\"\n        }\n      }\n    ]\n  }\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/data-model/item-selector.mdx",
    "content": "---\ntitle: ItemSelector\n---\n\nimport { Callout } from \"nextra/components\";\n\n# `ItemSelector`\n\nAn object describing the location of an item in the Puck [data](data).\n\n```json copy\n{\n  \"index\": 0,\n  \"zone\": \"Flex-123:children\" // The \"children\" slot field in the component with id \"Flex-123\"\n}\n```\n\n## Params\n\n| Param             | Example                       | Type   | Status   |\n| ----------------- | ----------------------------- | ------ | -------- |\n| [`index`](#index) | `index: 0`                    | Number | Required |\n| [`zone`](#zone)   | `zone: \"Flex-123:children\" }` | String | -        |\n\n### `index`\n\nThe index of the item.\n\n### `zone`\n\nAn optional string representing the location of the item. It is a concatenation of two values, joined with a colon (`:`):\n\n1. The id of the parent component\n2. The name of the [slot field](/docs/api-reference/fields/slot) or [DropZone `zone`](/docs/api-reference/components/drop-zone#zone) value\n\nThe root of the payload is represented by the `root` id. The default content is represented by `default-zone`. When not provided, `zone` will contain the value `root:default-zone`, injecting data into [`content`](/docs/api-reference/data-model/data#content)\n\n<Callout>\n  DropZones have been replaced by [`slot`\n  fields](/docs/api-reference/fields/slot). For migration notes, see [these\n  docs](/docs/guides/migrations/dropzones-to-slots).\n</Callout>\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/data-model/root-data.mdx",
    "content": "---\ntitle: RootData\n---\n\n# `RootData`\n\nAn object representing the root data. Similar to [`ComponentData`](/docs/api-reference/data-model/component-data).\n\n```json\n{\n  \"props\": {\n    \"title\": \"Hello, world\"\n  }\n}\n```\n\n## Params\n\n| Param                          | Example                            | Type   | Status   |\n| ------------------------------ | ---------------------------------- | ------ | -------- |\n| [`props`](#contentprops)       | `props: { title: \"Hello, world\" }` | Object | Required |\n| [`readOnly`](#contentreadonly) | `readOnly: { title: true }`        | Object | -        |\n\n### Required params\n\n#### `type`\n\nThe type of the component, which tells Puck to run the [`render()`](/docs/api-reference/configuration/component-config#renderprops) method for the component of the [same key](/docs/api-reference/config#components).\n\n#### `props`\n\nThe props stored based on the [`component config`](/docs/api-reference/configuration/component-config) that Puck will pass to the [`render()`](/docs/api-reference/configuration/component-config#renderprops) method for the component of the [same key](/docs/api-reference/config#components).\n\n```json {3-6} copy\n{\n  \"type\": \"HeadingBlock\",\n  \"props\": {\n    \"id\": \"HeadingBlock-1234\",\n    \"title\": \"Hello, world\"\n  }\n}\n```\n\nRequires `id` unless used for [`root`](/docs/api-reference/data-model/data#root).\n\n### Optional params\n\n#### `readOnly`\n\nAn object describing which fields are set to [read-only](/docs/api-reference/configuration/component-config#datareadonly-1).\n\n```json {7-9} copy\n{\n  \"type\": \"HeadingBlock\",\n  \"props\": {\n    \"id\": \"HeadingBlock-1234\",\n    \"title\": \"Hello, world\"\n  },\n  \"readOnly\": {\n    \"title\": true\n  }\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/data-model.mdx",
    "content": "# Data Model\n\nThe Puck data model\n\n- [AppState](data-model/app-state) - Puck's internal state.\n- [ComponentData](data-model/component-data) - The data model for each component instance.\n- [Data](data-model/data) - The data model produced by Puck for a page.\n- [RootData](data-model/root-data) - The data model for the root data.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/field-transforms.mdx",
    "content": "---\ntitle: FieldTransforms\n---\n\n# FieldTransforms\n\nTransform the data for each [field type](/docs/api-reference/fields) before rendering in the editor.\n\n```tsx copy\nconst fieldTransforms = {\n  text: ({ value }) => <p>{value}</p>,\n  // ...\n};\n```\n\nYou can specify a custom render method for each known [field type](/docs/api-reference/fields), or introduce completely new ones.\n\n## Render Props\n\n| Prop                          | Example              | Type                                |\n| ----------------------------- | -------------------- | ----------------------------------- |\n| [`componentId`](#componentid) | `\"Heading-12345\"`    | string                              |\n| [`field`](#field)             | `{ type: \"text\" }`   | [Field](/docs/api-reference/fields) |\n| [`isReadOnly`](#isreadonly)   | `false`              | boolean                             |\n| [`propName`](#propname)       | `\"title\"`            | string                              |\n| [`propPath`](#proppath)       | `\"obj.arr[2].title\"` | string                              |\n| [`value`](#value)             | `\"Value\"`            | any                                 |\n\n### `componentId`\n\nThe id of the component containing this prop\n\n### `field`\n\nThe component's field definition for this prop.\n\n### `isReadOnly`\n\nWhether or not this field is currently set to read-only.\n\n### `propName`\n\nThe name of this prop provided to the [component field](/docs/api-reference/configuration/component-config#fields) config.\n\n### `propPath`\n\nThe path of this prop within the props object.\n\nUse in conjunction with the [`setDeep`](/docs/api-reference/functions/set-deep) utility to set data for a key deep within an object or array, normally [ComponentData](/docs/api-reference/data-model/component-data).\n\n### `value`\n\nThe value of the prop.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/_meta.js",
    "content": "const menu = {\n  base: {\n    title: \"Base\",\n  },\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/array.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\nimport { Callout } from \"nextra/components\";\n\n# Array\n\nRender a list of items with a subset of fields. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      items: {\n        type: \"array\",\n        arrayFields: {\n          title: { type: \"text\" },\n        },\n      },\n    },\n    defaultProps: { items: [{ title: \"Apple\" }, { title: \"Banana\" }] },\n    render: ({ items }) => {\n      return (\n        <ul>\n          {items.map((item, i) => (\n            <li key={i}>{item.title}</li>\n          ))}\n        </ul>\n      );\n    },\n  }}\n/>\n\n```tsx {5-10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n        },\n      },\n      render: ({ items }) => {\n        return (\n          <ul>\n            {items.map((item, i) => (\n              <li key={i}>{item.title}</li>\n            ))}\n          </ul>\n        );\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                           | Example                                       | Type               | Status   |\n| ----------------------------------------------- | --------------------------------------------- | ------------------ | -------- |\n| [`type`](#type)                                 | `type: \"array\"`                               | \"array\"            | Required |\n| [`arrayFields`](#arrayfields)                   | `arrayFields: { title: { type: \"text\" } }`    | Object             | Required |\n| [`defaultItemProps`](#defaultitemprops)         | `defaultItemProps: { title: \"Hello, world\" }` | Object \\| Function | -        |\n| [`getItemSummary()`](#getitemsummaryitem-index) | `getItemSummary: (item) => item.title`        | Function           | -        |\n| [`max`](#max)                                   | `max: 3`                                      | Number             | -        |\n| [`min`](#min)                                   | `min: 1`                                      | Number             | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"array\"` for Array fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `arrayFields`\n\nDescribe the fields for each item in the array. Shares an API with `fields`.\n\nCan include any field type, including nested array fields.\n\n```tsx {7-9} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Optional params\n\n### `defaultItemProps`\n\nSet the default values when a new item is added to the array. Can be an object or a function that receives the current array index.\n\n```tsx {10-12} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n          defaultItemProps: {\n            title: \"Hello, world\",\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      items: {\n        type: \"array\",\n        arrayFields: {\n          title: { type: \"text\" },\n        },\n        defaultItemProps: {\n          title: \"Hello, world\",\n        },\n      },\n    },\n    defaultProps: { items: [{ title: \"Apple\" }, { title: \"Banana\" }] },\n    render: ({ items }) => {\n      return (\n        <ul>\n          {items.map((item, i) => (\n            <li key={i}>{item.title}</li>\n          ))}\n        </ul>\n      );\n    },\n  }}\n/>\n\nYou can also use a function to generate dynamic defaults:\n\n```tsx {10-14} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n            order: { type: \"number\" },\n          },\n          defaultItemProps: (index) => ({\n            title: `Item ${index + 1}`,\n            order: index + 1,\n          }),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Dynamic Example\"\n  componentConfig={{\n    fields: {\n      items: {\n        type: \"array\",\n        arrayFields: {\n          title: { type: \"text\" },\n          order: { type: \"number\" },\n        },\n        defaultItemProps: (index) => ({\n          title: `Item ${index + 1}`,\n          order: index + 1,\n        }),\n      },\n    },\n    defaultProps: { items: [] },\n    render: ({ items }) => {\n      return (\n        <ul>\n          {items.map((item, i) => (\n            <li key={i}>\n              {item.title} (Order: {item.order})\n            </li>\n          ))}\n        </ul>\n      );\n    },\n  }}\n/>\n\n### `getItemSummary(item, index)`\n\nGet a label of each item in the array. Returns a [ReactNode](https://react.dev/learn/typescript#typing-children).\n\n<Callout type=\"warning\">\n  Avoid using interactive HTML elements like `<button>`, `<input>`, or `<a>` inside `getItemSummary`. These elements can interfere with the array field's built-in interactions (such as drag-and-drop reordering and item expansion). Stick to non-interactive elements like `<div>`, `<span>`, `<p>`, or plain text for summaries.\n</Callout>\n\n```tsx {10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n          getItemSummary: (item) => item.title || \"Item\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      items: {\n        type: \"array\",\n        arrayFields: {\n          title: { type: \"text\" },\n        },\n        getItemSummary: (item) => item.title || \"Item\",\n      },\n    },\n    defaultProps: { items: [{ title: \"Apple\" }, { title: \"Banana\" }] },\n    render: ({ items }) => {\n      return (\n        <ul>\n          {items.map((item, i) => (\n            <li key={i}>{item.title}</li>\n          ))}\n        </ul>\n      );\n    },\n  }}\n/>\n\n### `max`\n\nThe maximum amount of items allowed in the array.\n\n```tsx {10-10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n          max: 3,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      items: {\n        type: \"array\",\n        max: 3,\n        arrayFields: {\n          title: { type: \"text\" },\n        },\n      },\n    },\n    defaultProps: { items: [{ title: \"Apple\" }, { title: \"Banana\" }] },\n    render: ({ items }) => {\n      return (\n        <ul>\n          {items.map((item, i) => (\n            <li key={i}>{item.title}</li>\n          ))}\n        </ul>\n      );\n    },\n  }}\n/>\n\n### `min`\n\nThe minimum amount of items allowed in the array.\n\n```tsx {10-10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"array\",\n          arrayFields: {\n            title: { type: \"text\" },\n          },\n          min: 1,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      items: {\n        type: \"array\",\n        min: 1,\n        arrayFields: {\n          title: { type: \"text\" },\n        },\n      },\n    },\n    defaultProps: { items: [{ title: \"Apple\" }, { title: \"Banana\" }] },\n    render: ({ items }) => {\n      return (\n        <ul>\n          {items.map((item, i) => (\n            <li key={i}>{item.title}</li>\n          ))}\n        </ul>\n      );\n    },\n  }}\n/>\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/base.mdx",
    "content": "# Base\n\nThe base type shared by all fields.\n\n## Params\n\n| Param                     | Example               | Type      | Status |\n| ------------------------- | --------------------- | --------- | ------ |\n| [`label`](#label)         | `label: \"Title\"`      | String    | -      |\n| [`labelIcon`](#labelicon) | `labelIcon: <Icon />` | ReactNode | -      |\n| [`metadata`](#metadata)   | `metadata: {}`        | Object    | -      |\n| [`visible`](#visible)     | `visible: false`      | Boolean   | -      |\n\n## Optional params\n\n### `label`\n\nSet the label for the input. Puck will use the key if not provided.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          label: \"My Field\",\n          // ...\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `labelIcon`\n\nSet an icon to display next to the label.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          labelIcon: <Icon />,\n          // ...\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `metadata`\n\nAn object containing additional information for the field.\n\n```tsx {6-8} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          metadata: {\n            title: \"Hello, world\",\n          },\n          // ...\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `visible`\n\nShow or hide the field. Defaults to `true`.\n\n```tsx {6-8} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          visible: false,\n          // ...\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/custom.mdx",
    "content": "import { Puck, FieldLabel, AutoField } from \"@/puck\";\nimport { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Callout } from \"nextra/components\";\n\n# Custom\n\nImplement a field with a custom UI. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        render: ({ name, onChange, value }) => {\n          return (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                padding: 4,\n              }}\n            />\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n```tsx {7-16} copy showLineNumbers\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: ({ name, onChange, value }) => (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n            />\n          ),\n        },\n      },\n      render: ({ title }) => {\n        return <p>{title}</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                 | Example                   | Type     | Status   |\n| ------------------------------------- | ------------------------- | -------- | -------- |\n| [`type`](#type)                       | `type: \"custom\"`          | \"custom\" | Required |\n| [`render()`](#renderparams)           | `render: () => <input />` | Function | Required |\n| [`contentEditable`](#contentEditable) | `contentEditable: true`   | Boolean  | -        |\n| [`key`](#key)                         | `key: \"custom-text\"`      | String   | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"custom\"` for Custom fields.\n\n```tsx {6} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: ({ name, onChange, value }) => (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n            />\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `render(params)`\n\nRender the custom field.\n\n```tsx {9-14} showLineNumbers copy\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: ({ name, onChange, value }) => (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n            />\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n#### `params`\n\n| Param                 | Example                    | Type     |\n| --------------------- | -------------------------- | -------- |\n| `field`               | `{ type: \"custom\" }`       | Object   |\n| `id`                  | `id`                       | String   |\n| `name`                | `\"title\"`                  | String   |\n| `onChange(value, ui)` | `onChange(\"Hello, world\")` | Function |\n| `value`               | `\"Hello, world\"`           | Any      |\n\n##### onChange(value, [ui])\n\nSet the value of the field and optionally update the [Puck UI state](/docs/api-reference/data-model/app-state#ui).\n\n| Param   | Example                       | Type                                                   | Status   |\n| ------- | ----------------------------- | ------------------------------------------------------ | -------- |\n| `value` | `\"Hello, world\"`              | Any                                                    | Required |\n| `ui`    | `{leftSideBarVisible: false}` | [UiState](/docs/api-reference/data-model/app-state#ui) |          |\n\n## Optional params\n\n### `contentEditable`\n\nEnable inline text editing for this field. Only works if the value is a string. Defaults to `false`.\n\n<Callout type=\"warning\">\n  When setting `contentEditable`, your [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) prop will be converted to an [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) when rendered inside [`<Puck>`](/docs/api-reference/components/puck) (but not [`<Render>`](/docs/api-reference/components/render)). When using TypeScript, change your `string` to  `string | ReactNode`.\n</Callout>\n\n```tsx {7, 10} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          contentEditable: true,\n          render: ({ name, onChange, value }) => (\n            <input\n              value={value} // Bind to value for 2-way binding\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n            />\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        contentEditable: true,\n        render: ({ name, onChange, value }) => (\n          <input\n            value={value}\n            name={name}\n            onChange={(e) => onChange(e.currentTarget.value)}\n            style={{\n              background: \"white\",\n              border: \"1px solid black\",\n              padding: 4,\n            }}\n          />\n        ),\n      },\n    },\n    defaultProps: {\n      title: \"Edit me inline\",\n    },\n    render: ({ title }) => {\n      return <div>{title}</div>;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n### `key`\n\nAssign a unique key to force the field to remount when using [`resolveFields`](/docs/api-reference/configuration/component-config#resolvefieldsdata-params) to resolve different custom field types on the same prop.\n\n```tsx {19, 28} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        isText: {\n          type: \"radio\",\n          options: [\n            { label: \"Yes\", value: true },\n            { label: \"No\", value: false },\n          ],\n        },\n      },\n      resolveFields: (data, params) => {\n        if (data.props.isText) {\n          return {\n            ...params.fields,\n            prop: {\n              type: \"custom\",\n              key: \"custom-text\",\n              // ...\n            },\n          };\n        } else {\n          return {\n            ...params.fields,\n            prop: {\n              type: \"custom\",\n              key: \"custom-number\",\n              // ...\n            },\n          };\n        }\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      isText: {\n        type: \"radio\",\n        options: [\n          { label: \"Yes\", value: true },\n          { label: \"No\", value: false },\n        ],\n      },\n    },\n    resolveFields: (data, params) => {\n      if (data.props.isText) {\n        return {\n          ...params.fields,\n          title: {\n            type: \"custom\",\n            key: \"custom-text\",\n            render: ({ onChange, value }) => (\n              <FieldLabel label=\"Text Input\">\n                  <AutoField\n                    field={{ type: \"text\" }}\n                    value={value}\n                    onChange={onChange}\n                  />\n              </FieldLabel>\n            ),\n          },\n        };\n      } else {\n        return {\n          ...params.fields,\n          title: {\n            type: \"custom\",\n            key: \"custom-number\",\n            render: ({ value, onChange }) => {\n              return (\n                <FieldLabel label=\"Number Input\">\n                  <AutoField\n                    field={{ type: \"number\" }}\n                    value={value}\n                    onChange={onChange}\n                  />\n                </FieldLabel>\n              );\n            },\n          },\n        };\n      }\n    },\n    resolveData: (data, params) => {\n      if (!params.changed.isText) return data;\n\n      if (data.props.isText) {\n        return {\n          ...data,\n          props: {\n            ...data.props,\n            title: \"Title\",\n          },\n        };\n      } else {\n        return {\n          ...data,\n          props: {\n            ...data.props,\n            title: 123,\n          },\n        };\n      }\n    },\n    defaultProps: {\n      isText: true,\n    },\n    render: ({ title }) => {\n      return <p style={{color: \"black\"}}>{title}</p>;\n    },\n\n}}\n\n>\n\n  <Puck.Preview />\n</ConfigPreview>\n\n## Further reading\n\n- [Custom Fields guide](/docs/extending-puck/custom-fields)\n- [The `<FieldLabel>` API reference](/docs/api-reference/components/field-label)\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/external.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\nimport { Callout } from \"nextra/components\";\n\n# External\n\nSelect data from a list, typically populated via a third-party API. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n      },\n    },\n    render: ({ data }) => {\n      return <p style={{ margin: 0 }}>{data?.title || \"No data selected\"}</p>;\n    },\n  }}\n/>\n\n```tsx {5-15} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            // ... fetch data from a third party API, or other async source\n\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n        },\n      },\n      render: ({ data }) => {\n        return <p>{data?.title || \"No data selected\"}</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                     | Example                                      | Type       | Status   |\n| ----------------------------------------- | -------------------------------------------- | ---------- | -------- |\n| [`type`](#type)                           | `type: \"external\"`                           | \"external\" | Required |\n| [`fetchList()`](#fetchlistqueryparams)    | `fetchList: async () => []`                  | Function   | Required |\n| [`cache`](#cache)                         | `cache: { enabled: true }`                   | Object     | -        |\n| [`filterFields`](#filterfields)           | `{ \"rating\": { type: \"number\" } }`           | Object     | -        |\n| [`getItemSummary()`](#getitemsummaryitem) | `getItemSummary: async ({ title }) => title` | Function   | -        |\n| [`initialFilters`](#initialfilters)       | `{ \"rating\": 1 }`                            | Object     | -        |\n| [`initialQuery`](#initialquery)           | `initialQuery: \"Hello, world\"`               | String     | -        |\n| [`mapProp()`](#mappropitem)               | `mapProp: async ({ title }) => title`        | Function   | -        |\n| [`mapRow()`](#maprowitem)                 | `mapRow: async ({ title }) => title`         | Function   | -        |\n| [`placeholder`](#placeholder)             | `placeholder: \"Select content\"`              | String     | -        |\n| [`renderFooter()`](#renderfooterprops)    | `renderFooter: (props) => <p>Hello</p>`      | Function   | -        |\n| [`showSearch`](#showsearch)               | `showSearch: true`                           | Boolean    | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"external\"` for Array fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `fetchList(queryParams)`\n\nReturn a promise with a list of objects to be rendered in a tabular format via the external input modal.\n\nThe table will only render strings and numbers.\n\n```tsx {7-14} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            // ... fetch data from a third party API, or other async source\n\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n#### `queryParams`\n\nThe parameters passed to the `fetchList` method based on your field configuration.\n\n| Param                 | Example             | Type   |\n| --------------------- | ------------------- | ------ |\n| [`query`](#query)     | `\"My Query\"`        | String |\n| [`filters`](#filters) | `\"{ \"rating\": 1 }\"` | Object |\n\n##### `query`\n\nThe search query when using [`showSearch`](#showsearch).\n\n##### `filters`\n\nAn object describing the filters configured by [`filterFields`](#filterfields).\n\n## Optional params\n\n### `cache`\n\nPuck will automatically cache the output of the [`fetchList()`](#fetchlistqueryparams) function in memory.\n\nYou can disable this behaviour by setting `cache: { enabled: false }` on your field.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          cache: { enabled: true },\n          fetchList: async () => {\n            // ... fetch data from a third party API, or other async source\n\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `filterFields`\n\nAn object describing filters for your query using the [Fields API](/docs/api-reference/fields)\n\n```tsx {13-17} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async ({ filters }) => {\n            return [\n              { title: \"Apple\", description: \"Lorem ipsum 1\", rating: 5 },\n              { title: \"Orange\", description: \"Lorem ipsum 2\", rating: 3 },\n            ].filter((item) => item.rating >= (filters.rating || 0));\n          },\n          filterFields: {\n            rating: {\n              type: \"number\",\n            },\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async ({ filters }) => {\n          return [\n            { title: \"Apple\", description: \"Lorem ipsum 1\", rating: 5 },\n            { title: \"Orange\", description: \"Lorem ipsum 2\", rating: 3 },\n          ].filter((item) =>\n            item.rating >= (filters.rating || 0)\n          )\n        },\n        filterFields: {\n          rating: {\n            type: \"number\",\n          },\n        },\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n\n}}\n/>\n\n### `getItemSummary(item)`\n\nGet the label to show once the item is selected. Returns a [ReactNode](https://react.dev/learn/typescript#typing-children).\n\n<Callout type=\"warning\">\n  Avoid using interactive HTML elements like `<button>`, `<input>`, or `<a>` inside `getItemSummary`. These elements can interfere with the array field's built-in interactions (such as drag-and-drop reordering and item expansion). Stick to non-interactive elements like `<div>`, `<span>`, `<p>`, or plain text for summaries.\n</Callout>\n\n```tsx {13} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n          getItemSummary: (item) => item.title,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n        getItemSummary: (item) => item.title,\n      },\n    },\n    defaultProps: {\n      data: {\n        title: \"Hello, world\",\n        description: \"Lorem ipsum 1\",\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n  }}\n/>\n\n### `initialFilters`\n\nThe initial filter values when using [`filterFields`](#filterfields).\n\n```tsx {18-20} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async ({ filters }) => {\n            return [\n              { title: \"Apple\", description: \"Lorem ipsum 1\", rating: 5 },\n              { title: \"Orange\", description: \"Lorem ipsum 2\", rating: 3 },\n            ].filter((item) => item.rating >= (filters.rating || 0));\n          },\n          filterFields: {\n            rating: {\n              type: \"number\",\n            },\n          },\n          initialFilters: {\n            rating: 1,\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async ({ filters }) => {\n          return [\n            { title: \"Apple\", description: \"Lorem ipsum 1\", rating: 5 },\n            { title: \"Orange\", description: \"Lorem ipsum 2\", rating: 3 },\n          ].filter((item) =>\n            item.rating >= (filters.rating || 0)\n          )\n        },\n        filterFields: {\n          rating: {\n            type: \"number\",\n          },\n        },\n        initialFilters: {\n          rating: 1,\n        },\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n\n}}\n/>\n\n### `initialQuery`\n\nSet an initial query when using showing a search input with [`showSearch`](#showsearch).\n\n```tsx {16} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async ({ query }) => {\n            return [\n              { title: \"Apple\", description: \"Lorem ipsum 1\" },\n              { title: \"Orange\", description: \"Lorem ipsum 2\" },\n            ].filter((item) => {\n              // ...\n            });\n          },\n          showSearch: true,\n          initialQuery: \"Apple\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async ({ query }) => {\n          return [\n            {\n              title: \"Apple\",\n              description:\n                \"An apple is a round, edible fruit produced by an apple tree.\",\n            },\n            {\n              title: \"Orange\",\n              description:\n                \"An orange is a fruit of various citrus species in the family Rutaceae.\",\n            },\n          ].filter((item) => {\n            if (!query) return item;\n\n            const queryLowercase = query.toLowerCase();\n\n            if (item.title.toLowerCase().indexOf(queryLowercase) > -1) {\n              return item;\n            }\n\n            if (item.description.toLowerCase().indexOf(queryLowercase) > -1) {\n              return item;\n            }\n          })\n        },\n        showSearch: true,\n        initialQuery: 'apple'\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n\n}}\n/>\n\n### `mapProp(item)`\n\nModify the shape of the item selected by the user in the table before writing to the page data.\n\n```tsx {13} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n          mapProp: (item) => item.description,\n        },\n      },\n      render: ({ data }) => {\n        return <p>{data || \"No data selected\"}</p>;\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n        mapProp: (item) => item.description,\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data || \"No data selected\"}</p>;\n    },\n  }}\n/>\n\n### `mapRow(item)`\n\nModify the shape of the item before rendering it in the table. This will not affect the selected data.\n\n```tsx {13} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n          mapRow: (item) => ({ ...item, title: item.title.toUpperCase() }),\n        },\n      },\n      render: ({ data }) => {\n        return <p>{data || \"No data selected\"}</p>;\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n        mapRow: (item) => ({ ...item, title: item.title.toUpperCase() }),\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n  }}\n/>\n\n### `placeholder`\n\nSet the placeholder text when no item is selected.\n\n```tsx {13} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            return [\n              { title: \"Apple\", description: \"Lorem ipsum 1\" },\n              { title: \"Orange\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n          placeholder: \"Pick your favorite fruit\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async () => {\n          return [\n            {\n              title: \"Apple\",\n              description:\n                \"An apple is a round, edible fruit produced by an apple tree.\",\n            },\n            {\n              title: \"Orange\",\n              description:\n                \"An orange is a fruit of various citrus species in the family Rutaceae.\",\n            },\n          ];\n        },\n        placeholder: \"Pick your favorite fruit\",\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n  }}\n/>\n\n### `renderFooter(props)`\n\nCustomize what will be displayed in the footer of the modal.\n\n```tsx {13-15} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            return [\n              { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n              { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            ];\n          },\n          renderFooter: ({ items }) => (\n            <b>Custom footer with {items.length} results</b>\n          ),\n        },\n      },\n      render: ({ data }) => {\n        return <p>{data || \"No data selected\"}</p>;\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n        renderFooter: ({ items }) => (\n          <b>Custom footer with {items.length} results</b>\n        ),\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n  }}\n/>\n\n### `showSearch`\n\nShow a search input, the value of which will be passed to `fetchList` as the `query` param.\n\n```tsx {15} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async ({ query }) => {\n            return [\n              { title: \"Apple\", description: \"Lorem ipsum 1\" },\n              { title: \"Orange\", description: \"Lorem ipsum 2\" },\n            ].filter((item) => {\n              // ...\n            });\n          },\n          showSearch: true,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        fetchList: async ({ query }) => {\n          return [\n            {\n              title: \"Apple\",\n              description:\n                \"An apple is a round, edible fruit produced by an apple tree.\",\n            },\n            {\n              title: \"Orange\",\n              description:\n                \"An orange is a fruit of various citrus species in the family Rutaceae.\",\n            },\n          ].filter((item) => {\n            if (!query) return item;\n\n            const queryLowercase = query.toLowerCase();\n\n            if (item.title.toLowerCase().indexOf(queryLowercase) > -1) {\n              return item;\n            }\n\n            if (item.description.toLowerCase().indexOf(queryLowercase) > -1) {\n              return item;\n            }\n          })\n        },\n        showSearch: true,\n      },\n    },\n    render: ({ data }) => {\n      return <p>{data?.title || \"No data selected\"}</p>;\n    },\n\n}}\n/>\n\n<div id=\"puck-portal-root\" />\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/number.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# Number\n\nRender a `number` input. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      myNumber: {\n        type: \"number\",\n      },\n    },\n    defaultProps: { myNumber: 5 },\n    render: ({ myNumber }) => {\n      return <div>{myNumber}</div>;\n    },\n  }}\n/>\n\n```tsx {5-7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        myNumber: {\n          type: \"number\",\n        },\n      },\n      render: ({ myNumber }) => {\n        return <div>{myNumber}</div>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                         | Example                         | Type     | Status   |\n| ----------------------------- | ------------------------------- | -------- | -------- |\n| [`type`](#type)               | `type: \"number\"`                | \"number\" | Required |\n| [`max`](#max)                 | `max: 10`                       | number   | -        |\n| [`min`](#min)                 | `min: 0`                        | number   | -        |\n| [`placeholder`](#placeholder) | `placeholder: \"Lorem ipsum...\"` | String   | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"number\"` for Number fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        myNumber: {\n          type: \"number\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Optional params\n\n### `max`\n\nThe maximum numeric value allowed.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        myNumber: {\n          type: \"number\",\n          max: 10,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      myNumber: {\n        type: \"number\",\n        max: 10,\n      },\n    },\n    defaultProps: { myNumber: 5 },\n    render: ({ myNumber }) => {\n      return <div>{myNumber}</div>;\n    },\n  }}\n/>\n\n### `min`\n\nThe minimum numeric value allowed.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        myNumber: {\n          type: \"number\",\n          min: 0,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      myNumber: {\n        type: \"number\",\n        min: 0,\n      },\n    },\n    defaultProps: { myNumber: 5 },\n    render: ({ myNumber }) => {\n      return <div>{myNumber}</div>;\n    },\n  }}\n/>\n\n### Placeholder\n\nThe placeholder text to display when the field is empty.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        myNumber: {\n          type: \"number\",\n          placeholder: \"Lorem ipsum...\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      myNumber: {\n        type: \"number\",\n        placeholder: \"Lorem ipsum...\",\n      },\n    },\n    render: ({ myNumber }) => {\n      return <div>{myNumber}</div>;\n    },\n  }}\n/>\n\n### `step`\n\nThe stepping interval when interacting with the field.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        myNumber: {\n          type: \"number\",\n          step: 0.1,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      myNumber: {\n        type: \"number\",\n        step: 0.1,\n      },\n    },\n    defaultProps: { myNumber: 5 },\n    render: ({ myNumber }) => {\n      return <div>{myNumber}</div>;\n    },\n  }}\n/>\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/object.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# Object\n\nRender an object with a subset of fields. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      params: {\n        type: \"object\",\n        objectFields: {\n          title: { type: \"text\" },\n        },\n      },\n    },\n    defaultProps: { params: { title: \"Hello, world\" } },\n    render: ({ params }) => {\n      return <p>{params.title}</p>;\n    },\n  }}\n/>\n\n```tsx {5-10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        params: {\n          type: \"object\",\n          objectFields: {\n            title: { type: \"text\" },\n          },\n        },\n      },\n      render: ({ params }) => {\n        return <p>{params.title}</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                           | Example                                     | Type    | Status   |\n| ------------------------------- | ------------------------------------------- | ------- | -------- |\n| [`type`](#type)                 | `type: \"array\"`                             | \"array\" | Required |\n| [`objectFields`](#objectfields) | `objectFields: { title: { type: \"text\" } }` | Object  | Required |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"object\"` for Object fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"object\",\n          objectFields: {\n            title: { type: \"text\" },\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `objectFields`\n\nDescribe the fields for the object. Shares an API with `fields`.\n\nCan include any field type, including nested object fields.\n\n```tsx {7-9} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"object\",\n          objectFields: {\n            title: { type: \"text\" },\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/radio.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# Radio\n\nRender a `radio` input with a list of options. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      textAlign: {\n        type: \"radio\",\n        options: [\n          { label: \"Left\", value: \"left\" },\n          { label: \"Right\", value: \"right\" },\n        ],\n      },\n    },\n    defaultProps: {\n      textAlign: \"left\",\n    },\n    render: ({ textAlign }) => {\n      return <p style={{ textAlign, margin: 0 }}>Hello, world</p>;\n    },\n  }}\n/>\n\n```tsx {5-11} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        textAlign: {\n          type: \"radio\",\n          options: [\n            { label: \"Left\", value: \"left\" },\n            { label: \"Right\", value: \"right\" },\n          ],\n        },\n      },\n      defaultProps: {\n        textAlign: \"left\",\n      },\n      render: ({ textAlign }) => {\n        return <p style={{ textAlign }}>Hello, world</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                 | Example                                               | Type     | Status   |\n| --------------------- | ----------------------------------------------------- | -------- | -------- |\n| [`type`](#type)       | `type: \"radio\"`                                       | \"radio\"  | Required |\n| [`options`](#options) | `options: [{ label: \"Option 1\", value: \"option-1\" }]` | Object[] | Required |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"radio\"` for Array fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        textAlign: {\n          type: \"radio\",\n          options: [\n            { label: \"Left\", value: \"left\" },\n            { label: \"Right\", value: \"right\" },\n          ],\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `options`\n\nThe options for the radio field. The `value` can be a String, Number or Boolean.\n\n```tsx {7-10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        textAlign: {\n          type: \"radio\",\n          options: [\n            { label: \"Left\", value: \"left\" },\n            { label: \"Right\", value: \"right\" },\n          ],\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/richtext.mdx",
    "content": "import { Puck, RichTextMenu } from \"@/puck\";\nimport { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Callout } from \"nextra/components\";\nimport { Highlighter, Quote } from \"lucide-react\";\n\n# Richtext\n\nRender a Rich Text editor field. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, <strong>world</strong></p>\",\n    },\n    render: ({ body }) => body,\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n```tsx {5-7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                          | Example                                    | Type             | Status   |\n| ---------------------------------------------- | ------------------------------------------ | ---------------- | -------- |\n| [`type`](#type)                                | `type: \"richtext\"`                         | \"richtext\"       | Required |\n| [`contentEditable`](#contentEditable)          | `contentEditable: false`                   | Boolean          | -        |\n| [`initialHeight`](#initialheight)              | `initialHeight: 312`                       | Number \\| String | -        |\n| [`options`](#options)                          | `options: { bold: false }`                 | Object           | -        |\n| [`renderMenu()`](#rendermenuprops)             | `renderMenu: () => <RichTextMenu />`       | Function         | -        |\n| [`renderInlineMenu()`](#renderinlinemenuprops) | `renderInlineMenu: () => <RichTextMenu />` | Function         | -        |\n| [`tiptap.extensions`](#tiptapextensions)       | `extensions: []`                           | Array            | -        |\n| [`tiptap.selector`](#tiptapselector)           | `selector: (ctx) => ({ ... })`             | Function         | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"richtext\"` for RichText fields.\n\n```tsx {6} copy showLineNumbers\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Optional params\n\n### `contentEditable`\n\nEnable inline text editing for this field. Defaults to `false`.\n\n```tsx {7} copy showLineNumbers\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          contentEditable: true,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        contentEditable: true,\n      },\n    },\n    defaultProps: {\n      body: \"<p>Edit me <strong>inline</strong></p>\",\n    },\n    render: ({ body }) => body,\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n### `initialHeight`\n\nSet the initial height for the field input. Defaults to `192`.\n\n```tsx {7} copy showLineNumbers\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          initialHeight: 312,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<Callout type=\"info\">\n  Does not apply to inline fields when using\n  [`contentEditable`](#contenteditable).\n</Callout>\n\n### `options`\n\nThe Rich Text field is built with [Tiptap](https://tiptap.dev). Configure Puck's [included Tiptap extensions](#included-extensions) to modify editing behaviour.\n\n```tsx {7-13} copy showLineNumbers\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          options: {\n            // Disable an included extension\n            bold: false,\n\n            // Configure an included extension\n            heading: { levels: [1, 2] },\n          },\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        contentEditable: true,\n        options: {\n          bold: false,\n          heading: { levels: [1, 2] },\n        },\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, world</p>\",\n    },\n    render: ({ body }) => body,\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n#### Included extensions\n\n- [`Blockquote`](https://tiptap.dev/docs/editor/extensions/nodes/blockquote)\n- [`Bold`](https://tiptap.dev/docs/editor/extensions/marks/bold)\n- [`BulletList`](https://tiptap.dev/docs/editor/extensions/nodes/bullet-list)\n- [`Code`](https://tiptap.dev/docs/editor/extensions/marks/code)\n- [`CodeBlock`](https://tiptap.dev/docs/editor/extensions/nodes/code-block)\n- [`Document`](https://tiptap.dev/docs/editor/extensions/nodes/document)\n- [`HardBreak`](https://tiptap.dev/docs/editor/extensions/nodes/hard-break)\n- [`Heading`](https://tiptap.dev/docs/editor/extensions/nodes/heading)\n- [`HorizontalRule`](https://tiptap.dev/docs/editor/extensions/nodes/horizontal-rule)\n- [`Italic`](https://tiptap.dev/docs/editor/extensions/marks/italic)\n- [`ListItem`](https://tiptap.dev/docs/editor/extensions/nodes/list-item)\n- [`ListKeymap`](https://tiptap.dev/docs/editor/extensions/functionality/listkeymap)\n- [`Link`](https://tiptap.dev/docs/editor/extensions/marks/link)\n- [`OrderedList`](https://tiptap.dev/docs/editor/extensions/nodes/ordered-list)\n- [`Paragraph`](https://tiptap.dev/docs/editor/extensions/nodes/paragraph)\n- [`Strike`](https://tiptap.dev/docs/editor/extensions/marks/strike)\n- [`Text`](https://tiptap.dev/docs/editor/extensions/nodes/text)\n- [`TextAlign`](https://tiptap.dev/docs/editor/extensions/functionality/textalign)\n- [`Underline`](https://tiptap.dev/docs/editor/extensions/marks/underline)\n\n### `renderMenu(props)`\n\nRender a custom menu for the field. Use with the [`<RichTextMenu>`](/docs/api-reference/components-rich-text-menu) component to arrange existing controls, or introduce new ones.\n\nReceives [Menu Render Props](#menu-render-props).\n\n```tsx {1, 9-23} copy showLineNumbers\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          renderMenu: ({ children }) => (\n            <RichTextMenu>\n              {/* Render default menu */}\n              {children}\n\n              <RichTextMenu.Group>\n                {/* Render a Blockquote control */}\n                <RichTextMenu.Blockquote />\n\n                {/* Render a custom control */}\n                <RichTextMenu.Control {/* ... */} />\n\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        renderMenu: ({ children }) => (\n          <RichTextMenu>\n            {children}\n            <RichTextMenu.Group>\n              <RichTextMenu.Control icon={<Quote />} />\n              <RichTextMenu.Control\n                icon={<Highlighter />}\n                onClick={() => {\n                  alert(\"Highlight clicked\");\n                }}\n              />\n            </RichTextMenu.Group>\n          </RichTextMenu>\n        ),\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, world</p>\",\n    },\n    render: ({ body }) => body,\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n### `renderInlineMenu(props)`\n\nRender a custom inline menu when using [`contentEditable`](#contenteditable). Use with the [`<RichTextMenu>`](/docs/api-reference/components-rich-text-menu) component to arrange existing controls, or introduce new ones.\n\nReceives [Menu Render Props](#menu-render-props).\n\n```tsx {1, 10-24} copy showLineNumbers\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          contentEditable: true,\n          renderInlineMenu: ({ children }) => (\n            <RichTextMenu>\n              {/* Render default menu */}\n              {children}\n\n              <RichTextMenu.Group>\n                {/* Render a Blockquote control */}\n                <RichTextMenu.Blockquote />\n\n                {/* Render a custom control */}\n                <RichTextMenu.Control {/* ... */} />\n\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<PuckPreview\n  config={{\n    components: {\n      Example: {\n        fields: {\n          body: {\n            type: \"richtext\",\n            contentEditable: true,\n            renderInlineMenu: ({ children }) => (\n              <RichTextMenu>\n                {children}\n                <RichTextMenu.Group>\n                  <RichTextMenu.Blockquote />\n                  <RichTextMenu.Control\n                    icon={<Highlighter />}\n                    onClick={() => {\n                      alert(\"Highlight clicked\");\n                    }}\n                  />\n                </RichTextMenu.Group>\n              </RichTextMenu>\n            ),\n          },\n        },\n        permissions: { drag: false, delete: false, duplicate: false },\n        render: ({ body }) => body,\n      },\n    },\n  }}\n  data={{\n    root: { props: {} },\n    content: [\n      {\n        type: \"Example\",\n        props: {\n          id: \"Example-1\",\n          body: \"<p>Hello, <strong>world</strong></p>\",\n        },\n      },\n    ],\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\n### `tiptap.extensions`\n\nProvide custom [Tiptap extensions](https://tiptap.dev/docs/editor/extensions/overview) to the editor to deeply customize behavior. See [included extensions](#included-extensions).\n\n```tsx {1, 10} copy showLineNumbers\nimport Emoji from \"@tiptap/extension-emoji\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          tiptap: {\n            extensions: [Emoji],\n          },\n        },\n      },\n    },\n  },\n};\n```\n\n<Callout type=\"info\">\n  TipTap maintains an extensive [library of\n  extensions](https://tiptap.dev/docs/editor/extensions/overview) and\n  documentation for [developing your\n  own](https://tiptap.dev/docs/editor/extensions/custom-extensions).\n</Callout>\n\n### `tiptap.selector`\n\nExtend the [default editor state](#included-state) to access additional editor information in the menu. See [Tiptap docs](https://tiptap.dev/docs/editor/getting-started/install/react#reacting-to-editor-state-changes).\n\n```tsx {9-15, 19-21} copy showLineNumbers\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          tiptap: {\n            selector: ({ editor, readOnly }) => ({\n              isHighlight: editor?.isActive(\"highlight\"),\n              canHighlight:\n                !readOnly && editor?.can().chain().toggleHighlight().run(),\n            }),\n          },\n          renderMenu: ({ editorState }) => (\n            <RichTextMenu>\n              <RichTextMenu.Group>\n                <button disabled={editorState?.canHighlight}>\n                  {editorState?.isHighlight ? \"Unhighlight\" : \"Highlight\"}\n                </button>\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n    },\n  },\n};\n```\n\n## Menu Render Props\n\nRender props for [`renderMenu()`](rendermenuprops) and [`renderInlineMenu()`](#renderinlinemenuprops) APIs.\n\n| Param                         | Example          | Type                |\n| ----------------------------- | ---------------- | ------------------- |\n| [`children`](#children)       | `<div />`        | ReactNode           |\n| [`editor`](#editor)           | `{}`             | Editor \\| null      |\n| [`editorState`](#editorstate) | `{isBold: true}` | EditorState \\| null |\n\n### `children`\n\nThe default menu items. Include this to extend the existing menu.\n\n```tsx {1, 9-19} copy showLineNumbers\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          renderMenu: ({ children }) => (\n            <RichTextMenu>\n              {/* Render default menu */}\n              {children}\n\n              <RichTextMenu.Group>\n                {/* Render a Blockquote control */}\n                <RichTextMenu.Blockquote />\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `editor`\n\nThe Tiptap [Editor Instance](https://tiptap.dev/docs/editor/api/editor). Use this to implement custom controls based on Puck's [included extensions](#included-extensions), and additional extensions provided by [`tiptap.extensions`](#tiptapextensions).\n\n```tsx {9-21} copy showLineNumbers\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          renderMenu: ({ editor }) => (\n            <RichTextMenu>\n              <RichTextMenu.Group>\n                <button\n                  onClick={() =>\n                    editor?.chain().focus().toggleBlockquote().run()\n                  }\n                >\n                  Quote\n                </button>\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `editorState`\n\nAccess editor state information without causing re-renders. Provides the [included state](#included-state), and additional state provided by [`tiptap.selector`](#tiptapselector).\n\n```tsx {9-13} copy showLineNumbers\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"richtext\",\n          renderMenu: ({ editorState }) => (\n            <RichTextMenu>\n              {editorState?.isBold ? \"Bold\" : \"Not bold\"}\n            </RichTextMenu>\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n#### Included state\n\n- `isAlignLeft`\n- `canAlignLeft`\n- `isAlignCenter`\n- `canAlignCenter`\n- `isAlignRight`\n- `canAlignRight`\n- `isAlignJustify`\n- `canAlignJustify`\n- `isBold`\n- `canBold`\n- `isItalic`\n- `canItalic`\n- `isUnderline`\n- `canUnderline`\n- `isStrike`\n- `canStrike`\n- `isInlineCode`\n- `canInlineCode`\n- `isBulletList`\n- `canBulletList`\n- `isOrderedList`\n- `canOrderedList`\n- `isCodeBlock`\n- `canCodeBlock`\n- `isBlockquote`\n- `canBlockquote`\n- `canHorizontalRule`\n\n### `readOnly`\n\nWhether or not the field is currently read-only.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/select.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# Select\n\nRender a `select` input with a list of options. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      textAlign: {\n        type: \"select\",\n        options: [\n          { label: \"Left\", value: \"left\" },\n          { label: \"Right\", value: \"right\" },\n        ],\n      },\n    },\n    render: ({ textAlign }) => {\n      return <p style={{ textAlign, margin: 0 }}>Hello, world</p>;\n    },\n  }}\n/>\n\n```tsx {5-11} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        textAlign: {\n          type: \"select\",\n          options: [\n            { label: \"Left\", value: \"left\" },\n            { label: \"Right\", value: \"right\" },\n          ],\n        },\n      },\n      render: ({ textAlign }) => {\n        return <p style={{ textAlign }}>Hello, world</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                 | Example                                               | Type     | Status   |\n| --------------------- | ----------------------------------------------------- | -------- | -------- |\n| [`type`](#type)       | `type: \"select\"`                                      | \"select\" | Required |\n| [`options`](#options) | `options: [{ label: \"Option 1\", value: \"option-1\" }]` | Object[] | Required |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"select\"` for Array fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        textAlign: {\n          type: \"select\",\n          options: [\n            { label: \"Left\", value: \"left\" },\n            { label: \"Right\", value: \"right\" },\n          ],\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `options`\n\nThe options for the select field. The `value` can be a String, Number or Boolean.\n\n```tsx {7-10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        textAlign: {\n          type: \"select\",\n          options: [\n            { label: \"Left\", value: \"left\" },\n            { label: \"Right\", value: \"right\" },\n          ],\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/slot.mdx",
    "content": "import {\n  ConfigPreview,\n  PuckPreview,\n  CodeBlockDrawer,\n} from \"@/docs/components/Preview\";\nimport { usePuck } from \"@/puck\";\n\n# Slot\n\nDefine a drag-and-drop area containing nested components. Extends [Base](base).\n\n- This field produces an array of [ComponentData](/docs/api-reference/data-model/component-data)\n- The array is transformed to a [render function](#render-function) before being provided to `render()`\n\nThis field does render any UI in the form section.\n\n<PuckPreview\n  label=\"Example\"\n  renderDrawer={() => {\n    return (\n      <CodeBlockDrawer getCode={(appState) => appState.data.content[0].props} />\n    );\n  }}\n  config={{\n    components: {\n      Example: {\n        fields: {\n          content: {\n            type: \"slot\",\n          },\n        },\n        render: ({ content: Content }) => {\n          return (\n            <div style={{ padding: 32 }}>\n              <Content />\n            </div>\n          );\n        },\n        permissions: {\n          duplicate: false,\n          delete: false,\n        },\n      },\n      Card: {\n        render: () => {\n          return (\n            <div\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                borderRadius: 4,\n                padding: 16,\n              }}\n            >\n              Hello, world\n            </div>\n          );\n        },\n      },\n    },\n  }}\n  data={{\n    content: [\n      {\n        type: \"Example\",\n        props: {\n          id: \"Example-1\",\n          content: [{ type: \"Card\", props: { id: \"Example-2\" } }],\n        },\n      },\n    ],\n    root: { props: {} },\n  }}\n/>\n\n```tsx {5-7,10} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      render: ({ content: Content }) => {\n        return <Content />;\n      },\n    },\n    Card: {\n      render: () => <div>Hello, world</div>,\n    },\n  },\n};\n```\n\n## TypeScript\n\nWhen using TypeScript, make sure to use the `Slot` type so the render prop is correctly transformed.\n\n```tsx\nimport { Slot } from \"@puckeditor/core\";\n\ntype Props = {\n  Example: {\n    content: Slot; // Converted from the ComponentData[] type into a render function\n  };\n};\n```\n\n## Params\n\n| Param                   | Example                      | Type   | Status   |\n| ----------------------- | ---------------------------- | ------ | -------- |\n| [`type`](#type)         | `type: \"slot\"`               | \"slot\" | Required |\n| [`allow`](#allow)       | `allow: [\"HeadingBlock\"]`    | Array  | -        |\n| [`disallow`](#disallow) | `disallow: [\"HeadingBlock\"]` | Array  | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"slot\"` for Slot fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Optional params\n\n### `allow`\n\nOnly allow specific components to be dragged into the slot. Will be overwritten by the [`allow` prop](#allow-1) provided to the render function.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n          allow: [\"HeadingBlock\"],\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n### `disallow`\n\nAllow all but specific components to be dragged into the slot. Any items in `allow` will override `disallow`. Will be overwritten by the [`disallow` prop](#disallow-1) provided to the render function.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n          disallow: [\"HeadingBlock\"],\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Render function\n\nSlot data is provided to `render()` as a render function (or component). Provide props to this function to customize the behavior of the slot at render-time.\n\n| Param                               | Example                      | Type                                 | Status |\n| ----------------------------------- | ---------------------------- | ------------------------------------ | ------ |\n| [`allow`](#allow-1)                 | `allow: [\"HeadingBlock\"]`    | Array                                | -      |\n| [`as`](#as)                         | `as: \"span\"`                 | ElementType                          | -      |\n| [`className`](#classname)           | `className: \"MyClass\"`       | String                               | -      |\n| [`collisionAxis`](#collisionaxis)   | `collisionAxis: \"x\"`         | String                               | -      |\n| [`disallow`](#disallow-1)           | `disallow: [\"HeadingBlock\"]` | Array                                | -      |\n| [`minEmptyHeight`](#minemptyheight) | `minEmptyHeight: \"256px\"`    | CSSProperties[\"minHeight\"] \\| number | -      |\n| [`ref`](#ref)                       | `ref: ref`                   | Ref                                  | -      |\n| [`style`](#style)                   | `style: {display: \"flex\"}`   | CSSProperties                        | -      |\n\n### `allow`\n\nOnly allow specific components to be dragged into the slot. Overrides the [`allow` parameter](#allow) provided to the field.\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content: Content }) => {\n        return <Content allow={[\"HeadingBlock\"]} />;\n      },\n    },\n  },\n};\n```\n\n### `as`\n\nRender a custom element or component for the slot.\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content: Content }) => {\n        return <Content as=\"table\" />;\n      },\n    },\n  },\n};\n```\n\n### `className`\n\nProvide a className to the rendered element. The default styles will still be applied.\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content: Content }) => {\n        return <Content className=\"MyComponent\" />;\n      },\n    },\n  },\n};\n```\n\n### `collisionAxis`\n\nConfigure which axis Puck will use for overlap collision detection.\n\nOptions:\n\n- `x` - detect collisions based their x-axis overlap\n- `y` - detect collisions based their y-axis overlap\n- `dynamic` - automatically choose an axis based on the direction of travel\n\nThe defaults are set based on the CSS layout of the parent:\n\n- grid: `dynamic`\n- flex (row): `x`\n- inline/inline-block: `x`\n- Everything else: `y`\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content: Content }) => {\n        return <Content collisionAxis=\"dynamic\" />;\n      },\n    },\n  },\n};\n```\n\n### `disallow`\n\nAllow all but specific components to be dragged into the slot. Any items in `allow` will override `disallow`.\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content }) => {\n        return <Content disallow={[\"HeadingBlock\"]} />;\n      },\n    },\n  },\n};\n```\n\n### `minEmptyHeight`\n\nThe minimum height of the slot when empty. Defaults to `128px`.\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return <Content minEmptyHeight=\"256px\" />;\n      },\n    },\n  },\n};\n```\n\n### `ref`\n\nA [React ref](https://react.dev/learn/manipulating-the-dom-with-refs), assigned to the root node of the slot.\n\n```tsx copy {7}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content: Content }) => {\n        const ref = useRef();\n\n        return <Content ref={ref} />;\n      },\n    },\n  },\n};\n```\n\n### `style`\n\nProvide a style attribute to the slot. The default styles will still be applied.\n\n```tsx copy {5}\nconst config = {\n  components: {\n    Example: {\n      render: ({ content: Content }) => {\n        return <Content style={{ display: \"flex\" }} />;\n      },\n    },\n  },\n};\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/text.mdx",
    "content": "import { Puck } from \"@/puck\";\nimport { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Callout } from \"nextra/components\";\n\n# Text\n\nRender a `text` input. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n    },\n    defaultProps: { title: \"Hello, world\" },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n```tsx {5-7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      render: ({ title }) => {\n        return <p>{title}</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                 | Example                         | Type    | Status   |\n| ------------------------------------- | ------------------------------- | ------- | -------- |\n| [`type`](#type)                       | `type: \"text\"`                  | \"text\"  | Required |\n| [`contentEditable`](#contentEditable) | `contentEditable: true`         | Boolean | -        |\n| [`placeholder`](#placeholder)         | `placeholder: \"Lorem ipsum...\"` | String  | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"text\"` for Text fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"text\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Optional params\n\n### contentEditable\n\nEnable inline text editing for this field. Defaults to `false`.\n\n<Callout type=\"warning\">\n  When setting `contentEditable`, your [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) prop will be converted to an [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) when rendered inside [`<Puck>`](/docs/api-reference/components/puck) (but not [`<Render>`](/docs/api-reference/components/render)). When using TypeScript, change your `string` to  `string | ReactNode`.\n</Callout>\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"text\",\n          contentEditable: true,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n        contentEditable: true,\n      },\n    },\n    defaultProps: {\n      title: \"Edit me inline\",\n    },\n    render: ({ title }) => {\n      return <div>{title}</div>;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n### Placeholder\n\nThe placeholder text to display when the field is empty.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"text\",\n          placeholder: \"Lorem ipsum...\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n        placeholder: \"Lorem ipsum...\",\n      },\n    },\n    render: ({ title }) => {\n      return <div>{title}</div>;\n    },\n  }}\n/>\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields/textarea.mdx",
    "content": "import { Puck } from \"@/puck\";\nimport { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Callout } from \"nextra/components\";\n\n# Textarea\n\nRender a `textarea` input. Extends [Base](base).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      description: {\n        type: \"textarea\",\n      },\n    },\n    render: ({ description }) => {\n      return <p>{description}</p>;\n    },\n    defaultProps: { description: \"Hello, world\" },\n  }}\n/>\n\n```tsx {5-7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"textarea\",\n        },\n      },\n      render: ({ description }) => {\n        return <p>{description}</p>;\n      },\n    },\n  },\n};\n```\n\n## Params\n\n| Param                                 | Example                         | Type       | Status   |\n| ------------------------------------- | ------------------------------- | ---------- | -------- |\n| [`type`](#type)                       | `type: \"textarea\"`              | \"textarea\" | Required |\n| [`contentEditable`](#contentEditable) | `contentEditable: true`         | Boolean    | -        |\n| [`placeholder`](#placeholder)         | `placeholder: \"Lorem ipsum...\"` | String     | -        |\n\n## Required params\n\n### `type`\n\nThe type of the field. Must be `\"textarea\"` for Textarea fields.\n\n```tsx {6} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"textarea\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Optional params\n\n### contentEditable\n\nEnable inline text editing for this field. Defaults to `false`.\n\n<Callout type=\"warning\">\n  When setting `contentEditable`, your [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String) prop will be converted to an [Object](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object) when rendered inside [`<Puck>`](/docs/api-reference/components/puck) (but not [`<Render>`](/docs/api-reference/components/render)). When using TypeScript, change your `string` to  `string | ReactNode`.\n</Callout>\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"textarea\",\n          contentEditable: true,\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      description: {\n        type: \"textarea\",\n        contentEditable: true,\n      },\n    },\n    defaultProps: {\n      description:\n        \"You can edit this in the preview section, too.\\nIt also supports line breaks.\",\n    },\n    render: ({ description }) => {\n      return <div>{description}</div>;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n### Placeholder\n\nThe placeholder text to display when the field is empty.\n\n```tsx {7} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        description: {\n          type: \"textarea\",\n          placeholder: \"Lorem ipsum...\",\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      description: {\n        type: \"textarea\",\n        placeholder: \"Lorem ipsum...\",\n      },\n    },\n    render: ({ description }) => {\n      return <div>{description}</div>;\n    },\n  }}\n/>\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/fields.mdx",
    "content": "# Fields\n\nA field represents a user input shown in the Puck interface.\n\n- [Base](fields/base) - The base type shared by all fields.\n- [Array](fields/array) - Render a list of items with a subset of fields.\n- [Custom](fields/custom) - Implement a field with a custom UI.\n- [External](fields/external) - Select data from a list, typically populated via a third-party API.\n- [Number](fields/number) - Render a `number` input.\n- [Object](fields/object) - Render a subset of fields.\n- [Radio](fields/radio) - Render a `radio` input with a list of options.\n- [RichText](fields/richtext) - Render a rich text editor.\n- [Select](fields/select) - Render a `select` input with a list of options.\n- [Slot](fields/slot) - Define an area containing nested components.\n- [Text](fields/text) - Render a `text` input.\n- [Textarea](fields/textarea) - Render a `textarea` input.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/migrate.mdx",
    "content": "---\ntitle: migrate\n---\n\n# migrate\n\nMigrate the [Data payload](/docs/api-reference/data-model/data) to the latest shape, automatically transforming deprecated data.\n\n```tsx copy showLineNumbers {7-10}\nimport { migrate } from \"@puckeditor/core\";\n\nmigrate(legacyData);\n```\n\n## Migrations\n\n### Root data to props\n\nMigrates any props stored on root data to the `props` object.\n\n**Before**\n\n```json\n{\n  \"root\": {\n    \"title\": \"Hello, world\"\n  }\n}\n```\n\n**After**\n\n```json\n{\n  \"root\": {\n    \"props\": { \"title\": \"Hello, world\" }\n  }\n}\n```\n\n### DropZones to slots\n\nMigrates all [DropZone](/docs/api-reference/components/drop-zone) data from [`zones`](/docs/api-reference/data-model/data#zones) to inline [slots](/docs/api-reference/fields/slot). Requires slots to be provided via the [`config`](#config) arg, where the field name for the slot matches the `zone` prop of the DropZone. See the [DropZone to slots migration guide](/docs/guides/migrations/dropzones-to-slots) for more information.\n\n**Before**\n\n```json showLineNumbers {10-20}\n{\n  \"content\": [\n    {\n      \"type\": \"Grid\",\n      \"props\": {\n        \"id\": \"Grid-12345\"\n      }\n    }\n  ],\n  \"zones\": {\n    \"Grid-12345:items\": [\n      {\n        \"type\": \"HeadingBlock\",\n        \"props\": {\n          \"id\": \"Heading-12345\",\n          \"title\": \"Hello, world\"\n        }\n      }\n    ]\n  }\n}\n```\n\n**After**\n\n```json showLineNumbers {7-15}\n{\n  \"content\": [\n    {\n      \"type\": \"Grid\",\n      \"props\": {\n        \"id\": \"Grid-12345\",\n        \"items\": [\n          {\n            \"type\": \"HeadingBlock\",\n            \"props\": {\n              \"id\": \"Heading-12345\",\n              \"title\": \"Hello, world\"\n            }\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n## Args\n\n| Param                                   | Example                                       | Type                                               | Status   |\n| --------------------------------------- | --------------------------------------------- | -------------------------------------------------- | -------- |\n| [`data`](#data)                         | `{ content: [{type: \"Heading\", props: {} }]}` | [Data](/docs/api-reference/data-model/data)        | Required |\n| [`config`](#config)                     | `{ components: {} }`                          | [Config](/docs/api-reference/configuration/config) | -        |\n| [`migrationOptions`](#migrationoptions) | `{ migrateDynamicZonesForComponent: {} }`     | [MigrationOptions](#migrationoptions-type)         | -        |\n\n### `data`\n\nThe legacy data you want to transform.\n\n### `config`\n\nA [config](/docs/api-reference/configuration/config) object. Required if migrating data with [slots](/docs/api-reference/fields/slot).\n\n### `migrationOptions`\n\nOptions to customize how data is migrated.\n\n#### `migrateDynamicZonesForComponent`\n\nAn object mapping component names to custom dropzone migration functions. The function will be called for any component with DropZones that don't have a slot field definition with a matching name in the config.\n\nEach migration function receives:\n\n- `props`: The current component props\n- `zones`: A record of zone names to their content\n\nThe function should return the updated component props with the migrated zone data.\n\n```tsx\nmigrate(legacyData, config, {\n  migrateDynamicZonesForComponent: {\n    Columns: (props, zones) => {\n      return {\n        ...props,\n        columns: Object.values(zones).map((zone) => ({\n          column: zone,\n        })),\n      };\n    },\n  },\n});\n```\n\n**Before**\n\n```json showLineNumbers {16-26}\n{\n  \"root\": {\n    \"props\": {\n      \"title\": \"Legacy Zones Migration\"\n    }\n  },\n  \"content\": [\n    {\n      \"type\": \"Columns\",\n      \"props\": {\n        \"columns\": [{}],\n        \"id\": \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be\"\n      }\n    }\n  ],\n  \"zones\": {\n    \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be:column-0\": [\n      {\n        \"type\": \"Text\",\n        \"props\": {\n          \"text\": \"Drop zone 1\",\n          \"id\": \"Text-c2b5c0a5-d76b-4120-8bb3-99934e119967\"\n        }\n      }\n    ]\n  }\n}\n```\n\n**After**\n\n```json showLineNumbers {11-23}\n{\n  \"root\": {\n    \"props\": {\n      \"title\": \"Legacy Zones Migration\"\n    }\n  },\n  \"content\": [\n    {\n      \"type\": \"Columns\",\n      \"props\": {\n        \"columns\": [\n          {\n            \"column\": [\n              {\n                \"type\": \"Text\",\n                \"props\": {\n                  \"text\": \"Drop zone 1\",\n                  \"id\": \"Text-c2b5c0a5-d76b-4120-8bb3-99934e119967\"\n                }\n              }\n            ]\n          }\n        ],\n        \"id\": \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be\"\n      }\n    }\n  ]\n}\n```\n\n## Returns\n\nThe updated [Data](/docs/api-reference/data-model/data) object.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/register-overlay-portal.mdx",
    "content": "---\ntitle: registerOverlayPortal\n---\n\n# registerOverlayPortal\n\nRegister a node as an [Overlay Portal](/docs/integrating-puck/overlay-portals), enabling interaction beneath the Puck overlay.\n\n```tsx copy\nimport { registerOverlayPortal } from \"@puckeditor/core\";\n\nconst MyComponent = () => {\n  const ref = useRef(null);\n\n  useEffect(() => registerOverlayPortal(ref.current), [ref.current]);\n\n  return <button ref={ref}>Clickable</button>;\n};\n```\n\n## Args\n\n| Param           | Example | Type                                                                        | Status |\n| --------------- | ------- | --------------------------------------------------------------------------- | ------ |\n| [`el`](#el)     | `div`   | [HTMLElement](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement) | -      |\n| [`opts`](#opts) | `{}`    | [Config](/docs/api-reference/configuration/config)                          | -      |\n\n### `el`\n\nThe element to turn into a portal. Will do nothing if `null` or `undefined`.\n\n### `opts`\n\n| Param                | Example | Type    | Status |\n| -------------------- | ------- | ------- | ------ |\n| `disableDrag`        | `false` | boolean | -      |\n| `disableDragOnFocus` | `false` | boolean | -      |\n\n#### `opts.disableDrag`\n\nDisable triggering a drag of the parent component when interacting with this element. Defaults to `false`.\n\n#### `opts.disableDragOnFocus`\n\nDisable triggering a drag of the parent component when interacting with this element once it is focused. Drag is disabled following a short delay after focusing. Defaults to `true`.\n\n## Returns\n\nA function to clean-up the portal.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/resolve-all-data.mdx",
    "content": "---\ntitle: resolveAllData\n---\n\n# resolveAllData\n\nUtility function to execute all [`resolveData` methods](/docs/api-reference/configuration/component-config#resolvedatadata-params) on a data payload outside of the [`<Puck>`](/docs/api-reference/components/puck) editor, returning the updated value.\n\n```tsx copy\nimport { resolveAllData } from \"@puckeditor/core\";\n\nconst updatedData = await resolveAllData(data, config);\n```\n\nThis is useful if you need to run your resolvers before passing your data to [`<Render>`](/docs/api-reference/components/render).\n\n## Args\n\n| Param    | Example              | Type                                               |\n| -------- | -------------------- | -------------------------------------------------- |\n| `data`   | `{}`                 | [Data](/docs/api-reference/data-model/data)        |\n| `config` | `{ components: {} }` | [Config](/docs/api-reference/configuration/config) |\n\n## Returns\n\nThe updated [Data](/docs/api-reference/data-model/data) object.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/set-deep.mdx",
    "content": "---\ntitle: setDeep\n---\n\n# setDeep\n\nA convenience utility for setting the value of a key deep within an object.\n\nUseful when implementing [field transforms](/docs/api-reference/field-transforms).\n\n```tsx\nimport { setDeep } from \"@puckeditor/core\";\n\nconst newData = setDeep(\n  {\n    object: {\n      array: [{ key: \"Hello, world\" }],\n    },\n  },\n  \"object.array[0].key\",\n  \"Goodbye, world\"\n);\n\nconsole.log(newData);\n// {\n//   object: {\n//     array: [{ key: \"Goodbye, world\" }],\n//   },\n// }\n```\n\n## Args\n\n| Param             | Example                 | Type            |\n| ----------------- | ----------------------- | --------------- |\n| [`input`](#input) | `{}`                    | Object \\| Array |\n| [`path`](#path)   | `\"object.array[0].key\"` | String          |\n| [`value`](#value) | `\"Hello, world\"`        | Any             |\n\n### `input`\n\nThe input object or array that contains the key to be updated.\n\n### `path`\n\nA dot-notated path that describes a key deep within an object or array.\n\nUses dot-notation and square brackets:\n\n- `.` deliminates sub-props in an [object field](/docs/api-reference/fields/object) (i.e. `a.b` represents `b` where `a` is `{b: \"Value\"}`)\n- `[*]` which donate an index in an [array field](/docs/api-reference/fields/array) where `*` is an integer (i.e. `a[0].b` represents `b` in the first item in array `a` with the value `[{b: \"Value\"}]`)\n\nCommonly provided by the [propPath parameter](/docs/api-reference/field-transforms#proppath) for field transforms.\n\n### `value`\n\nThe value to set the key to.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/transform-props.mdx",
    "content": "---\ntitle: transformProps\n---\n\n# transformProps\n\nTransform component props stored in a [Data payload](/docs/api-reference/data-model/data). This convenience method can be used for [prop renames and other data migrations](/docs/integrating-puck/data-migration).\n\nThis method will modify all data in [`content`](/docs/api-reference/data-model/data#content) and [`zones`](/docs/api-reference/data-model/data#zones).\n\n```tsx copy showLineNumbers {7-10}\nimport { transformProps } from \"@puckeditor/core\";\n\nconst data = {\n  content: [{ type: \"HeadingBlock\", props: { title: \"Hello, world\" } }],\n};\n\nconst updatedData = transformProps(data, {\n  // Rename `title` to `heading`\n  HeadingBlock: ({ title, ...props }) => ({ heading: title, ...props }),\n});\n\nconsole.log(updatedData);\n// { content: [{ type: \"HeadingBlock\", props: { heading: \"Hello, world\" } }] };\n```\n\n## Args\n\n| Param                       | Example                                | Type                                               | Status   |\n| --------------------------- | -------------------------------------- | -------------------------------------------------- | -------- |\n| [`data`](#data)             | `{}`                                   | [Data](/docs/api-reference/data-model/data)        | Required |\n| [`transforms`](#transforms) | `{ HeadingBlock: (props) => (props) }` | Object                                             | Required |\n| [`config`](#config)         | `{ components: {} }`                   | [Config](/docs/api-reference/configuration/config) | -        |\n\n### `data`\n\nThe [Data payload](/docs/api-reference/data-model/data) to be transformed.\n\n### `transforms`\n\nAn object describing the transform functions for each component defined in your [`config`](/docs/api-reference/configuration/config).\n\n- `root` is a reserved property, and can be used to update the [`root` component](/docs/api-reference/configuration/config#root) props.\n\n### `config`\n\nA [config](/docs/api-reference/configuration/config) object. Required if transforming data within [slots](/docs/api-reference/fields/slot).\n\n## Returns\n\nThe updated [Data](/docs/api-reference/data-model/data) object.\n\n## Notes\n\n- It's important to consider that data may include both components with old data and new data, and write your transform accordingly.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/use-get-puck.mdx",
    "content": "---\ntitle: useGetPuck\n---\n\n# useGetPuck\n\nA hook that returns a function that retrieves the latest [`PuckApi`](/docs/api-reference/puck-api) at call time. This is useful in callbacks, effects, or any logic that runs outside the component render lifecycle.\n\n```tsx copy\nimport { useGetPuck } from \"@puckeditor/core\";\n\nconst Example = () => {\n  const getPuck = useGetPuck();\n\n  const handleClick = useCallback(() => {\n    // Current PuckApi is always provided\n    const { appState } = getPuck();\n  }, [getPuck]);\n\n  return <button onClick={handleClick}>Click me</button>;\n};\n```\n\nIf you need to react to changes to `PuckApi`, try the [`usePuck` hook](use-puck).\n\n## Returns\n\nA function to retrieve the latest [`PuckApi`](/docs/api-reference/puck-api) data at call time.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/use-puck.mdx",
    "content": "---\ntitle: usePuck\n---\n\n# usePuck\n\nA hook for accessing the [`PuckApi`](/docs/api-reference/puck-api) as part of your React render lifecycle.. The best way to access `usePuck` is via the `createUsePuck()` factory.\n\n```tsx copy\nimport { createUsePuck } from \"@puckeditor/core\";\n\nconst usePuck = createUsePuck();\n\nconst Example = () => {\n  const type = usePuck((s) => s.selectedItem?.type || \"Nothing\");\n\n  return <h2>{type} selected</h2>;\n};\n```\n\nYou can also access `usePuck` as a direct export, but you won't be able to use [selectors](#selectordata), resulting in unwanted re-renders and degraded performance.\n\n## Args\n\n| Param                             | Example                      | Type     |\n| --------------------------------- | ---------------------------- | -------- |\n| [`selector(data)`](#selectordata) | `(s: PuckApi) => s.appState` | Function |\n\n### `selector(data)`\n\nA selector function that describes what `usePuck` returns. Receives [`PuckApi`](/docs/api-reference/puck-api) and returns anything. Be as granular as possible to minimize re-renders.\n\n```tsx\n// Good: only re-render when the `selectedItem` changes\nconst selectedItem = usePuck((s) => s.selectedItem);\n\n// Bad: re-render when anything changes\nconst { selectedItem } = usePuck();\nconst { selectedItem } = usePuck((s) => s);\n\n// Bad: selector creates a new object reference, causing an infinite comparison loop\nconst { selectedItem } = usePuck((s) => ({ ...s.selectedItem }));\n```\n\n## Returns\n\nWhatever is returned by the [`selector`](#selectordata).\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions/walk-tree.mdx",
    "content": "---\ntitle: walkTree\n---\n\n# walkTree\n\nRecursively walk the entire tree for the [`Data`](/docs/api-reference/data-model/data) or a single [`ComponentData`](/docs/api-reference/data-model/component-data) node, using a depth-first approach where the deepest [slots](/docs/api-reference/fields/slot) are processed first.\n\nReceives a callback function that is called once for each slot. You can optionally return a value to update the slot.\n\n```tsx\nimport { walkTree } from \"@puckeditor/core\";\n\n// Add the example prop to all children in the data\nconst newData = walkTree(data, config, (content) =>\n  content.map((child) => ({\n    ...child,\n    props: { ...child.props, example: \"Hello, world\" },\n  }))\n);\n\nconsole.log(newData);\n// {\n//   \"root\": {},\n//   \"content\": [\n//     {\n//       \"type\": \"Component\",\n//       \"props\": {\n//         \"id\": \"1234\",\n//         \"content\": [\n//           {\n//             \"type\": \"Child\",\n//             \"props\": { \"id\": \"5678\", \"example\": \"Hello, world\" }\n//           }\n//         ],\n//         \"example\": \"Hello, world\"\n//       }\n//     }\n//   ]\n// }\n```\n\n## Args\n\n| Param                                        | Example                            | Type                                                                                                          |\n| -------------------------------------------- | ---------------------------------- | ------------------------------------------------------------------------------------------------------------- |\n| [`data`](#data)                              | `{ root: {}, content: [] }`        | [Data](/docs/api-reference/data-model/data) \\| [ComponentData](/docs/api-reference/data-model/component-data) |\n| [`config`](#config)                          | `{ components: {} }`               | [Config](/docs/api-reference/configuration/config)                                                            |\n| [`callbackFn()`](#callbackfncontent-options) | `(content) => content.slice(0, 1)` | Function                                                                                                      |\n\n### `data`\n\nThe [`Data`](/docs/api-reference/data-model/data) or [`ComponentData`](/docs/api-reference/data-model/component-data) to traverse.\n\n### `config`\n\nA Puck [config](/docs/api-reference/configuration/config) object, used to determine which components contain slots.\n\n### `callbackFn(content, options)`\n\nA callback function called for each slot. Receives an array of [`ComponentData`](/docs/api-reference/data-model/component-data). Optionally returns an updated array of [`ComponentData`](/docs/api-reference/data-model/component-data) to update the content for this slot.\n\n#### Args\n\n| Param                 | Example                                         | Type                                                               |\n| --------------------- | ----------------------------------------------- | ------------------------------------------------------------------ |\n| [`content`](#content) | `[{ type: \"Heading\", props: {} }]`              | [`ComponentData[]`](/docs/api-reference/data-model/component-data) |\n| [`options`](#options) | `{ parentId: \"Flex-123\", propName: \"Content\" }` | object                                                             |\n\n##### `content`\n\nAn array of [`ComponentData`](/docs/api-reference/data-model/component-data), containing all the nodes for this slot.\n\n##### `options`\n\nAn object containing additional options\n\n##### `options.parentId`\n\nThe id of the parent component that defines this slot.\n\n##### `options.propName`\n\nThe name of the slot field.\n\n#### Returns\n\nOptionally return an updated array of [`ComponentData`](/docs/api-reference/data-model/component-data) objects.\n\n## Returns\n\nA new [`Data`](/docs/api-reference/data-model/data) or [`ComponentData`](/docs/api-reference/data-model/component-data) object populated with any values returned by the callbackFn.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/functions.mdx",
    "content": "# Functions\n\n- [migrate](functions/migrate) - Migrate a legacy [data payload](/docs/api-reference/data-model/data) to the latest shape.\n- [resolveAllData](functions/resolve-all-data) - Utility function to execute all [`resolveData` methods](/docs/api-reference/configuration/component-config#resolvedatadata-params) on a data payload.\n- [transformProps](functions/transform-props) - Transform component props stored in the [data payload](/docs/api-reference/data-model/data). Use this for migrations, like prop renames.\n- [useGetPuck](functions/use-get-puck) - A hook for accessing the latest [PuckApi](/docs/api-reference/puck-api) outside of the React render lifecycle.\n- [usePuck](functions/use-puck) - A hook for accessing the [PuckApi](/docs/api-reference/puck-api) inside your components.\n- [walkTree](functions/walk-tree) - Walk the tree recursively, modifying it if necessary.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/action-bar.mdx",
    "content": "---\ntitle: actionBar\n---\n\n# actionBar\n\nOverride the action bar. Use the [`<ActionBar>` component](/docs/api-reference/components/action-bar) to extend the default ActionBar UI.\n\n```tsx copy\nimport { ActionBar } from \"@puckeditor/core\";\n\nconst overrides = {\n  actionBar: ({ children, label }) => (\n    <ActionBar label={label}>\n      <ActionBar.Group>{children}</ActionBar.Group>\n    </ActionBar>\n  ),\n};\n```\n\n## Props\n\n| Prop                            | Example          | Type      |\n| ------------------------------- | ---------------- | --------- |\n| [`children`](#children)         | `<div />`        | ReactNode |\n| [`label`](#label)               | `\"HeadingBlock\"` | String    |\n| [`parentAction`](#parentAction) | `<div />`        | ReactNode |\n\n### `children`\n\nA fragment containing the default [actions](/docs/api-reference/components/action-bar-action). This should normally be rendered inside an [`<ActionBar.Group>`](/docs/api-reference/components/action-bar-group).\n\n### `label`\n\nThe default label for the action bar.\n\n### `parentAction`\n\nA single [`<ActionBar.Action>`](/docs/api-reference/components/action-bar-action) to select the current component's parent.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/component-overlay.mdx",
    "content": "---\ntitle: componentOverlay\n---\n\n# componentOverlay\n\nOverride the overlay shown on hover or selection of a component.\n\n```tsx copy\nconst overrides = {\n  overlay: ({ children }) => <div>{children}</div>,\n};\n```\n\n## Props\n\n| Prop                              | Example        | Type      |\n| --------------------------------- | -------------- | --------- |\n| [`children`](#children)           | `<div />`      | ReactNode |\n| [`componentId`](#componentid)     | `Heading-1234` | string    |\n| [`componentType`](#componenttype) | `Heading`      | string    |\n| [`hover`](#hover)                 | `false`        | boolean   |\n| [`isSelected`](#isselected)       | `false`        | boolean   |\n\n### `children`\n\nThe default node for the overlay.\n\n### `componentId`\n\nThe [id]() for the component underneath the overlay.\n\n### `componentType`\n\nThe [type]() for the component underneath the overlay.\n\n### `hover`\n\nWhether or not the the component for this overlay is hovered over.\n\nA parent will not be `true` if a child node is `true`.\n\n### `isSelected`\n\nWhether or not the the component for this overlay is selected.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/drawer-item.mdx",
    "content": "---\ntitle: drawerItem\n---\n\n# drawerItem\n\nOverride an item within the component drawer.\n\n```tsx copy\nconst overrides = {\n  drawerItem: ({ name }) => <div>{name}</div>,\n};\n```\n\n## Props\n\n| Prop                    | Example    | Type      |\n| ----------------------- | ---------- | --------- |\n| [`children`](#children) | `<div />`  | ReactNode |\n| [`name`](#name)         | `\"Button\"` | ReactNode |\n\n### `children`\n\nThe default node for the drawer item.\n\n### `name`\n\nThe name of the drawer item.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/drawer.mdx",
    "content": "---\ntitle: drawer\n---\n\n# drawer\n\nOverride the component drawer.\n\n```tsx copy\nconst overrides = {\n  drawer: ({ children }) => <div>{children}</div>,\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `children`\n\nThe default node for the component list.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/field-label.mdx",
    "content": "---\ntitle: fieldLabel\n---\n\n# fieldLabel\n\nOverride the label for all internal fields.\n\n```tsx copy\nconst overrides = {\n  fieldLabel: ({ children, label }) => (\n    <label>\n      <div>{label}</div>\n      {children}\n    </label>\n  ),\n};\n```\n\n## Props\n\nSee [FieldLabel](/docs/api-reference/components/field-label) component.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/field-types.mdx",
    "content": "---\ntitle: fieldTypes\n---\n\n# fieldTypes\n\nOverride each [field type](/docs/api-reference/fields).\n\n```tsx copy\nconst overrides = {\n  fieldTypes: {\n    number: ({ onChange }) => (\n      <input type=\"number\" onChange={(e) => onChange(e.currentTarget.value)} />\n    ),\n    text: ({ onChange }) => (\n      <input type=\"text\" onChange={(e) => onChange(e.currentTarget.value)} />\n    ),\n    // ...\n  },\n};\n```\n\nYou can specify a custom render method for each known [field type](/docs/api-reference/fields), or introduce completely new ones.\n\n## Render Props\n\nExtends the [`<AutoField>` API](/docs/api-reference/components/auto-field).\n\n| Prop                    | Example     | Type                                                           |\n| ----------------------- | ----------- | -------------------------------------------------------------- |\n| [`children`](#children) | `<input />` | ReactNode                                                      |\n| [`name`](#name)         | `\"title\"`   | string                                                         |\n| `...`                   | `{}`        | [`<AutoField>` API](/docs/api-reference/components/auto-field) |\n\n### `children`\n\nThe default node for this field type.\n\n### `name`\n\nThe name of the prop this field is rendering for.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/fields.mdx",
    "content": "---\ntitle: fields\n---\n\n# fields\n\nOverride the fields wrapper.\n\n```tsx copy\nconst overrides = {\n  fields: ({ children }) => <div>{children}</div>,\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `children`\n\nThe default node for the fields.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/header-actions.mdx",
    "content": "---\ntitle: headerActions\n---\n\n# headerActions\n\nOverride the header actions. Return a fragment so your items appear inline.\n\n```tsx copy\nconst overrides = {\n  headerActions: ({ children }) => (\n    <>\n      {children}\n      <button>Click me</button>\n    </>\n  ),\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `children`\n\nThe default node for the header actions, which includes the default publish button.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/header.mdx",
    "content": "---\ntitle: header\n---\n\n# header\n\nOverride the header.\n\n```tsx copy\nconst overrides = {\n  header: ({ actions }) => (\n    <header>\n      <span>My header</span>\n      <div>{actions}</div>\n    </header>\n  ),\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`actions`](#actions)   | `<div />` | ReactNode |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `actions`\n\nA node containing the [`headerActions`](header-actions).\n\n### `children`\n\nThe default node for the header.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/iframe.mdx",
    "content": "---\ntitle: iframe\n---\n\n# iframe\n\nOverride the root of the iframe.\n\n```tsx copy\nconst overrides = {\n  iframe: ({ children, document }) => {\n    useEffect(() => {\n      if (document) {\n        document.body.setAttribute(\"style\", \"background: hotpink;\");\n      }\n    }, [document]);\n\n    return <>{children}</>;\n  },\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`document`](#document) | `{}`      | Document  |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `document`\n\nThe document of the iframe window.\n\n### `children`\n\nThe default node for the iframe.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/outline.mdx",
    "content": "---\ntitle: outline\n---\n\n# outline\n\nOverride the outline.\n\n```tsx copy\nconst overrides = {\n  outline: ({ children }) => <div>{children}</div>,\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `children`\n\nThe default node for the outline.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/preview.mdx",
    "content": "---\ntitle: preview\n---\n\n# preview\n\nOverride the drag-and-drop preview.\n\n```tsx copy\nconst overrides = {\n  preview: ({ children }) => <div>{children}</div>,\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `children`\n\nThe default node for the preview.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides/puck.mdx",
    "content": "---\ntitle: puck\n---\n\n# puck\n\nOverride the Puck children. This is the equivalent of passing in [`children`](/docs/api-reference/components/puck#children) to the [`<Puck>`](/docs/api-reference/components/puck) component.\n\n```tsx copy\nconst overrides = {\n  puck: ({ children }) => <div>{children}</div>,\n};\n```\n\n## Props\n\n| Prop                    | Example   | Type      |\n| ----------------------- | --------- | --------- |\n| [`children`](#children) | `<div />` | ReactNode |\n\n### `children`\n\nThe default node for the [`<Puck>`](/docs/api-reference/components/puck) children.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/overrides.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# Overrides\n\n<Callout>\n  The overrides API is highly experimental and is likely to experience breaking\n  changes.\n</Callout>\n\nAn object describing render functions to override the default Puck interface.\n\n```tsx copy\nconst overrides = {\n  header: () => <header>My header</header>,\n};\n```\n\n## Available overrides\n\n- [`actionBar`](overrides/action-bar): Override the action bar.\n- [`componentOverlay`](overrides/component-overlay): Override the overlay shown on hover or selection of a component.\n- [`drawer`](overrides/drawer): Override the component drawer.\n- [`drawerItem`](overrides/drawer-item): Override an item within the component drawer.\n- [`fields`](overrides/fields): Override the fields wrapper.\n- [`fieldLabel`](overrides/field-label): Override the [field labels](/docs/api-reference/configuration/field-label).\n- [`fieldTypes`](overrides/field-types): Override each [field type](/docs/api-reference/fields).\n- [`header`](overrides/header): Override the header.\n- [`headerActions`](overrides/header-actions): Override the header actions. Return a fragment so your items appear inline.\n- [`iframe`](overrides/iframe): Override the root of the iframe. Useful for injecting styles.\n- [`outline`](overrides/outline): Override the outline.\n- [`preview`](overrides/preview): Override the drag-and-drop preview.\n- [`puck`](overrides/puck): Override the Puck children. This is the equivalent of passing in [`children`](/docs/api-reference/components/puck#children) to the [`<Puck>`](/docs/api-reference/components/puck) component.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/permissions.mdx",
    "content": "# Permissions\n\nPermissions enable the [toggling of Puck features](/docs/integrating-puck/feature-toggling). There are three types of permissions:\n\n1. **Global permissions** are shared across the entire Puck application. Controlled via the `permissions` prop on the `<Puck>` component.\n2. **Component permissions** allow feature control across for all instances of a given component type. Controlled by the `permissions` component config API.\n3. **Dynamic permissions** enable control after each data change, and are ideal for per-instance component permissions. Controlled by the `resolvePermissions` component config API.\n\n## APIs\n\n### Setting permissions\n\n1. Global [`permissions` prop](/docs/api-reference/components/puck#permissions)\n2. Component [`permissions` parameter](/docs/api-reference/configuration/component-config#permissions)\n3. Component [`resolvePermissions` parameter](/docs/api-reference/configuration/component-config#resolvepermissionsdata-params)\n\n### Extending permissions\n\n1. `getPermissions`\n2. `refreshPermissions`\n\n## Supported permissions\n\n| Param                     | Example           | Type    | Default     | Status |\n| ------------------------- | ----------------- | ------- | ----------- | ------ |\n| [`delete`](#delete)       | `delete: true`    | Boolean | `true`      | -      |\n| [`drag`](#drag)           | `drag: true`      | Boolean | `true`      | -      |\n| [`duplicate`](#duplicate) | `duplicate: true` | Boolean | `true`      | -      |\n| [`edit`](#edit)           | `edit: true`      | Boolean | `true`      | -      |\n| [`insert`](#insert)       | `insert: true`    | Boolean | `true`      | -      |\n| [`...custom`](#custom)    | `myPerm: true`    | Boolean | `undefined` | -      |\n\n### `delete`\n\nEnable deletion of components.\n\n### `drag`\n\nEnable component dragging. Disabling this will lock the component in place, but other components can still be dragged around it.\n\n### `duplicate`\n\nEnable duplication of components.\n\n### `edit`\n\nEnable field editing. This is the same as setting `readOnly` to `true` for all fields.\n\n### `insert`\n\nEnable insertion of new components. Disabling this will disable the component list items.\n\n### `...custom`\n\nCustom permissions enable you to control your own functionality when [extending Puck](/docs/extending-puck/composition).\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/plugin.mdx",
    "content": "# Plugin\n\nA plugin is an extension that enhances the capabilities of Puck.\n\n```tsx showLineNumbers copy {3-8, 14}\nimport { Puck } from \"@puckeditor/core\";\n\nconst MyPlugin = {\n  name: \"my-plugin\", // Globally unique name\n  label: \"My plugin\", // Plugin rail label\n  icon: <svg />, // Icon for plugin rail\n  render: () => <div>My plugin</div>, // UI for plugin panel\n};\n\nexport function Editor() {\n  return (\n    <Puck\n      // ...\n      plugins={[MyPlugin]}\n    />\n  );\n}\n```\n\n## Params\n\n| Prop                                      | Example                                    | Type                                                    | Status |\n| ----------------------------------------- | ------------------------------------------ | ------------------------------------------------------- | ------ |\n| [`icon`](#icon)                           | `icon: <Heading1 />`                       | ReactNode                                               | -      |\n| [`label`](#label)                         | `label: \"Audit\"`                           | String                                                  | -      |\n| [`fieldTransforms`](#fieldtransforms)     | `fieldTransforms: { text: () => <div /> }` | [FieldTransforms](/docs/api-reference/field-transforms) | -      |\n| [`mobilePanelHeight`](#mobilepanelheight) | `mobilePanelHeight: \"min-content\"`         | `\"toggle\" \\| \"min-content\"`                             | -      |\n| [`name`](#name)                           | `name: \"audit\"`                            | String                                                  | -      |\n| [`render`](#render)                       | `render: () => <div />`                    | `() => ReactElement`                                    | -      |\n| [`overrides`](#overrides)                 | `overrides: { fields: () => <div /> }`     | [Overrides](/docs/api-reference/overrides)              | -      |\n\n## Optional params\n\n### `icon`\n\nReact node displayed alongside `label` in the plugin rail.\n\n```tsx {4}\nconst MyPlugin = {\n  icon: <MyIcon />,\n};\n```\n\nPuck uses [Lucide icons](https://lucide.dev/icons/). You can use [lucide-react](https://lucide.dev/guide/packages/lucide-react) to choose a similar icon, if desired.\n\n### `label`\n\nHuman-readable label shown in the plugin rail. Falls back to `name`.\n\n```tsx {2}\nconst MyPlugin = {\n  label: \"My plugin\", // Human-readable label\n};\n```\n\n### `fieldTransforms`\n\nTransform the value of a field before rendering in the editor. Implements the [Field Transforms API](/docs/api-reference/field-transforms).\n\n```tsx {2-5}\nconst MyPlugin = {\n  fieldTransforms: {\n    // Make all props powered by \"text\" field pink in the editor\n    text: ({ value }) => <span style={{ color: \"hotpink\" }}>{value}</span>,\n  },\n};\n```\n\n### `mobilePanelHeight`\n\nControls how the plugin panel behaves on mobile screens.\n\n- `\"toggle\"` (default): show a smaller plugin panel with a maximize button to expand it.\n- `\"min-content\"`: size the panel to the height of its content.\n\n### `name`\n\nA globally unique name for the plugin. When `render` is provided, `name` is required for the plugin to appear within the plugin rail. The currently open plugin is tracked in [`ui.plugin.current`](/docs/api-reference/data-model/app-state#uiplugincurrent).\n\n```tsx {2}\nconst MyPlugin = {\n  name: \"my-plugin\", // Globally unique identifier\n};\n```\n\n### `overrides`\n\nOverride the render functions for specific portions of the Puck UI. Implements the [`overrides` API](/docs/api-reference/overrides).\n\n```tsx {2-5}\nconst MyPlugin = {\n  overrides: {\n    // Make all drawer items pink\n    drawerItem: ({ name }) => <div style={{ color: \"hotpink\" }}>{name}</div>,\n  },\n};\n```\n\n### `render`\n\nRender function for the plugin panel, shown when the plugin is selected in the plugin rail.\n\n```tsx {2}\nconst MyPlugin = {\n  render: () => <div>My plugin</div>,\n};\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/plugins/blocks-plugin.mdx",
    "content": "---\ntitle: blocksPlugin\n---\n\n# blocksPlugin\n\nShow the component drawer for dragging new components onto the canvas.\n\n```tsx copy\nimport { Puck, blocksPlugin } from \"@puckeditor/core\";\n\nconst blocks = blocksPlugin();\n\nexport function Editor() {\n  return <Puck plugins={[blocks]} />;\n}\n```\n\n**This plugin is included by default.** Explicitly including it allows you to reorder plugins in the plugin rail.\n\n## Params\n\nThis plugin does not accept any params.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/plugins/fields-plugin.mdx",
    "content": "---\ntitle: fieldsPlugin\n---\n\n# fieldsPlugin\n\nShow the fields for the currently selected component. On desktop, the fields will appear in the right-hand sidebar. On mobile, they will appear in the plugin rail.\n\n```tsx copy\nimport { Puck, fieldsPlugin } from \"@puckeditor/core\";\n\nconst fields = fieldsPlugin();\n\nexport function Editor() {\n  return <Puck plugins={[fields]} />;\n}\n```\n\n**This plugin is included by default.** Explicitly including it allows you to reorder plugins in the plugin rail, and configure behavior.\n\n## Params\n\n| Param            | Example                  | Type              | Status |\n| ---------------- | ------------------------ | ----------------- | ------ |\n| `desktopSideBar` | `desktopSideBar: \"left\"` | \"left\" \\| \"right\" | -      |\n\n## Optional params\n\n### `desktopSideBar`\n\nDisplay the plugin in the left or right sidebar on desktop. This field is `\"right\"` by default.\n\n```tsx copy\nconst fields = fieldsPlugin({\n  desktopSideBar: \"left\",\n});\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/plugins/legacy-side-bar-plugin.mdx",
    "content": "---\ntitle: legacySideBarPlugin\n---\n\n# legacySideBarPlugin\n\nHide the plugin rail and show a single sidebar with stacked \"Components\" and \"Outline\" sections. This will not affect mobile behavior.\n\n```tsx copy\nimport { Puck, legacySideBarPlugin } from \"@puckeditor/core\";\n\nconst legacySideBar = legacySideBarPlugin();\n\nexport function Editor() {\n  return <Puck plugins={[legacySideBar]} />;\n}\n```\n\n## Params\n\nThis plugin does not accept any params.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/plugins/outline-plugin.mdx",
    "content": "---\ntitle: outlinePlugin\n---\n\n# outlinePlugin\n\nShow an outline of the components currently on the canvas.\n\n```tsx copy\nimport { Puck, outlinePlugin } from \"@puckeditor/core\";\n\nconst outline = outlinePlugin();\n\nexport function Editor() {\n  return <Puck plugins={[outline]} />;\n}\n```\n\n**This plugin is included by default.** Explicitly including it allows you to change the order in the plugin rail.\n\n## Params\n\nThis plugin does not accept any params.\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/puck-api.mdx",
    "content": "---\ntitle: PuckApi\n---\n\nimport { Callout } from \"nextra/components\";\n\n# PuckApi\n\n`PuckApi` exposes Puck's internals to enable extension and modification to Puck's core behavior. It can be accessed by the [`usePuck`](/docs/api-reference/functions/use-puck) and [`useGetPuck`](/docs/api-reference/functions/use-get-puck) hooks.\n\n<Callout type=\"info\">\n  `PuckApi` can currently only be accessed through\n  [composition](/docs/extending-puck/composition), [UI\n  overrides](/docs/extending-puck/ui-overrides) or [custom\n  fields](/docs/extending-puck/custom-fields).\n</Callout>\n\n## Params\n\n| Param                                                             | Example                                            | Type                                                           |\n| ----------------------------------------------------------------- | -------------------------------------------------- | -------------------------------------------------------------- |\n| [`appState`](#appstate)                                           | `{ data: {}, ui: {} }`                             | [AppState](/docs/api-reference/data-model/app-state)           |\n| [`dispatch`](#dispatchaction)                                     | `(action: PuckAction) => void`                     | Function                                                       |\n| [`getItemBySelector`](#getitembyselectorselector)                 | `() => ({ type: \"Heading\", props: {} })`           | Function                                                       |\n| [`getItemById`](#getitembyidid)                                   | `() => ({ type: \"Heading\", props: {} })`           | Function                                                       |\n| [`getSelectorForId`](#getselectorforidid)                         | `() => ({ index: 0, zone: 'Flex-123:children'  })` | Function                                                       |\n| [`getPermissions`](#getpermissionsparams)                         | `() => ({ delete: true  })`                        | Function                                                       |\n| [`history`](#history)                                             | `{}`                                               | Object                                                         |\n| [`refreshPermissions`](#refreshpermissionsparams)                 | `() => void`                                       | Function                                                       |\n| [`resolveDataById`](#resolvedatabyidid-trigger)                   | `() => void`                                       | Function                                                       |\n| [`resolveDataBySelector`](#resolvedatabyselectorselector-trigger) | `() => void`                                       | Function                                                       |\n| [`selectedItem`](#selecteditem)                                   | `{ type: \"Heading\", props: {id: \"my-heading\"} }`   | [ComponentData](/docs/api-reference/data-model/data#content-1) |\n\n### `appState`\n\nThe current [application state](/docs/api-reference/data-model/app-state) for this Puck instance.\n\n```tsx\nconsole.log(appState.data);\n// { content: [], root: {}, zones: {} }\n```\n\n### `dispatch(action)`\n\nExecute an [action](/docs/api-reference/actions) to mutate the Puck [application state](/docs/api-reference/data-model/app-state).\n\n```tsx\ndispatch({\n  type: \"setUi\",\n  ui: {\n    leftSideBarVisible: false,\n  },\n});\n```\n\n### `getItemBySelector(selector)`\n\nGet an item's [`ComponentData`](/docs/api-reference/data-model/component-data) by its [selector](/docs/api-reference/data-model/item-selector).\n\n```tsx\ngetItemBySelector({\n  index: 0,\n  zone: \"Flex-123:children\", // The \"children\" slot field in the component with id \"Flex-123\"\n});\n// { type: \"HeadingBlock\", props: {} }\n```\n\n### `getItemById(id)`\n\nGet an item's [`ComponentData`](/docs/api-reference/data-model/component-data) by its component id.\n\n```tsx\ngetItemById(\"HeadingBlock-123\");\n// { type: \"HeadingBlock\", props: {} }\n```\n\n### `getParentById(id)`\n\nGet an item's parent [`ComponentData`](/docs/api-reference/data-model/component-data) using the child component's ID.\n\n### `getSelectorForId(id)`\n\nGet an item's [selector](/docs/api-reference/data-model/app-state#uiitemselector) by its component id.\n\n```tsx\ngetSelectorForId(\"HeadingBlock-123\");\n// { index: 0, zone: \"Flex-123:children\" }\n```\n\n### `getPermissions(params)`\n\nGet global, component or resolved dynamic [permissions](/docs/api-reference/permissions).\n\n```tsx\ngetPermissions();\n// { delete: true, edit: true }\n```\n\n#### Params\n\n| Param  | Example                                           | Type    |\n| ------ | ------------------------------------------------- | ------- |\n| `item` | `{ type: \"HeadingBlock\", props: { id: \"1234\" } }` | Object  |\n| `root` | `false`                                           | Boolean |\n| `type` | `\"HeadingBlock\"`                                  | String  |\n\n##### `item`\n\nSpecify `item` to retrieve the permissions for a given component instance, resolving any dynamic permissions for that component, as set by the [`resolvePermissions` parameter](/docs/api-reference/configuration/component-config#resolvepermissionsdata-params).\n\n```tsx\ngetPermissions({\n  item: { type: \"HeadingBlock\", props: { id: \"Heading-1234\" } }, // Get resolved permissions for Heading-1234\n});\n// { delete: false }\n```\n\nThe `getPermissions` function will be redefined when after resolving dynamic permissions, so it's generally required to wrap it in a `useEffect` hook:\n\n```tsx\nconst [myPermissions, setMyPermissions] = useState(getPermissions());\n\nuseEffect(() => {\n  setMyPermissions(getPermissions());\n}, [getPermissions]);\n```\n\n##### `root`\n\nSpecify `root` to retrieve the permissions for the `root`, as set by the [`permissions` parameter](/docs/api-reference/configuration/component-config#permissions).\n\n```tsx\ngetPermissions({ root: true });\n// { delete: false }\n```\n\n##### `type`\n\nSpecify `type` to retrieve the permissions for a given component type, as set by the [`permissions` parameter](/docs/api-reference/configuration/component-config#permissions).\n\n```tsx\ngetPermissions({ type: \"HeadingBlock\" });\n// { delete: false }\n```\n\n### `history`\n\nThe `history` API provides programmatic access to the undo/redo [AppState](/docs/api-reference/data-model/app-state) history.\n\n| Param                                 | Example                             | Type                           |\n| ------------------------------------- | ----------------------------------- | ------------------------------ |\n| [`back`](#historyback)                | `() => void`                        | Function                       |\n| [`forward`](#historyforward)          | `() => void`                        | Function                       |\n| [`hasPast`](#historyhaspast)          | `true`                              | Boolean                        |\n| [`hasFuture`](#historyhasfuture)      | `false`                             | Boolean                        |\n| [`histories`](#historyhistories)      | `[{ id: 'abc123', data: {} }]`      | [History](#history-params)\\[\\] |\n| [`index`](#historyindex)              | `5`                                 | Number                         |\n| [`setHistories`](#sethistories)       | `setHistories: (histories) => void` | Function                       |\n| [`setHistoryIndex`](#sethistoryindex) | `setHistoryIndex: (index) => void`  | Function                       |\n\n#### `history.back()`\n\nA function to move the app state back through the [histories](#historyhistories).\n\n#### `history.forward()`\n\nA function to move the app state forward through the [histories](#historyhistories).\n\n#### `history.hasPast`\n\nA boolean describing whether or not the present app state has past history items.\n\n#### `history.hasFuture`\n\nA boolean describing whether or not the present app state has future history items.\n\n#### `history.histories`\n\nAn array describing the recorded history as `History` objects.\n\n##### `History` params\n\n| Param   | Example  | Type                                                 |\n| ------- | -------- | ---------------------------------------------------- |\n| `state` | `{}`     | [AppState](/docs/api-reference/data-model/app-state) |\n| `id`    | `abc123` | String                                               |\n\n###### `state`\n\nThe [app state](/docs/api-reference/data-model/app-state) payload for this history entry.\n\n###### `id`\n\nAn optional ID for this history entry.\n\n#### `history.index`\n\nThe index of the currently selected history in [`history.histories`](#historyhistories)\n\n#### `setHistories`\n\nA function to set the history state.\n\n```tsx\nsetHistories([]); // clears all history\n```\n\n#### `setHistoryIndex`\n\nA function to set current history index.\n\n```tsx\nsetHistoryIndex(2);\n```\n\n### `refreshPermissions(params)`\n\nForce the permissions to refresh, running all [`resolvePermissions` functions](/docs/api-reference/configuration/component-config#resolvepermissionsdata-params) and skipping the cache.\n\n```tsx\nresolvePermissions(); // Refresh all permissions\n```\n\n#### Params\n\n| Param  | Example                                           | Type    |\n| ------ | ------------------------------------------------- | ------- |\n| `item` | `{ type: \"HeadingBlock\", props: { id: \"1234\" } }` | Object  |\n| `root` | `false`                                           | Boolean |\n| `type` | `\"HeadingBlock\"`                                  | String  |\n\n##### `item`\n\nSpecify `item` to refresh the permissions for a given component instance only.\n\n```tsx\nrefreshPermissions({\n  item: { type: \"HeadingBlock\", props: { id: \"Heading-1234\" } }, // Force refresh the resolved permissions for Heading-1234\n});\n```\n\n##### `root`\n\nSpecify `root` to refresh the permissions for the `root` only.\n\n```tsx\nrefreshPermissions({ root: true });\n```\n\n##### `type`\n\nSpecify `type` to refresh the permissions for all components of a given component type.\n\n```tsx\nrefreshPermissions({ type: \"HeadingBlock\" });\n```\n\n### `resolveDataById(id, trigger)`\n\nRuns a component's [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) by id for a given [trigger](/docs/api-reference/configuration/component-config#paramstrigger).\n\n```tsx\nresolveDataById(\"Heading-1234\", \"replace\");\n```\n\n### `resolveDataBySelector(selector, trigger)`\n\nRuns a component's [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) by selector for a given [trigger](/docs/api-reference/configuration/component-config#paramstrigger).\n\n```tsx\nresolveDataBySelector({ zone: \"Flex-1234:items\", index: 0 }, \"insert\");\n```\n\n### `selectedItem`\n\nThe currently selected item, as defined by `appState.ui.itemSelector`.\n\n```tsx\nconsole.log(selectedItem);\n// { type: \"Heading\", props: {id: \"my-heading\"} }\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/api-reference/theming.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# Theming\n\n<Callout type=\"info\">\n  Theming in Puck is currently limited in functionality, and being explored via\n  [#139 on GitHub](https://github.com/puckeditor/puck/issues/139).\n</Callout>\n\nCSS properties for theming the default Puck user interface.\n\n## Properties\n\n| Param                                                             | Example |\n| ----------------------------------------------------------------- | ------- |\n| [`--puck-font-family`](#--puck-font-family)                       | `Arial` |\n| [`--puck-font-family-monospaced`](#--puck-font-family-monospaced) | `Menlo` |\n\n### `--puck-font-family`\n\nThe font family used for the Puck interface. Must be used with the `no-external` bundle that stops Puck from loading the default font.\n\n```css\n/* Load bundle without existing font */\n@import \"@puckeditor/core/no-external.css\";\n\n:root {\n  --puck-font-family: Arial;\n}\n```\n\n### `--puck-font-family-monospaced`\n\nThe font family used for monospaced elements of the Puck interface.\n\n```css\n/* Monospaced fonts don't use external files, so the default bundle is safe */\n@import \"@puckeditor/core/puck.css\";\n\n:root {\n  --puck-font-family-monospaced: Menlo;\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/_meta.js",
    "content": "const menu = {\n  composition: {},\n  \"custom-fields\": {},\n  \"field-transforms\": {},\n  \"internal-puck-api\": {},\n  theming: { title: \"Theming\" },\n  plugins: {},\n  \"ui-overrides\": {},\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/composition.mdx",
    "content": "import { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# Composition\n\nPuck uses compositional patterns enable completely custom editor interfaces. [See an example](https://demo.puckeditor.com/custom-ui/edit/).\n\n## Using composition\n\nComposition can be achieved by providing [`children`](/docs/api-reference/components/puck#children) to the [`<Puck>` component](/docs/api-reference/components/puck):\n\n```tsx showLineNumbers copy\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <div\n        style={{ display: \"grid\", gridTemplateColumns: \"1fr 2fr\", gridGap: 16 }}\n      >\n        <div>\n          {/* Render the drag-and-drop preview */}\n          <Puck.Preview />\n        </div>\n        <div>\n          {/* Render the component list */}\n          <Puck.Components />\n        </div>\n      </div>\n    </Puck>\n  );\n}\n```\n\n<PuckPreview\n  config={{\n    components: {\n      HeadingBlock: {\n        render: () => {\n          return <span>Hello, world</span>;\n        },\n      },\n    },\n  }}\n  data={{\n    content: [{ type: \"HeadingBlock\", props: { id: \"HeadingBlock-1\" } }],\n    root: { props: {} },\n  }}\n>\n  <div style={{ display: \"grid\", gridTemplateColumns: \"1fr 2fr\", gridGap: 16 }}>\n    <div>\n      <Puck.Components />\n    </div>\n    <div>\n      <Puck.Preview />\n    </div>\n  </div>\n</PuckPreview>\n\n## Compositional components\n\nPuck exposes its core components, allowing you to compose them together to create new layouts:\n\n- [`<Puck.Components>`](/docs/api-reference/components/puck-components) - A draggable list of components.\n- [`<Puck.Fields>`](/docs/api-reference/components/puck-fields) - The fields for the currently selected item.\n- [`<Puck.Outline>`](/docs/api-reference/components/puck-outline) - An interactive outline.\n- [`<Puck.Preview>`](/docs/api-reference/components/puck-preview) - A drag-and-drop preview.\n\nThe internal UI for these components can also be changed by implementing [UI overrides](/docs/extending-puck/overrides) or [theming](theming).\n\n### Helper components\n\nPuck also exposes helper components for even deeper customization:\n\n- [`<Drawer>`](/docs/api-reference/components/drawer) - A reference list of items that can be dragged into a droppable area, normally `<Puck.Preview>`.\n- [`<Drawer.Item>`](/docs/api-reference/components/drawer-item) - An item that can be dragged from a `<Drawer>`.\n- [`<FieldLabel>`](/docs/api-reference/components/field-label) - A styled label for creating inputs.\n\n## Further reading\n\n- [Internal Puck API](/docs/extending-puck/internal-puck-api)\n- [UI overrides](/docs/extending-puck/ui-overrides)\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/custom-fields.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\nimport { AutoField, FieldLabel } from \"@/puck\";\n\n# Custom Fields\n\nPuck can be extended with completely custom fields for different use-cases.\n\n## Creating a custom field\n\nCreating a custom field is possible using the [`custom` field type](/docs/api-reference/fields/custom):\n\n```tsx copy showLineNumbers {5-15}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: ({ name, onChange, value }) => (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n              style={{ border: \"1px solid black\", padding: 4 }}\n            />\n          ),\n        },\n      },\n      render: ({ title }) => {\n        return <p>{title}</p>;\n      },\n    },\n  },\n};\n```\n\nThe [`onChange` function](/docs/api-reference/fields/custom#onchangevalue-ui) updates the Puck data payload for the field name, in this case \"title\".\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        render: ({ name, onChange, value }) => {\n          return (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                padding: 4,\n              }}\n            />\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n## Adding a label\n\nYou can add your own label, but it's recommended to use the [`<FieldLabel>` component](/docs/api-reference/components/field-label) provided by Puck to seamlessly integrate into the Puck field UI.\n\n```tsx copy showLineNumbers {1, 11-13}\nimport { FieldLabel } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          label: \"Label Example\",\n          render: ({ field }) => (\n            <FieldLabel label={field.label}>\n              <input {/*...*/} />\n            </FieldLabel>\n          ),\n        },\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        label: \"Label Example\",\n        render: ({ field, name, onChange, value }) => {\n          return (\n            <FieldLabel label={field.label}>\n              <input\n                defaultValue={value}\n                name={name}\n                onChange={(e) => onChange(e.currentTarget.value)}\n                style={{\n                  background: \"white\",\n                  border: \"1px solid black\",\n                  padding: 4,\n                }}\n              />\n            </FieldLabel>\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n## Rendering Puck fields internally\n\nUse the [`<AutoField>` component](/docs/api-reference/components/auto-field) to render Puck fields within your custom field.\n\n```tsx copy showLineNumbers {1, 12-16}\nimport { AutoField } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          label: \"Label Example\",\n          render: ({ field, value, onChange }) => (\n            <FieldLabel label={field.label}>\n              <AutoField\n                field={{ type: \"text\" }}\n                onChange={(value) => onChange(value)}\n                value={value}\n              />\n            </FieldLabel>\n          ),\n        },\n        // ...\n      },\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"custom\",\n        label: \"AutoField Example\",\n        render: ({ field, value, onChange }) => {\n          return (\n            <FieldLabel label={field.label}>\n              <AutoField\n                field={{ type: \"text\" }}\n                value={value}\n                onChange={onChange}\n              />\n            </FieldLabel>\n          );\n        },\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <p style={{ margin: 0 }}>{title}</p>;\n    },\n  }}\n/>\n\n## Updating the UI state\n\nThe [`onChange` function](/docs/api-reference/fields/custom#onchangevalue-ui) can also be used to modify the [Puck UI state](/docs/api-reference/data-model/app-state#ui) at the same time as updating the field value:\n\n```tsx copy showLineNumbers {14,15}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        title: {\n          type: \"custom\",\n          render: ({ name, onChange, value }) => (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) =>\n                onChange(\n                  e.currentTarget.value,\n                  // Close the left side bar when this field is changed\n                  { leftSideBarVisible: false }\n                )\n              }\n              style={{ border: \"1px solid black\", padding: 4 }}\n            />\n          ),\n        },\n      },\n      render: ({ title }) => {\n        return <p>{title}</p>;\n      },\n    },\n  },\n};\n```\n\n## Further reading\n\n- [The `<AutoField>` API reference](/docs/api-reference/components/auto-field)\n- [The `<FieldLabel>` API reference](/docs/api-reference/components/field-label)\n- [The `custom` field API reference](/docs/api-reference/fields/custom)\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/field-transforms.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# Field Transforms\n\nPuck lets you modify props before rendering in the editor via the [`FieldTransforms` API](/docs/api-reference/field-transforms).\n\nUse this API to implement custom rendering behavior for specific field types, which can be used to implement features such as inline text editing.\n\n<Callout type=\"info\">\n  Field transforms only apply to components rendered in `<Puck>` and will not be applied to `<Render>`.\n</Callout>\n\n## Implementing a transform\n\nSpecify a transforms object for the fields you want to modify before rendering:\n\n```tsx\nconst fieldTransforms = {\n  text: ({ value }) => <div>Value: {value}</div>, // Wrap all text field props in divs\n};\n\nconst Example = () => <Puck fieldTransforms={fieldTransforms} />;\n```\n\n## Making it interactive\n\nCombine transforms with [Overlay Portals](/docs/integrating-puck/overlay-portals) to make them interactive.\n\n```tsx\nimport { registerOverlayPortal } from \"@puckeditor/core\";\n\nconst EditableText = ({ value }) => {\n  const ref = useRef(null);\n\n  useEffect(() => {\n    if (ref.current) {\n      // Register the element as an overlay portal\n      registerOverlayPortal(ref.current);\n    }\n  }, [ref.current]);\n\n  return (\n    // Mark the element as editable for inline text editing\n    <p ref={ref} contentEditable>\n      {value}\n    </p>\n  );\n};\n\nconst fieldTransforms = {\n  text: EditableText,\n};\n\nconst Example = () => <Puck fieldTransforms={fieldTransforms} />;\n```\n\n## Define new fields\n\nAs with [field type overrides](/docs/extending-puck/ui-overrides#introducing-new-field-types), field transforms let you define your own field types:\n\n```tsx\nconst fieldTransforms = {\n  example: () => <div />,\n};\n```\n\n## Distributing field transforms as plugins\n\nDistribute transforms as plugins to package up custom behavior.\n\n```tsx\nconst plugin = {\n  fieldTransforms: {\n    example: ({ value }) => <div>{value}</div>, // Wrap all example fields with divs\n  },\n\n  // This example combines transforms with overrides\n  overrides: {\n    fieldTypes: {\n      example: () => <input />, // Define a field interface\n    },\n  },\n};\n\nconst Example = () => <Puck plugins={[plugin]} />;\n```\n\n## Further reading\n\n- [Field Transforms API reference](/docs/api-reference/field-transforms)\n- [Overlay Portals API reference](/docs/integrating-puck/overlay-portals)\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/internal-puck-api.mdx",
    "content": "---\ntitle: Internal Puck API\n---\n\n# Internal Puck API\n\nPuck exposes its internal API as [`PuckApi`](/docs/api-reference/puck-api) for extending Puck with custom functionality within [custom fields](/docs/extending-puck/custom-fields), [compositional interfaces](/docs/extending-puck/composition) or [UI overrides](/docs/extending-puck/ui-overrides).\n\n## Accessing the internal API\n\nYou can access [`PuckApi`](/docs/api-reference/puck-api) via two hooks:\n\n- [`usePuck`](/docs/api-reference/functions/use-puck) - returns `PuckApi` as part of your component render lifecycle\n- [`useGetPuck`](/docs/api-reference/functions/use-get-puck) - returns a function to access the latest `PuckApi` at call time\n\n### Within the render lifecycle\n\nTo access the API within your render lifecycle, use the `usePuck` hook. You can use a selector to limit re-rendering to a specific part of the API.\n\n```tsx copy\nimport { createUsePuck } from \"@puckeditor/core\";\n\nconst usePuck = createUsePuck();\n\nconst Example = () => {\n  // Use a selector so you only re-render when the selected type changes\n  const type = usePuck((s) => s.selectedItem?.type || \"Nothing\");\n\n  return <h2>{type} selected</h2>;\n};\n```\n\nSee the [`usePuck`](/docs/api-reference/functions/use-puck) docs for a full API reference.\n\n### Outside of the render lifecycle\n\nOften it's not necessary to re-render your component when the PuckApi changes. Puck provides the `useGetPuck` hook for accessing the latest `PuckApi` at call time.\n\n```tsx copy\nimport { useGetPuck } from \"@puckeditor/core\";\n\nconst Example = () => {\n  const getPuck = useGetPuck();\n\n  const handleClick = useCallback(() => {\n    // Get the latest PuckApi value\n    const { appState } = getPuck();\n\n    console.log(appState);\n  }, [getPuck]);\n\n  return <button onClick={handleClick}>Click me</button>;\n};\n```\n\nSee the [`useGetPuck`](/docs/api-reference/functions/use-get-puck) docs for a full API reference.\n\n## Usage in practice\n\nGenerally, you'll want to combine this with composition, UI overrides or custom fields.\n\nHere's an example using the internal API to log the page data as JSON while retaining the standard Puck UI:\n\n```tsx\nimport { Puck, createUsePuck } from \"@puckeditor/core\";\n\nconst usePuck = createUsePuck();\n\nconst JSONLogger = ({ children }) => {\n  const appState = usePuck((s) => s.appState);\n\n  useEffect(() => {\n    console.log(appState);\n  }, [appState]);\n\n  return <>{children}</>;\n};\n\nexport function Editor() {\n  return (\n    // Render the Puck context\n    <Puck>\n      <JSONLogger>\n        {/* Since we're overriding Puck's children, we restore the standard Puck UI */}\n        <Puck.Layout />\n      </JSONLogger>\n    </Puck>\n  );\n}\n```\n\n## Further reading\n\n- [`PuckApi` API reference](/docs/api-reference/puck-api)\n- [`usePuck` API reference](/docs/api-reference/functions/use-puck)\n- [`useGetPuck` API reference](/docs/api-reference/functions/use-get-puck)\n- [`<Puck.Layout>` API reference](/docs/api-reference/components/puck-layout)\n- [Composition](/docs/extending-puck/composition)\n- [Custom fields](/docs/extending-puck/custom-fields)\n- [UI overrides](/docs/extending-puck/ui-overrides)\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/plugins.mdx",
    "content": "# Plugin API\n\nimport { Callout } from \"nextra/components\";\n\nThe [plugin API](/docs/api-reference/plugins) enables developers to share extensions to Puck.\n\nPlugins can display dedicated UI in the **Plugin Rail** on the left-hand side of the screen (bottom on mobile), configure their own [overrides](ui-overrides), or apply [field transforms](field-transforms).\n\n## Official plugins\n\n### Core plugins\n\nThese plugins are included in the core package:\n\n- [`blocks`](/docs/api-reference/plugins/blocks-plugin): show the component drawer for dragging components onto the canvas\n- [`fields`](/docs/api-reference/plugins/fields-plugin): render the fields for the currently selected component\n- [`outline`](/docs/api-reference/plugins/outline-plugin): display an outline of the current page structure\n- [`legacy-side-bar`](/docs/api-reference/plugins/legacy-side-bar-plugin): disable the plugin rail in favor of stacked \"Components\" / \"Outline\" sections\n\n### Enhancements\n\nPuck provides official plugins that can be installed for common use-cases:\n\n- [`ai`](https://puckeditor.com/docs/ai/overview): Use AI to generate pages using your own components.\n- [`emotion-cache`](https://github.com/puckeditor/puck/tree/main/packages/plugin-emotion-cache): Inject emotion cache into the Puck iframe.\n- [`heading-analyzer`](https://github.com/puckeditor/puck/tree/main/packages/plugin-heading-analyzer): Analyze the heading outline of your page and be warned when you're not respecting WCAG 2 accessibility standards.\n\nPlease see the [awesome-puck repo](https://github.com/puckeditor/awesome-puck) for a full list of community plugins.\n\n## Loading a Plugin\n\nTo load a plugin, provide it to the [`plugins` prop](/docs/api-reference/components/puck#plugins) on the `<Puck>` component.\n\n```tsx showLineNumbers {2,8}\nimport { Puck } from \"@puckeditor/core\";\nimport myPlugin from \"my-puck-plugin\";\n\nexport function Editor() {\n  return (\n    <Puck\n      // ...\n      plugins={[myPlugin]}\n    />\n  );\n}\n```\n\n## Developing a Plugin\n\nIf you're familiar with Puck, you can likely already build a Puck plugin. See the [Plugin API reference](/docs/api-reference/plugins) for a full breakdown of available APIs.\n\n### Rendering UI in the Plugin Rail\n\nPlugins can render dedicated UI in a panel shown by the Plugin Rail. To add a plugin to the rail, create a new plugin and provide some parameters:\n\n```tsx showLineNumbers copy\nimport { Coffee } from \"lucide-react\";\n\nconst myPlugin = {\n  name: \"my-plugin\", // Globally unique name\n  label: \"My Plugin\", // Human-readable name shown in the rail\n  icon: <Coffee />, // Icon shown in the rail (use lucide to match Puck)\n  render: () => <div>My plugin UI</div>, // Component rendered in plugin panel\n};\n```\n\nYou can leverage the [internal Puck API](internal-puck-api) to integrate Puck behavior with your plugin:\n\n```tsx showLineNumbers copy {2, 4, 10-14}\nimport { Coffee } from \"lucide-react\";\nimport { createUsePuck } from \"@puckeditor/core\";\n\nconst usePuck = createUsePuck();\n\nconst myPlugin = {\n  name: \"my-plugin\",\n  label: \"My Plugin\",\n  icon: <Coffee />,\n  render: () => {\n    const type = usePuck((s) => s.selectedItem?.type || \"Nothing\");\n\n    return <h2>{type} selected</h2>;\n  },\n};\n```\n\n### Transforming fields\n\nPlugins support [Field Transforms](/docs/extending-puck/field-transforms), enabling you to modify prop data before it's rendered in the `<Puck>` preview.\n\n```tsx showLineNumbers copy\nconst plugin = {\n  fieldTransforms: {\n    // Make all props powered by \"text\" field pink in the canvas\n    text: ({ value }) => <span style={{ color: \"hotpink\" }}>{value}</span>,\n  },\n};\n```\n\n### Overriding the UI\n\nPlugins support [UI Overrides](/docs/extending-puck/ui-overrides), enabling you to override discrete section of the Puck interface.\n\n```tsx showLineNumbers copy\nconst plugin = {\n  overrides: {\n    // Make all drawer items pink\n    drawerItem: ({ name }) => <div style={{ color: \"hotpink\" }}>{name}</div>,\n  },\n};\n```\n\n<Callout type=\"info\">\n\n<b>Override currying</b>\n\nPlugin overrides are rendered in the order they are defined. Unless otherwise specified, all overrides are _curried_, meaning that the return node of one plugin will be passed as `children` to the next plugin.\n\nThis may result in some incompatible plugin combinations. To improve your chance of building a widely compatible plugin, consider:\n\n1. Implementing as few override methods as you need\n2. Always rendering `children` if possible\n\n</Callout>\n\n### Introducing new field types\n\nBoth the field transforms and overrides let you introduce [entirely new field types](/docs/extending-puck/ui-overrides#introducing-new-field-types). Plugins can combine this functionality to bundle up new field behavior in a convenient package.\n\nThis example uses [Overlay Portals](overlay-portals) to create an interactive rich text field that can modified directly in the editor preview.\n\n```tsx showLineNumbers copy {6-8, 12-20}\nimport { registerOverlayPortal } from \"@puckeditor/core\";\n\nconst plugin = {\n  overrides: {\n    // Add a richText field type\n    fieldTypes: {\n      richText: ({ name, value }) => <input name={name} value={value} />,\n    },\n  },\n  fieldTransforms: {\n    // Wrap the value in a span, create an overlay portal, and make it editable\n    richText: ({ value }) => {\n      const handleInput = useCallback(() => {}, []); // Implement your input behavior\n\n      return (\n        <span ref={registerOverlayPortal} contentEditable onInput={handleInput}>\n          {value}\n        </span>\n      );\n    },\n  },\n};\n```\n\n## Further reading\n\n- [Plugin API reference](/docs/api-reference/plugin)\n- [FieldTransforms API reference](/docs/api-reference/field-transforms)\n- [UI Overrides](/docs/extending-puck/ui-overrides)\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/theming/_meta.js",
    "content": "const menu = {\n  overview: {},\n  fonts: {},\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/theming/fonts.mdx",
    "content": "# Fonts\n\nPuck uses the [Inter typeface family](https://rsms.me/inter/) by default, loaded via a CDN.\n\nIt's possible to change the font, or provide your own version of Inter if you need to host it locally.\n\n## Load your own font file\n\nTo load your own font file, use the `no-external.css` bundle instead of the primary one.\n\n```css\n/* @import \"@puckeditor/core/puck.css\"; */\n@import \"@puckeditor/core/no-external.css\";\n```\n\n## Changing the font family\n\nTo change the font family Puck uses, change the [`--puck-font-family`](/docs/api-reference/theming#--puck-font-family) CSS property:\n\n```css\n/* @import \"@puckeditor/core/puck.css\"; */\n@import \"@puckeditor/core/no-external.css\";\n\n:root {\n  --puck-font-family: Arial;\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/theming/overview.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# Theming\n\n<Callout type=\"info\">\n  Theming in Puck is currently limited in functionality, and being explored via\n  [#139 on GitHub](https://github.com/puckeditor/puck/issues/139).\n</Callout>\n\nThe [Theming API](/docs/api-reference/theming) supports:\n\n- [Fonts](fonts): Change the font family, or font loading mechanism.\n\nFor further changes to the Puck interface, use the [composition](../composition) and [UI overrides](../ui-overrides) APIs.\n\n## Further reading\n\n- [Theming API reference](/docs/api-reference/theming)\n- [Composition](../composition.mdx)\n- [UI overrides](../ui-overrides.mdx)\n"
  },
  {
    "path": "apps/docs/pages/docs/extending-puck/ui-overrides.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# UI overrides\n\n<Callout>\n  The overrides API is highly experimental and is likely to experience breaking\n  changes.\n</Callout>\n\nOverrides allow you to change how Puck renders its default interface. It can be used with or without [composition](/docs/extending-puck/composition).\n\nThere are many different overrides available. See the [`overrides` API reference](/docs/api-reference/overrides) for the full list.\n\n## Implementing an override\n\nUse the [`overrides` prop](/docs/api-reference/components/puck#overrides) to implement an override:\n\n```tsx showLineNumbers copy {7-12}\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck\n      // ...\n      overrides={{\n        // Render a custom element for each item in the component list\n        drawerItem: ({ name }) => (\n          <div style={{ backgroundColor: \"hotpink\" }}>{name}</div>\n        ),\n      }}\n    />\n  );\n}\n```\n\n## Overriding field types\n\nYou can override all fields of certain type by specifying the [`fieldTypes` override](/docs/api-reference/overrides/field-types).\n\n```tsx showLineNumbers copy {8-18}\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck\n      // ...\n      overrides={{\n        fieldTypes: {\n          // Override all text fields with a custom input\n          text: ({ name, onChange, value }) => (\n            <input\n              defaultValue={value}\n              name={name}\n              onChange={(e) => onChange(e.currentTarget.value)}\n              style={{ border: \"1px solid black\", padding: 4 }}\n            />\n          ),\n        },\n      }}\n    />\n  );\n}\n```\n\n## Introducing new field types\n\nSpecify new field types to expose new fields to your components.\n\n```tsx showLineNumbers copy {9}\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck\n      // ...\n      overrides={{\n        fieldTypes: {\n          myField: ({ name, onChange, value }) => <div />,\n        },\n      }}\n    />\n  );\n}\n```\n\n<Callout type=\"info\">\n  <b>TypeScript consideration</b>: When introducing new field types with\n  TypeScript, you'll need to extend the available field types with the `fields`\n  key in your `Config` type generic.\n</Callout>\n\n## Examples\n\n### Custom publish button\n\nA common use case is to override the Puck header. You can either use the [`header` override](/docs/api-reference/overrides/header) to change the entire header, or use the [`headerActions` override](/docs/api-reference/overrides/header-actions) to inject new controls into the header and change the publish button.\n\nHere's an example that also leverage the [internal Puck API](/docs/extending-puck/internal-puck-api) to replace the default publish button with a custom one:\n\n```tsx showLineNumbers copy {10-30}\nimport { Puck, createUsePuck } from \"@puckeditor/core\";\n\nconst usePuck = createUsePuck();\n\nconst save = () => {};\n\nexport function Editor() {\n  return (\n    <Puck\n      // ...\n      overrides={{\n        headerActions: ({ children }) => {\n          const appState = usePuck((s) => s.appState);\n\n          return (\n            <>\n              <button\n                onClick={() => {\n                  save(appState.data);\n                }}\n              >\n                Save\n              </button>\n\n              {/* Render default header actions, such as the default Button */}\n              {/*{children}*/}\n            </>\n          );\n        },\n      }}\n    />\n  );\n}\n```\n\n## Further reading\n\n- [`overrides` API reference](/docs/api-reference/overrides)\n- [Composition](/docs/extending-puck/composition)\n- [Internal Puck API](/docs/extending-puck/internal-puck-api)\n"
  },
  {
    "path": "apps/docs/pages/docs/getting-started.mdx",
    "content": "# Getting Started\n\n## Installation\n\nInstall the package\n\n```sh npm2yarn copy\nnpm i @puckeditor/core --save\n```\n\nOr generate a Puck application using a [recipe](https://github.com/puckeditor/puck#recipes)\n\n```sh copy\nnpx create-puck-app my-app\n```\n\n## Render the editor\n\n```jsx copy filename=\"Editor.jsx\"\nimport { Puck } from \"@puckeditor/core\";\nimport \"@puckeditor/core/puck.css\";\n\n// Create Puck component config\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        children: {\n          type: \"text\",\n        },\n      },\n      render: ({ children }) => {\n        return <h1>{children}</h1>;\n      },\n    },\n  },\n};\n\n// Describe the initial data\nconst initialData = {};\n\n// Save the data to your database\nconst save = (data) => {};\n\n// Render Puck editor\nexport function Editor() {\n  return <Puck config={config} data={initialData} onPublish={save} />;\n}\n```\n\n## Render the page\n\n```jsx copy filename=\"Page.jsx\"\nimport { Render } from \"@puckeditor/core\";\n\nexport function Page() {\n  return <Render config={config} data={data} />;\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/guides/_meta.js",
    "content": "const menu = {\n  migrations: {\n    title: \"Migrations\",\n  },\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/guides/migrations/_meta.js",
    "content": "const menu = {\n  \"dropzones-to-slots\": {\n    title: \"DropZones to Slots\",\n  },\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/guides/migrations/dropzones-to-slots.mdx",
    "content": "---\ntitle: Migrating - DropZones to Slots\n---\n\n# How to migrate from DropZones to Slots\n\nThis guide will help you migrate from [`DropZones`](/docs/api-reference/components/drop-zone) to [Slots](/docs/api-reference/fields/slot).\n\n---\n\n[Slot fields](/docs/api-reference/fields/slot) replace the [`<DropZone>` component](/docs/api-reference/components/drop-zone), introducing an inline data model that supports [`defaultProps`](/docs/api-reference/configuration/component-config#defaultprops), [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) and [Server Components](/docs/integrating-puck/server-components) out-of-the-box.\n\n## Replace DropZone instances\n\nReplace your `<DropZone>` instances with [slot fields](/docs/api-reference/fields/slot) and the slot [render function](/docs/api-reference/fields/slot#render-function).\n\n**Before**\n\n```tsx {5}\nconst config = {\n  components: {\n    Example: {\n      render: () => {\n        return <DropZone zone=\"items\" allow={[\"HeadingBlock\"]} />;\n      },\n    },\n  },\n};\n```\n\n**After**\n\n```tsx {5-7, 10, 13}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        items: {\n          type: \"slot\",\n        },\n      },\n      defaultProps: {\n        items: [], // Slots support defaultProps and other APIs like resolveData\n      },\n      render: ({ items: Items }) => {\n        return <Items allow={[\"HeadingBlock\"]} />; // Slots support most DropZone APIs\n      },\n    },\n  },\n};\n```\n\n## The data model\n\nThe slot data model is inline and recursive. This means that instead of storing data in a global `zones` object, data is stored as arrays of [ComponentData](/docs/api-reference/data-model/component-data) as a prop.\n\n**Before**\n\n```json showLineNumbers {10-20}\n{\n  \"content\": [\n    {\n      \"type\": \"Grid\",\n      \"props\": {\n        \"id\": \"Grid-12345\"\n      }\n    }\n  ],\n  \"zones\": {\n    \"Grid-12345:items\": [\n      {\n        \"type\": \"HeadingBlock\",\n        \"props\": {\n          \"id\": \"Heading-12345\",\n          \"title\": \"Hello, world\"\n        }\n      }\n    ]\n  }\n}\n```\n\n**After**\n\n```json showLineNumbers {7-15}\n{\n  \"content\": [\n    {\n      \"type\": \"Grid\",\n      \"props\": {\n        \"id\": \"Grid-12345\",\n        \"items\": [\n          {\n            \"type\": \"HeadingBlock\",\n            \"props\": {\n              \"id\": \"Heading-12345\",\n              \"title\": \"Hello, world\"\n            }\n          }\n        ]\n      }\n    }\n  ]\n}\n```\n\n### Migrating legacy data\n\nFor new slots, you don't need to do anything. If you're migrating existing DropZones to slots, you will need to migrate your the data accordingly. Puck provides the [`migrate()` helper](/docs/api-reference/functions/migrate) to help with this:\n\n```tsx\nimport { migrate } from \"@puckeditor/core\";\nimport config from \"puck.config.tsx\";\n\nconst newData = migrate(legacyData, config);\n```\n\nThis will migrate any existing zone in `zones` where you have defined a `slot` with the same name.\n\n### Migrating dynamic zones to slots\n\nTo migrate DropZone data with dynamic zone names (e.g., those generated when rendering DropZones in a loop), use the [`migrateDynamicZonesForComponent`](/docs/api-reference/functions/migrate#migratedynamiczonesforcomponent) option in the [`migrate()` helper](/docs/api-reference/functions/migrate).\n\n```tsx\nconst newData = migrate(legacyData, config, {\n  migrateDynamicZonesForComponent: {\n    Columns: (props, zones) => {\n      return {\n        ...props,\n        columns: Object.values(zones).map((zone) => ({\n          column: zone,\n        })),\n      };\n    },\n  },\n});\n```\n\n## Further reading\n\n- [The `slot` field API](/docs/api-reference/fields/slot)\n- [The `DropZone` component](/docs/api-reference/components/drop-zone)\n"
  },
  {
    "path": "apps/docs/pages/docs/index.mdx",
    "content": "# Introduction\n\nWelcome to the Puck documentation!\n\n## What is Puck?\n\nPuck is a modular, open-source visual editor for React.js. You can use Puck to build custom drag-and-drop experiences with your own application and React components.\n\nBecause Puck is just a React component, it plays well with all React.js environments, including Next.js. You own your data and there's no vendor lock-in.\n\nPuck is also licensed under MIT, making it suitable for both internal systems and commercial applications.\n\n## Main Features\n\n| Feature                                                                   | Description                                                                                                          |\n| ------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------- |\n| [Component Configuration](/docs/integrating-puck/component-configuration) | Integrate your own components with Puck by providing render functions and configuring fields that map to your props. |\n| [Root Configuration](/docs/integrating-puck/root-configuration)           | Customize the root component that wraps all other Puck components.                                                   |\n| [Multi-column Layouts](/docs/integrating-puck/multi-column-layouts)       | Create multi-column layouts using nested components. Now supports advanced CSS layouts.                              |\n| [Categories](/docs/integrating-puck/categories)                           | Group your components in the side bar.                                                                               |\n| [Dynamic Props](/docs/integrating-puck/dynamic-props)                     | Dynamically set props after user input and mark fields as read-only                                                  |\n| [Dynamic Fields](/docs/integrating-puck/dynamic-fields)                   | Dynamically set fields based on user input                                                                           |\n| [External Data Sources](/docs/integrating-puck/external-data-sources)     | Load content from a third-party CMS or other data source                                                             |\n| [Server Components](/docs/integrating-puck/server-components)             | Opt-in support for React Server Components                                                                           |\n| [Data Migration](/docs/integrating-puck/data-migration)                   | Migrate between breaking Puck releases and your own breaking prop changes                                            |\n| [Viewports](/docs/integrating-puck/viewports)                             | Preview and edit your content in a same-origin iframe to simulate different viewports                                |\n| [Feature Toggling](/docs/integrating-puck/feature-toggling)               | Toggle Puck features, like duplication or deletion, via the permissions API.                                         |\n\n## Get Support\n\nIf you have any questions about Puck, please open a [GitHub issue](https://github.com/puckeditor/puck/issues) or join us on [Discord](https://discord.gg/D9e4E3MQVZ).\n\nOr [book a discovery call](https://app.cal.com/chrisvxd/puck-enquiry/) for hands-on support and consultancy.\n\n## License\n\nPuck is licensed under MIT.\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/_meta.js",
    "content": "const menu = {\n  \"component-configuration\": {},\n  \"root-configuration\": {},\n  \"multi-column-layouts\": {},\n  categories: {},\n  \"rich-text-editing\": {},\n  \"dynamic-props\": {},\n  \"dynamic-fields\": {},\n  \"external-data-sources\": {},\n  \"server-components\": {},\n  \"data-migration\": {},\n  viewports: {},\n  \"feature-toggling\": {},\n};\n\nexport default menu;\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/categories.mdx",
    "content": "# Categories\n\nCategories allow you to group components in the left side bar.\n\n## Creating categories\n\nUse the [`categories` API](/docs/api-reference/configuration/config#categories) to define the component categories.\n\n```tsx {2-6} copy showLineNumbers\nconst config = {\n  categories: {\n    typography: {\n      components: [\"HeadingBlock\", \"ParagraphBlock\"],\n    },\n  },\n  // ...\n};\n```\n\nComponents can appear in separate categories:\n\n```tsx /HeadingBlock/ copy showLineNumbers\nconst config = {\n  categories: {\n    typography: {\n      components: [\"HeadingBlock\", \"ParagraphBlock\"],\n    },\n    foundational: {\n      components: [\"HeadingBlock\"],\n    },\n  },\n  // ...\n};\n```\n\nYou can also change the title, collapse and hide categories:\n\n```tsx {5,6,10} copy showLineNumbers\nconst config = {\n  categories: {\n    typography: {\n      components: [\"HeadingBlock\", \"ParagraphBlock\"],\n      title: \"Text\",\n      defaultExpanded: false, // Collapse this category by default\n    },\n    foundational: {\n      components: [\"HeadingBlock\"],\n      visible: false, // Mark this category as hidden\n    },\n  },\n  // ...\n};\n```\n\n## The \"other\" category\n\nAny uncategorized components will be grouped in the `other` category. This will be visible by default. It respects the same API as other categories.\n\n```tsx {6-8} copy showLineNumbers\nconst config = {\n  categories: {\n    typography: {\n      components: [\"HeadingBlock\", \"ParagraphBlock\"],\n    },\n    other: {\n      title: \"Other components\",\n    },\n  },\n  // ...\n};\n```\n\n## TypeScript\n\nYou can pass in available category names to the `Config` type if using TypeScript\n\n```tsx copy {3}\nimport type { Config } from \"@puckeditor/core\";\n\nconst config: Config<{}, {}, \"typography\" | \"interactive\"> = {\n  categories: {\n    typography: {},\n    interactive: {},\n  },\n  // ...\n};\n```\n\n## Further reading\n\n- [`categories` API reference](/docs/api-reference/configuration/config#categories)\n- [`renderComponentList` API reference](/docs/api-reference/components/puck#rendercomponentlistparams)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/component-configuration.mdx",
    "content": "import { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# Component Configuration\n\nPuck's core behaviour is configured via the [Config](/docs/api-reference/configuration/config). This describes:\n\n- which components are available to Puck\n- how to render each component\n- which fields to show when the user selects a component\n- additional information, like [category grouping](categories)\n\nThe [Config](/docs/api-reference/configuration/config) is provided via the `config` prop to the main Puck components:\n\n- [`<Puck>`](/docs/api-reference/components/puck) reads the Config and renders an editor UI. The user interacts with the editor to produce a [data payload](/docs/api-reference/data-model/data).\n- [`<Render>`](/docs/api-reference/components/render) takes a [data payload](/docs/api-reference/data-model/data) and renders it according to the provided Config.\n\n## The `render` function\n\nComponents can be defined via the `components` object in [Config](/docs/api-reference/configuration/config). Every definition must provide a [`render` function](/docs/api-reference/configuration/component-config#renderprops):\n\n```tsx showLineNumbers copy {4-6}\nconst config = {\n  components: {\n    HeadingBlock: {\n      render: () => {\n        return <h1>Hello, world</h1>;\n      },\n    },\n  },\n};\n```\n\nThis tells Puck that **HeadingBlock** is a valid component, and describes how to render it.\n\nWhen the user drags the component onto the preview and hits **Publish** in the editor UI via the `<Puck>` component, this Config will produce a [data payload](/docs/api-reference/data-model/data) like this:\n\n```json copy\n{\n  \"content\": [\n    {\n      \"type\": \"HeadingBlock\",\n      \"props\": {\n        \"id\": \"HeadingBlock-1234\"\n      }\n    }\n  ],\n  \"root\": {}\n}\n```\n\nThe data payload and Config together tell `<Render>` how to render the page. It can also be provided to `<Puck>` as an [initial `data` payload](/docs/api-reference/components/puck#data).\n\n<PuckPreview\n  label=\"Try interacting with the heading\"\n  config={{\n    components: {\n      HeadingBlock: {\n        render: () => {\n          return <span>Hello, world</span>;\n        },\n      },\n    },\n  }}\n  data={{\n    content: [{ type: \"HeadingBlock\", props: { id: \"HeadingBlock-1\" } }],\n    root: { props: {} },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\n### TypeScript\n\nIf you're using TypeScript, we recommend strictly typing your config:\n\n```tsx copy {1,3-5} /Components/2\nimport type { Config } from \"@puckeditor/core\";\n\ntype Components = {\n  HeadingBlock: {};\n};\n\nconst config: Config<Components> = {\n  components: {\n    HeadingBlock: {\n      render: () => {\n        return <h1>Hello, world</h1>;\n      },\n    },\n  },\n};\n```\n\n## Adding fields\n\n[Fields](/docs/api-reference/fields) allow users to provide input to components. The value of each field is passed in as a prop to the `render` function.\n\nYou can define a field via the [`fields` parameter](/docs/api-reference/configuration/component-config#fields):\n\n```tsx showLineNumbers copy {5-7} /title/2,3\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      render: ({ title }) => {\n        return <h1>{title}</h1>;\n      },\n    },\n  },\n};\n```\n\nThis will render a [Text field](/docs/api-reference/fields/text) when the user selects an instance of the **HeadingBlock** component in the editor UI.\n\n<ConfigPreview\n  label='Text field example'\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n    },\n    render: ({ title }) => {\n      return <span>{title}</span>;\n    },\n\n}}\n/>\n\nWhen the user modifies the input, the editor will produce a data payload like this:\n\n```json copy {7}\n{\n  \"content\": [\n    {\n      \"type\": \"HeadingBlock\",\n      \"props\": {\n        \"id\": \"HeadingBlock-1234\",\n        \"title\": \"Hello, world\"\n      }\n    }\n  ],\n  \"root\": {}\n}\n```\n\n### TypeScript\n\nIt's best to define the props for the component if using TypeScript. This enables strict type checking for your fields.\n\n```tsx copy {5}\nimport type { Config } from \"@puckeditor/core\";\n\ntype Components = {\n  HeadingBlock: {\n    title: string;\n  };\n};\n\nconst config: Config<Components> = {\n  // ...\n};\n```\n\n## Setting default props\n\nDefault props allow you to set an initial value for a prop when a new component is added.\n\nProvide an object to the [`defaultProps`](/docs/api-reference/configuration/component-config#fields) parameter to configure this:\n\n```tsx showLineNumbers copy {9-11}\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n      },\n      defaultProps: {\n        title: \"Hello, world\",\n      },\n      render: ({ title }) => {\n        return <h1>{title}</h1>;\n      },\n    },\n  },\n};\n```\n\nUnlike [default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters), `defaultProps` are stored in the data payload and will populate the Puck fields.\n\n<ConfigPreview\n  label=\"Text field example\"\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    render: ({ title }) => {\n      return <span>{title}</span>;\n    },\n  }}\n/>\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/data-migration.mdx",
    "content": "# Data Migration\n\n## Version migrating\n\nPuck follows semantic versioning. Major releases may introduce breaking changes for your Data payload.\n\nPuck provides the [`migrate`](/docs/api-reference/functions/migrate) helper method to help migrate legacy data payloads to the latest data model, transforming any deprecated properties to their latest counterparts as described by the [Data API reference](/docs/api-reference/data-model/data).\n\n```tsx\nimport { migrate } from \"@puckeditor/core\";\n\nmigrate(legacyData);\n```\n\n## Breaking changes to props\n\nRenaming or removing the props passed to your components are considered breaking changes. Any existing [Data](/docs/api-reference/data-model/data) payloads that reference these props will be unable to render.\n\nThere are two strategies for dealing with this:\n\n1. Retaining backwards-compatible props\n2. Implementing a prop migration\n\n### Retaining backwards-compatibility\n\nThe easiest way to avoid breaking changes is to implement your prop changes in a backwards compatible manor:\n\n```tsx copy showLineNumbers {2}\nconst config = {\n  HeadingBlock: ({ title, heading }) => <h1>{heading || title}</h1>,\n};\n```\n\n### Implementing a prop migration\n\nIt will often be preferrable to update the underlying [Data](/docs/api-reference/data-model/data) payload. Puck provides the [`transformProps`](/docs/api-reference/functions/transform-props) utility method to conveniently transform the props for a given component throughout the payload.\n\n```tsx copy showLineNumbers {15-18}\nimport { transformProps } from \"@puckeditor/core\";\n\nconst config = {\n  // Renamed `title` prop to `heading`\n  HeadingBlock: ({ heading }) => <h1>{heading}</h1>,\n};\n\nconst data = {\n  content: [\n    // HeadingBlock references the legacy `title` prop\n    { type: \"HeadingBlock\", props: { title: \"Hello, world\" } },\n  ],\n};\n\nconst updatedData = transformProps(data, {\n  // Map `heading` to the legacy `title` prop\n  HeadingBlock: ({ title, ...props }) => ({ heading: title, ...props }),\n});\n\nconsole.log(updatedData);\n// { content: [{ type: \"HeadingBlock\", props: { heading: \"Hello, world\" } }] };\n```\n\nYou may choose to run this transform every time you render your content, or perform a batch operation against your database.\n\n```tsx copy showLineNumbers filename=\"Example showing data being updated before rendering\"\nimport { Puck, Render, transformProps } from \"@puckeditor/core\";\n\nconst transforms = {\n  HeadingBlock: ({ title, ...props }) => ({ heading: title, ...props }),\n};\n\nexport const MyEditor = ({ data, config }) => (\n  <Puck data={transformProps(data, transforms)} config={config} />\n);\n\nexport const MyPage = ({ data, config }) => (\n  <Render data={transformProps(data, transforms)} config={config} />\n);\n```\n\n## Further reading\n\n- [`Data` API reference](/docs/api-reference/data-model/data)\n- [`migrate` API reference](/docs/api-reference/functions/migrate)\n- [`transformProps` API reference](/docs/api-reference/functions/transform-props)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/dynamic-fields.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# Dynamic Fields\n\nDynamic field resolution allows you to change the [field configuration](/docs/api-reference/configuration/component-config#fields) for a component based on the current component props.\n\n## Dynamic component fields\n\nThe [`resolveFields` function](/docs/api-reference/configuration/component-config#resolvefieldsdata-params) allows you to make synchronous and asynchronous changes to the field configuration.\n\nFor example, we can set the configuration of one field based on the prop value of another:\n\n```tsx {4-25} showLineNumbers copy\nconst config = {\n  components: {\n    MyComponent: {\n      resolveFields: (data) => {\n        const fields = {\n          drink: {\n            type: \"radio\",\n            options: [\n              { label: \"Water\", value: \"water\" },\n              { label: \"Orange juice\", value: \"orange-juice\" },\n            ],\n          },\n        };\n\n        if (data.props.drink === \"water\") {\n          return {\n            ...fields,\n            waterType: {\n              // ... Define field\n            },\n          };\n        }\n\n        return fields;\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label='Try changing the \"drink\" field'\n  componentConfig={{\n    resolveFields: (data) => {\n      const fields = {\n        drink: {\n          type: \"radio\",\n          options: [\n            { label: \"Water\", value: \"water\" },\n            { label: \"Orange juice\", value: \"orange-juice\" },\n          ],\n        },\n      };\n\n      if (data.props.drink === \"water\") {\n        return {\n          ...fields,\n          waterType: {\n            type: \"radio\",\n            options: [\n              { label: \"Still\", value: \"still\" },\n              { label: \"Sparkling\", value: \"sparkling\" },\n            ],\n          },\n        };\n      }\n\n      return fields;\n    },\n    defaultProps: {\n      drink: \"water\",\n      waterType: \"still\",\n    },\n    render: ({ drink, waterType }) => (\n      <p>\n        {drink}\n        {drink === \"water\" ? ` (${waterType})` : \"\"}\n      </p>\n    ),\n\n}}\n/>\n\n### Making asynchronous calls\n\nThe [`resolveFields` function](/docs/api-reference/configuration/component-config#resolvefieldsdata-params) also enables asynchronous calls.\n\nHere's an example populating the options for a [`select` field](/docs/api-reference/fields/select) based on a [`radio` field](/docs/api-reference/fields/radio)\n\n```tsx {4-24} showLineNumbers copy\nconst config = {\n  components: {\n    MyComponent: {\n      resolveFields: async (data, { changed, lastFields }) => {\n        // Don't call the API unless `category` has changed\n        if (!changed.category) return lastFields;\n\n        // Make an asynchronous API call to get the options\n        const options = await getOptions(data.category);\n\n        return {\n          category: {\n            type: \"radio\",\n            options: [\n              { label: \"Fruit\", value: \"fruit\" },\n              { label: \"Vegetables\", value: \"vegetables\" },\n            ],\n          },\n          item: {\n            type: \"select\",\n            options,\n          },\n        };\n      },\n      render: ({ item }) => <h1>{item}</h1>,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label='Try changing the \"category\" field'\n  componentConfig={{\n    resolveFields: async (data, { changed, lastFields }) => {\n      if (!changed.category) return lastFields;\n\n      await new Promise((resolve) => setTimeout(resolve, 500));\n\n      return {\n        category: {\n          type: \"radio\",\n          options: [\n            { label: \"Fruit\", value: \"fruit\" },\n            { label: \"Vegetables\", value: \"vegetables\" },\n          ],\n        },\n        item: {\n          type: \"select\",\n          options:\n            data.props.category === \"fruit\"\n              ? [\n                { label: \"Select a fruit\", value: \"\" },\n                { label: \"Apple\", value: \"apple\" },\n                { label: \"Orange\", value: \"orange\" },\n                { label: \"Tomato\", value: \"tomato\" }\n              ] : [\n                { label: \"Select a vegetable\", value: \"\" },\n                { label: \"Broccoli\", value: \"broccoli\" },\n                { label: \"Cauliflower\", value: \"cauliflower\" },\n                { label: \"Mushroom\", value: \"mushroom\" },\n              ],\n        },\n      };\n    },\n\n    defaultProps: {\n      category: \"fruit\",\n      item: \"\",\n    },\n    render: ({ item }) => <p>{item}</p>,\n\n}}\n/>\n\n## Limitations\n\nThe [`slot` field](/docs/api-reference/fields/slot) is not currently supported by Dynamic Fields, but most use-cases can be achieved using [Dynamic Props](dynamic-props).\n\n## Further reading\n\n- [`resolveFields` API reference](/docs/api-reference/configuration/component-config#resolvefieldsdata-params)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/dynamic-props.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# Dynamic Props\n\nDynamic prop resolution allows you to change the props for a component after the props have been changed by the user. This is useful for making third-party API calls, such as requesting the latest content from a headless CMS.\n\n## Dynamic component props\n\nThe [`resolveData` function](/docs/api-reference/configuration/component-config#resolvedatadata-params) allows you to make changes to the props and set fields as read-only.\n\nFor example, we can set the value of one prop to another:\n\n```tsx {12-18} showLineNumbers copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: {\n          type: \"text\",\n        },\n        resolvedTitle: {\n          type: \"text\",\n        },\n      },\n      resolveData: async ({ props }) => {\n        return {\n          props: {\n            resolvedTitle: props.title,\n          },\n        };\n      },\n      render: ({ resolvedTitle }) => {\n        return <h1>{resolvedTitle}</h1>;\n      },\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label='Try changing the \"title\" field'\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n      resolvedTitle: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    resolveData: ({ props }) => {\n      return {\n        props: { resolvedTitle: props.title },\n      };\n    },\n    render: ({ resolvedTitle }) => {\n      return <p style={{ margin: 0 }}>{resolvedTitle}</p>;\n    },\n\n}}\n/>\n\n> When inserting components with `resolveData`, the Puck state will update twice - once for the initial insert, and once more when the method resolves, if it changes the data. This will be reflected in the undo/redo history.\n\n### Setting fields as read-only\n\n[`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) also allows us to mark fields as read-only using the [`readOnly` parameter](/docs/api-reference/configuration/component-config#datareadonly-1).\n\n```tsx {17} showLineNumbers copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      // ...\n      resolveData: async ({ props }) => {\n        return {\n          props: {\n            resolvedTitle: props.title,\n          },\n          readOnly: { resolvedTitle: true },\n        };\n      },\n      // ...\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label='The resolvedTitle field is locked'\n  componentConfig={{\n    fields: {\n      title: {\n        type: \"text\",\n      },\n      resolvedTitle: {\n        type: \"text\",\n      },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n    },\n    resolveData: ({ props }) => {\n      return {\n        props: { resolvedTitle: props.title },\n        readOnly: { resolvedTitle: true }\n      };\n    },\n    render: ({ resolvedTitle }) => {\n      return <p style={{ margin: 0 }}>{resolvedTitle}</p>;\n    },\n\n}}\n/>\n\n### Preventing duplicate calls\n\nIt's possible that `resolveData` may carry out an expensive operation (like an API call) that we want to avoid making unless a specific prop has changed.\n\nThis can be restricted by checking the [`changed` param](/docs/api-reference/configuration/component-config#paramschanged) before calling any expensive operations.\n\n```tsx {6} showLineNumbers copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      // ...\n      resolveData: async ({ props }, { changed }) => {\n        if (!changed.text) return { props };\n\n        return {\n          props: {\n            resolvedTitle: await expensiveOperation(props.title),\n          },\n        };\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Dynamic Root props\n\nThe `resolveData` method is also available on the [root component](/docs/api-reference/configuration/config#root).\n\n```tsx showLineNumbers copy {12-18}\nconst config = {\n  components: {},\n  root: {\n    fields: {\n      title: {\n        type: \"text\",\n      },\n      resolvedTitle: {\n        type: \"text\",\n      },\n    },\n    resolveData: async ({ props }) => {\n      return {\n        props: {\n          resolvedTitle: props.title,\n        },\n      };\n    },\n    render: ({ children, resolvedTitle }) => {\n      return (\n        <>\n          <h1>{resolvedTitle}</h1>\n          {children}\n        </>\n      );\n    },\n  },\n};\n```\n\n## Triggering `resolveData`\n\nResolve data is triggered whenever the props for a component change, or when the [`resolveAllData` utility](/docs/api-reference/functions/resolve-all-data) is used.\n\n```tsx\nimport { resolveAllData } from \"@puckeditor/core\";\n\nconst updatedData = await resolveAllData(data, config);\n```\n\n## Further reading\n\n- [`resolveData` API reference](/docs/api-reference/configuration/component-config#resolvedatadata-params)\n- [`resolveAllData` API reference](/docs/api-reference/functions/resolve-all-data)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/external-data-sources.mdx",
    "content": "import { ConfigPreview } from \"@/docs/components/Preview\";\n\n# External Data Sources\n\nThere are several different approaches for loading external data into a Puck component.\n\nIt's possible for Puck components to load their own data internally on the client, or on the server using [React server components](/docs/integrating-puck/server-components). This doesn't require any Puck configuration.\n\nIf you want to provide the user a way to select the data, you can use the [`external` field type](/docs/api-reference/fields/external).\n\n## Selecting external data\n\nThe [`external` field type](/docs/api-reference/fields/external) allows users to select tabular data from a third-party data source, like a headless CMS. This will load the data once and save it into the [data payload](/docs/api-reference/data-model/data).\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        getItemSummary: (item) => item.title,\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n      },\n    },\n    render: ({ data }) => {\n      if (!data) {\n        return \"No data selected\";\n      }\n\n      return (\n        <>\n          <b>{data.title}</b>\n          <p>{data.description}</p>\n        </>\n      );\n    },\n\n}}\n/>\n\n```tsx {5-17} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            // Query an API for a list of items\n            const items = await fetch(`/api/items`).then((res) => res.json());\n            // [\n            //   { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            //   { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n            // ];\n\n            return items;\n          },\n        },\n      },\n      render: ({ data }) => {\n        if (!data) {\n          return \"No data selected\";\n        }\n\n        return (\n          <>\n            <b>{data.title}</b>\n            <p>{data.description}</p>\n          </>\n        );\n      },\n    },\n  },\n};\n```\n\nYou can also use the [`showSearch` parameter](/docs/api-reference/fields/external#showsearch) to show a search input to the user.\n\n## Data syncing\n\nTo keep the data in sync with the external source, we can combine the `external` field with the [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params) function.\n\nThis technique re-fetches the content every time the page is loaded, or the [`resolveAllData` utility](/docs/api-reference/functions/resolve-all-data) is called.\n\n```tsx showLineNumbers {19-37} /id: 0/1 /id: 1/ copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          type: \"external\",\n          fetchList: async () => {\n            // Query an API for a list of items\n            const items = await fetch(`/api/items`).then((res) => res.json());\n            // [\n            //   { title: \"Hello, world\", id: 0 },\n            //   { title: \"Goodbye, world\", id: 1 },\n            // ];\n\n            return items;\n          },\n        },\n      },\n      resolveData: async ({ props }, { changed }) => {\n        if (!props.data) return { props };\n\n        // Don't query unless `data` has changed since resolveData was last run\n        if (!changed.data) return { props };\n\n        // Re-query the API for a particular item\n        const latestData = await fetch(`/api/items/${props.data.id}`).then(\n          (res) => res.json()\n        );\n        // { title: \"Hello, world\", description: \"Lorem ipsum 1\", id: 0 }\n\n        return {\n          props: {\n            // Update the value for `data`\n            data: latestData,\n          },\n        };\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Hybrid authoring\n\nHybrid authoring enables users to edit fields inline, or populate those fields with data from an external source.\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      data: {\n        type: \"external\",\n        getItemSummary: (item) => item.title,\n        fetchList: async () => {\n          return [\n            { title: \"Hello, world\", description: \"Lorem ipsum 1\" },\n            { title: \"Goodbye, world\", description: \"Lorem ipsum 2\" },\n          ];\n        },\n      },\n      title: {\n        type: \"text\",\n      },\n    },\n    resolveData: async ({ props }) => {\n      if (!props.data) return { props,  readOnly: { title: false } };\n\n      return {\n        props: { title: props.data.title },\n        readOnly: { title: true }\n      };\n    },\n    render: ({ title }) => {\n      return (\n        <>\n          <b>{title}</b>\n        </>\n      );\n    },\n\n}}\n/>\n\nThis can be achieved by mapping the data from `data.title` to `title` in [`resolveData`](/docs/api-reference/configuration/component-config#resolvedatadata-params), and marking the field as read-only.\n\n```tsx showLineNumbers {21,22} copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        data: {\n          // ...\n        },\n        title: {\n          type: \"text\",\n        },\n      },\n      resolveData: async ({ props }, { changed }) => {\n        // Remove read-only from the title field if `data` is empty\n        if (!props.data) return { props, readOnly: { title: false } };\n\n        // Don't query unless `data` has changed since resolveData was last run\n        if (!changed.data) return { props };\n\n        return {\n          props: {\n            title: props.data.title,\n            readOnly: { title: true },\n          },\n        };\n      },\n      render: ({ title }) => <b>{title}</b>,\n    },\n  },\n};\n```\n\n## External data packages\n\nWe provide helper packages to load data from common data sources.\n\n- [`contentful`](https://github.com/puckeditor/puck/tree/main/packages/field-contentful): Select content entries from a [Contentful](https://www.contentful.com) space.\n\n## Further reading\n\n- [`external` field API reference](/docs/api-reference/fields/external)\n- [`resolveData` API reference](/docs/api-reference/configuration/component-config#resolvedatadata-params)\n- [`resolveAllData` API reference](/docs/api-reference/functions/resolve-all-data)\n\n<div id=\"puck-portal-root\" />\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/feature-toggling.mdx",
    "content": "# Feature Toggling\n\nFeature toggling is enabled by Puck's [Permissions API](/docs/api-reference/permissions). This enables you to toggle behavior like:\n\n- Deletion\n- Dragging\n- Duplication\n- Editing (setting all fields to read-only)\n- etc\n\nSee the [supported permissions reference](/docs/api-reference/permissions#supported-permissions) for a complete list.\n\n## Toggling features globally\n\nToggling features across the entire Puck instance can be done with global permissions. These can be set by the [`permissions` prop](/docs/api-reference/components/puck#permissions) on the Puck component:\n\n```tsx showLineNumbers copy {4-6}\nexport function Editor() {\n  return (\n    <Puck\n      permissions={{\n        delete: false, // Disable delete function on all components\n      }}\n      // ...\n    />\n  );\n}\n```\n\n## Toggling features per component\n\nToggling feature for all instance of a component can be done using component permissions. This is controlled by the [`permissions` parameter](/docs/api-reference/configuration/component-config#permissions) on the component config, and inherits the global permissions.\n\n```tsx showLineNumbers copy {4-6}\nconst config = {\n  components: {\n    HeadingBlock: {\n      permissions: {\n        delete: false, // Disable delete function on all HeadingBlock instances\n      },\n      // ...\n    },\n  },\n};\n```\n\nComponent permissions can also be applied to the `root` config.\n\n## Toggling features dynamically\n\nDynamic permissions enable runtime calculation of permissions based on the component data, enabling instance-specific permissions. This is controlled by the [`resolvePermissions` parameter](/docs/api-reference/configuration/component-config#resolvepermissionsdata-params) on the component config.\n\n```tsx showLineNumbers copy {4-12}\nconst config = {\n  components: {\n    HeadingBlock: {\n      resolvePermissions: (data, { permissions }) => {\n        if (data.props.locked) {\n          return {\n            delete: false, // Disable delete function when HeadingBlock `locked` prop is set\n          };\n        }\n\n        return permissions; // Return inherited permissions (component or global)\n      },\n      // ...\n    },\n  },\n};\n```\n\n### Asynchronous feature toggling\n\nPermissions can be resolved asynchronously, enabling powerful patterns like querying permissions from an endpoint whenever the data changes.\n\n```tsx showLineNumbers copy {4-8}\nconst config = {\n  components: {\n    HeadingBlock: {\n      resolvePermissions: async (data) => {\n        const serverPermissions = await myPermissionsApi(data.props.id); // Query permissions from a server\n\n        return serverPermissions;\n      },\n      // ...\n    },\n  },\n};\n```\n\n### Preventing duplicate calls\n\nPermission resolvers are cached based on the component props. If none of the props change, then the resolver won't be called. This prevents duplicate calls to expensive asynchronous operations.\n\nHowever, it's possible that you may want to avoid making an expensive operation unless a _specific_ prop has changed, rather than any prop.\n\nThis can be restricted by checking the [`changed` param](/docs/api-reference/configuration/component-config#paramschanged-2) before calling any expensive operations.\n\n```tsx {6} showLineNumbers copy\nconst config = {\n  components: {\n    HeadingBlock: {\n      // ...\n      resolvePermissions: async (data, { changed, lastPermissions }) => {\n        if (!changed.locked) return lastPermissions; // Return last permissions if `locked` hasn't changed\n\n        return await myExpensivePermissionsApi(data),\n      },\n      // ...\n    },\n  },\n};\n```\n\n## Further reading\n\n- [Permissions API reference](/docs/api-reference/permissions)\n- [Supported permissions reference](/docs/api-reference/permissions#supported-permissions)\n- [Global `permissions` prop API reference](/docs/api-reference/components/puck#permissions)\n- [Component `permissions` param API reference](/docs/api-reference/configuration/component-config#permissions)\n- [Component `resolvePermissions` param API reference](/docs/api-reference/configuration/component-config#resolvepermissionsdata-params)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/multi-column-layouts.mdx",
    "content": "---\ntitle: Multi-column Layouts\n---\n\nimport { PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\nimport { Callout } from \"nextra/components\";\n\n# Multi-column Layouts\n\nPuck supports nested and multi-column layouts across any CSS layout using the [`slot` field](/docs/api-reference/fields/slot).\n\n<Callout type=\"info\">Slots replace the [`<DropZone>` component](/docs/api-reference/components/drop-zone) component, which will soon be deprecated and removed. For migration notes, see [these docs](/docs/guides/migrations/dropzones-to-slots).</Callout>\n\n## Nested components\n\nAdd the [`slot`](/docs/api-reference/fields/slot) field to your component to create a zone that you can drop components into.\n\n```tsx {5-7,10} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      render: ({ content: Content }) => {\n        return <Content />;\n      },\n    },\n    Card: {\n      render: () => <div>Hello, world</div>,\n    },\n  },\n};\n```\n\n<PuckPreview\n  label=\"Nested components example\"\n  config={{\n    components: {\n      Example: {\n        fields: {\n          content: {\n            type: \"slot\",\n          },\n        },\n        render: ({ content: Content }) => {\n          return (\n            <div style={{ padding: 32 }}>\n              <Content />\n            </div>\n          );\n        },\n      },\n      Card: {\n        render: () => {\n          return (\n            <div\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                borderRadius: 4,\n                padding: 16,\n              }}\n            >\n              Hello, world\n            </div>\n          );\n        },\n      },\n    },\n  }}\n  data={{\n    content: [\n      {\n        type: \"Example\",\n        props: {\n          id: \"Example-1\",\n          content: [{ type: \"Card\", props: { id: \"Example-2\" } }],\n        },\n      },\n    ],\n    root: { props: {} },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\n## Fixed layouts\n\nCombine multiple DropZones to achieve fixed layouts. By default, components inside a DropZone are arranged along the vertical (`block`) axis.\n\n```tsx {5-10,17,18} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        leftColumn: {\n          type: \"slot\",\n        },\n        rightColumn: {\n          type: \"slot\",\n        },\n      },\n      render: ({ leftColumn: LeftColumn, rightColumn: RightColumn }) => {\n        return (\n          <div\n            style={{ display: \"grid\", gridTemplateColumns: \"1fr 1fr\", gap: 16 }}\n          >\n            <LeftColumn />\n            <RightColumn />\n          </div>\n        );\n      },\n    },\n    Card: {\n      render: ({ text }) => <div>{text}</div>,\n    },\n  },\n};\n```\n\n<PuckPreview\n  label=\"Fixed layout example\"\n  config={{\n    root: {\n      render: ({ puck: { renderDropZone: DropZone } }) => (\n        <DropZone zone=\"default-zone\" disallow={[\"Card\"]} />\n      ),\n    },\n    components: {\n      Example: {\n        fields: {\n          leftColumn: {\n            type: \"slot\",\n          },\n          rightColumn: {\n            type: \"slot\",\n          },\n        },\n        render: ({ leftColumn: LeftColumn, rightColumn: RightColumn }) => {\n          return (\n            <div\n              style={{\n                display: \"grid\",\n                gridTemplateColumns: \"1fr 1fr\",\n                gap: 16,\n              }}\n            >\n              <LeftColumn />\n              <RightColumn />\n            </div>\n          );\n        },\n      },\n      Card: {\n        render: ({ content }) => {\n          return (\n            <div\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                borderRadius: 4,\n                padding: 16,\n              }}\n            >\n              {content}\n            </div>\n          );\n        },\n      },\n    },\n  }}\n  data={{\n    content: [\n      {\n        type: \"Example\",\n        props: {\n          id: \"Example-1\",\n          leftColumn: [\n            {\n              type: \"Card\",\n              props: { id: \"Example-2\", content: \"1\" },\n            },\n          ],\n          rightColumn: [\n            {\n              type: \"Card\",\n              props: { id: \"Example-3\", content: \"2\" },\n            },\n          ],\n        },\n      },\n    ],\n    root: { props: {} },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\n## Fluid layouts\n\nApply the [CSS display](https://developer.mozilla.org/en-US/docs/Web/CSS/display) property to a slot via the [`style`](/docs/api-reference/components/drop-zone#style) or [`className`](/docs/api-reference/components/drop-zone#className) props to arrange your components in different layouts. Puck supports drag-and-drop for all `display` values, including `grid` and `flex`.\n\n```tsx {12-15} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      render: ({ content: Content }) => (\n        <Content\n          style={{\n            // Use CSS grid in this slot\n            display: \"grid\",\n            gridTemplateColumns: \"2fr 1fr\",\n            gap: 16,\n          }}\n        />\n      ),\n    },\n    Card: {\n      render: ({ text }) => <div>{text}</div>,\n    },\n  },\n};\n```\n\n<PuckPreview\n  label=\"Fluid layout using CSS grid\"\n  config={{\n    root: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      render: ({ content: Content }) => (\n        <Content\n          style={{\n            display: \"grid\",\n            gridTemplateColumns: \"2fr 1fr\",\n            gap: 16,\n          }}\n        />\n      ),\n    },\n    components: {\n      Card: {\n        render: ({ content }) => {\n          return (\n            <div\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                borderRadius: 4,\n                padding: 16,\n              }}\n            >\n              {content}\n            </div>\n          );\n        },\n      },\n    },\n  }}\n  data={{\n    content: [],\n    root: {\n      props: {\n        content: [\n          {\n            type: \"Card\",\n            props: { id: \"Example-2\", content: \"1\" },\n          },\n          {\n            type: \"Card\",\n            props: { id: \"Example-3\", content: \"2\" },\n          },\n        ],\n      },\n    },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\n## Removing the wrapper\n\nBy default, Puck will wrap your components in a `div` element. For some layouts, you may need to eliminate the wrapping element and treat the child component as a direct descendant of its' parent slot.\n\nFor example, this is required if you wish to use CSS rules like `flex-grow`, `grid-column`, or `grid-row`.\n\nUse the [`inline`](/docs/api-reference/configuration/component-config#inline) component parameter to remove the wrapping element. **When using this API, you must also specify which element is draggable by passing the [`puck.dragRef` prop](/docs/api-reference/configuration/component-config#puckdragref) to your element's `ref` prop.**\n\n```tsx {13-14,21,24-28} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      render: ({ content: Content }) => (\n        <Content\n          style={{\n            display: \"grid\",\n            gridTemplateColumns: \"1fr 1fr 1fr 1fr\",\n            gridTemplateRows: \"1fr 1fr 1fr 1fr\",\n            gap: 16,\n          }}\n        />\n      ),\n    },\n    Card: {\n      inline: true, // Enable inline mode, removing the Puck wrapper\n      render: ({ text, spanCol, spanRow, puck }) => (\n        <div\n          ref={puck.dragRef} // Let Puck know this element is draggable\n          style={{\n            gridColumn: `span ${spanCol}`,\n            gridRow: `span ${spanRow}`,\n          }}\n        >\n          {text}\n        </div>\n      ),\n    },\n  },\n};\n```\n\n<PuckPreview\n  label=\"Advanced grid example\"\n  config={{\n    root: {\n      fields: {\n        Content: {\n          type: \"slot\",\n        },\n      },\n      render: ({ Content }) => (\n        <Content\n          zone=\"default-zone\"\n          style={{\n            display: \"grid\",\n            gridTemplateColumns: \"1fr 1fr 1fr 1fr\",\n            gridTemplateRows: \"1fr 1fr 1fr\",\n            gap: 16,\n          }}\n        />\n      ),\n    },\n    components: {\n      Card: {\n        inline: true,\n        render: ({ content, spanCol, spanRow, puck }) => {\n          return (\n            <div\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                borderRadius: 4,\n                padding: 16,\n                gridColumn: `span ${spanCol}`,\n                gridRow: `span ${spanRow}`,\n              }}\n              // Let Puck know this element is draggable\n              ref={puck.dragRef}\n            >\n              {content}\n            </div>\n          );\n        },\n      },\n    },\n\n}}\ndata={{\n    content: [],\n    root: {\n      props: {\n        Content: [\n          {\n            type: \"Card\",\n            props: { id: \"Example-1\", content: \"1\", spanCol: 2, spanRow: 2 },\n          },\n          {\n            type: \"Card\",\n            props: { id: \"Example-2\", content: \"2\", spanCol: 1, spanRow: 1 },\n          },\n          {\n            type: \"Card\",\n            props: { id: \"Example-3\", content: \"3\", spanCol: 1, spanRow: 1 },\n          },\n          {\n            type: \"Card\",\n            props: { id: \"Example-4\", content: \"4\", spanCol: 2, spanRow: 1 },\n          },\n          {\n            type: \"Card\",\n            props: { id: \"Example-5\", content: \"5\", spanCol: 1, spanRow: 1 },\n          },\n        ],\n      },\n    },\n  }}\n/>\n\n## Restricting components\n\nUse the [`allow`](/docs/api-reference/fields/slot#allow) and [`disallow`](/docs/api-reference/fields/slot#disallow) parameters to restrict which components can be dragged into a slot.\n\n```tsx {6} showLineNumbers copy\nconst config = {\n  components: {\n    fields: {\n      content: {\n        type: \"slot\",\n        allow: [\"Card\"],\n      },\n    },\n    Example: {\n      render: ({ content: Content }) => {\n        return <Content />;\n      },\n    },\n  },\n};\n```\n\nCombine this with [categories](/docs/integrating-puck/categories) to restrict behavior based on your existing groups.\n\n```tsx {2-6,12} showLineNumbers copy\nconst config = {\n  categories: {\n    typography: {\n      components: [\"Card\"],\n    },\n  },\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n          allow: categories.typography.components,\n        },\n      },\n      render: ({ content: Content }) => {\n        return <Content />;\n      },\n    },\n  },\n};\n```\n\nAlternatively, you can provide `allow` and `disallow` [to your render function](/docs/api-reference/fields/slot#allow-1).\n\n## Setting default props\n\nUse slots with [`defaultProps`](/docs/api-reference/configuration/component-config#defaultprops) to pre-populate it when the component is inserted with an array of [`ComponentData`](/docs/api-reference/data-model/component-data).\n\n```tsx {9-18} showLineNumbers copy\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      defaultProps: {\n        content: [\n          {\n            type: \"Card\",\n            props: {\n              text: \"Pre-populated\",\n            },\n          },\n        ],\n      },\n      render: ({ content: Content }) => <Content />,\n    },\n    Card: {\n      render: ({ text }) => <div>{text}</div>,\n    },\n  },\n};\n```\n\n<PuckPreview\n  label=\"Fluid layout using CSS grid\"\n  config={{\n    root: {\n      fields: {\n        content: {\n          type: \"slot\",\n        },\n      },\n      defaultProps: {\n        content: [\n          {\n            type: \"Card\",\n            props: {\n              text: \"Pre-populated\",\n            },\n          },\n        ],\n      },\n      render: ({ content }) =>\n        content({\n          style: {\n            display: \"grid\",\n            gridTemplateColumns: \"2fr 1fr\",\n            gap: 16,\n          },\n        }),\n    },\n    components: {\n      Card: {\n        render: ({ content }) => {\n          return (\n            <div\n              style={{\n                background: \"white\",\n                border: \"1px solid black\",\n                borderRadius: 4,\n                padding: 16,\n              }}\n            >\n              {content}\n            </div>\n          );\n        },\n      },\n    },\n  }}\n  data={{\n    content: [],\n    root: {\n      props: {\n        content: [\n          {\n            type: \"Card\",\n            props: { id: \"Example-2\", content: \"Pre-populated\" },\n          },\n        ],\n      },\n    },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\n## Further reading\n\n- [The `slot` field API](/docs/api-reference/fields/slot)\n- [The `inline` component config](/docs/api-reference/configuration/component-config#inline)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/overlay-portals.mdx",
    "content": "import {\n  PuckPreview,\n  OverlayPortalPreview,\n  OverlayPortalTabsPreview,\n} from \"@/docs/components/Preview\";\nimport { Puck } from \"@/puck\";\n\n# Overlay Portals\n\nOverlay Portals enable you to disable the Puck overlay when hovering over specific elements, making them interactive in the editor.\n\nUse the [`registerOverlayPortal` API](/docs/api-reference/functions/register-overlay-portal) to mark an element as a portal.\n\n```tsx\nimport { registerOverlayPortal } from \"@puckeditor/core\";\n\nconst Example = () => {\n  const ref = useRef<HTMLButtonElement>(null);\n\n  useEffect(() => registerOverlayPortal(ref.current), [ref.current]);\n\n  return (\n    <button ref={ref} onClick={() => alert(\"Click\")}>\n      Clickable\n    </button>\n  );\n};\n```\n\n<PuckPreview\n  label=\"Overlay Portals example\"\n  config={{\n    components: {\n      Example: {\n        render: () => {\n          return (\n            <div style={{ padding: 32 }}>\n              <OverlayPortalPreview />\n            </div>\n          );\n        },\n      },\n    },\n  }}\n  data={{\n    content: [\n      {\n        type: \"Example\",\n        props: {\n          id: \"Example-1\",\n        },\n      },\n    ],\n    root: { props: {} },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n\nPortals can be used to create interactive functionality for previewing, such as to paginate through tabs, or combined with [`usePuck()`](/docs/extending-puck/internal-puck-api) to create an inline form input.\n\n## Example: Tabs\n\n<PuckPreview\n  label=\"Tabs example\"\n  config={{\n    components: {\n      Example: {\n        render: () => {\n          return <OverlayPortalTabsPreview />;\n        },\n      },\n    },\n  }}\n  data={{\n    content: [\n      {\n        type: \"Example\",\n        props: {\n          id: \"Example-1\",\n        },\n      },\n    ],\n    root: { props: {} },\n  }}\n>\n  <Puck.Preview />\n</PuckPreview>\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/rich-text-editing.mdx",
    "content": "import { ConfigPreview, PuckPreview } from \"@/docs/components/Preview\";\nimport { Puck, RichTextMenu } from \"@/puck\";\nimport { Callout } from \"nextra/components\";\n\n# Rich Text Editing\n\nPuck supports rich text editing via the [`richtext`](/docs/api-reference/fields/richtext) field.\n\n## Adding a rich text field\n\nTo use rich text, add the `richtext` field to your component config:\n\n```tsx copy showLineNumbers {6}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, <strong>world</strong></p><ul><li><p>List item 1</p></li><li><p>List item 2</p></li><li><p>List item 3</p></li></ul>\",\n    },\n    render: ({ body }) => {\n      return body;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n## Enabling inline editing\n\nSet [`contentEditable`](/docs/api-reference/fields/richtext#contenteditable) to make the field editable inline:\n\n```tsx copy showLineNumbers {7}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          contentEditable: true,\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        contentEditable: true,\n      },\n    },\n    defaultProps: {\n      body: \"<p>Edit me <strong>inline:</strong></p><ul><li><p>List item 1</p></li><li><p>List item 2</p></li><li><p>List item 3</p></li></ul>\",\n    },\n    render: ({ body }) => {\n      return body;\n    },\n  }}\n  hideActionBar={false}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n## Customizing behavior\n\nThe rich text field is built with [Tiptap](https://tiptap.dev), and comes with [several Tiptap extensions](/docs/api-reference/fields/richtext#included-extensions) pre-configured. Editor behavior can be customized by configuring these extensions.\n\n### Disabling functionality\n\nDisable any extension by setting it to `false` in [`options`](/docs/api-reference/fields/richtext#options):\n\n```ts showLineNumbers copy {7-10}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          options: {\n            // Disable bold extension\n            bold: false,\n          },\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        options: { bold: false },\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, <strong>world</strong></p>\", // Check sanitization\n    },\n    render: ({ body }) => {\n      return body;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n### Configuring extensions\n\nProvide configuration options for the [included extensions](/docs/api-reference/fields/richtext#included-extensions) directly to `options` for further customization:\n\n```ts showLineNumbers copy {7-11}\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          options: {\n            // Restrict headings to h1 and h2\n            // https://tiptap.dev/docs/editor/extensions/nodes/heading\n            heading: { levels: [1, 2] },\n          },\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        options: { heading: { levels: [1, 2] } },\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, <strong>world</strong></p>\",\n    },\n    render: ({ body }) => {\n      return body;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n## Customizing the menu bar\n\nUse [`renderMenu()`](/docs/api-reference/fields/richtext#rendermenuprops) to customize the menu bar layout using the [`<RichTextMenu>`](/docs/api-reference/components/rich-text-menu) component:\n\n```tsx showLineNumbers copy {1, 9-18}\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          renderMenu: () => (\n            {/*  Always wrap in <RichTextMenu> */}\n            <RichTextMenu>\n              {/* Group items in <RichTextMenu.Group> */}\n              <RichTextMenu.Group>\n                {/* Only include the bold control */}\n                <RichTextMenu.Bold />\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n<ConfigPreview\n  label=\"Example\"\n  componentConfig={{\n    fields: {\n      body: {\n        type: \"richtext\",\n        renderMenu: () => (\n          <RichTextMenu>\n            <RichTextMenu.Group>\n              <RichTextMenu.Bold />\n            </RichTextMenu.Group>\n          </RichTextMenu>\n        ),\n      },\n    },\n    defaultProps: {\n      body: \"<p>Hello, <strong>world</strong></p>\",\n    },\n    render: ({ body }) => {\n      return body;\n    },\n  }}\n>\n  <Puck.Preview />\n</ConfigPreview>\n\n<Callout type=\"info\">\n  Hiding a control does not disable the functionality. Users can still use\n  keyboard shortcuts and other input mechanisms. To disable functionality, use\n  [`options`](/docs/api-reference/fields/richtext#options).\n</Callout>\n\nA full list of controls is available in the [included controls API reference](/docs/api-reference/components/rich-text-menu#included-controls).\n\n## Adding custom extensions\n\n### Configure the extension\n\nProvide additional [Tiptap extensions](https://tiptap.dev/docs/editor/extensions/overview) to the [`tiptap.extensions`](/docs/api-reference/fields/richtext#tiptapextensions) parameter to introduce new functionality:\n\n```tsx copy showLineNumbers {1, 9-11}\nimport Superscript from \"@tiptap/extension-superscript\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          tiptap: {\n            extensions: [Superscript],\n          },\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n### Add a new control\n\nIf you're adding a control for a new extension, first update the [`tiptap.selector`](/docs/api-reference/fields/richtext#tiptapselector) to make the state available to the menu:\n\n```tsx copy showLineNumbers {11-14}\nimport Superscript from \"@tiptap/extension-superscript\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          tiptap: {\n            extensions: [Superscript],\n            selector: ({ editor }) => ({\n              isSuperscript: editor?.isActive(\"superscript\"),\n              canSuperscript: editor?.can().chain().toggleSuperscript().run(),\n            }),\n          },\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\nThen implement a new control with [`<RichTextMenu.Control>`](/docs/api-reference/components/rich-text-menu-control) and render it with `renderMenu()`:\n\n```tsx copy showLineNumbers {2-3, 12-28}\nimport Superscript from \"@tiptap/extension-superscript\";\nimport { Superscript } from \"lucide-react\";\nimport { RichTextMenu } from \"@puckeditor/core\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        body: {\n          type: \"richtext\",\n          // ...\n          renderMenu: ({ children, editor, editorState }) => (\n            <RichTextMenu>\n              {/* Render the default controls */}\n              {children}\n\n              <RichTextMenu.Group>\n                <RichTextMenu.Control\n                  icon={<Superscript />}\n                  onClick={() =>\n                    editor?.chain().focus().toggleSuperscript().run()\n                  }\n                  active={editorState?.isSuperscript}\n                  disabled={!editorState?.canSuperscript}\n                />\n              </RichTextMenu.Group>\n            </RichTextMenu>\n          ),\n        },\n      },\n      render: ({ body }) => body,\n    },\n  },\n};\n```\n\n## Further reading\n\n- [`richtext` field API reference](/docs/api-reference/fields/richtext)\n- [`<RichTextMenu>` API reference](/docs/api-reference/components/rich-text-menu)\n- [Tiptap Overview](https://tiptap.dev/docs/editor/getting-started/overview)\n- [Tiptap Extensions](https://tiptap.dev/docs/editor/extensions/overview)\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/root-configuration.mdx",
    "content": "# Root Configuration\n\nThe root is the top-level component within Puck. It:\n\n1. Renders a single wrapper around your other components. This can be overwritten with a `render` function.\n2. Stores meta data, like the page title. This can be extended with `fields`.\n\nConfiguring the root is similar to [configuring components](component-configuration).\n\n## The root `render` function\n\nUse the [`root` parameter](/docs/api-reference/configuration/config#root) to specify a [`render` function](/docs/api-reference/configuration/component-config#renderprops):\n\n```tsx showLineNumbers copy {10-12}\nconst config = {\n  components: {\n    HeadingBlock: {\n      render: () => {\n        return <h1>Hello, world</h1>;\n      },\n    },\n  },\n  root: {\n    render: ({ children }) => {\n      return <div>{children}</div>;\n    },\n  },\n};\n```\n\nThe root `render` function will wrap all of the components. `children` is a node containing the nested components.\n\nIf you don't render `children`, your components will not be rendered unless you define another [slot](/docs/api-reference/fields/slot).\n\n### Example output\n\nGiven a minimal data payload containing one **HeadingBlock**\n\n```json copy\n{\n  \"content\": [\n    {\n      \"type\": \"HeadingBlock\",\n      \"props\": {\n        \"id\": \"HeadingBlock-1234\"\n      }\n    }\n  ],\n  \"root\": {}\n}\n```\n\nthe example config will render HTML nodes like this:\n\n```html\n<!-- root render -->\n<div>\n  <!-- HeadingBlock render -->\n  <h1>Hello, world</h1>\n\n  <!-- Remaining nodes -->\n</div>\n```\n\n## Adding fields\n\nRoot fields provide user input to the root render method, and can be used to store metadata.\n\nBy default, `root` is configured with a `title` text field:\n\n```tsx showLineNumbers copy /title/\nconst config = {\n  // ...\n  root: {\n    render: ({ children, title }) => {\n      return (\n        <div>\n          <h1>{title}</h1>\n          {children}\n        </div>\n      );\n    },\n  },\n};\n```\n\nYou can override the default field configuration by providing custom [Fields](/docs/api-reference/fields) to the [`fields` parameter](/docs/api-reference/configuration/component-config#fields):\n\n```tsx showLineNumbers copy {4-7} /description/2,3\nconst config = {\n  // ...\n  root: {\n    fields: {\n      title: { type: \"text\" }, // You need to redefine the `title` field if we want to retain it\n      description: { type: \"textarea\" },\n    },\n    render: ({ children, title, description }) => {\n      return (\n        <div>\n          <h1>{title}</h1>\n          <p>{description}</p>\n          {children}\n        </div>\n      );\n    },\n  },\n};\n```\n\nWhen the user modifies the inputs, the editor will produce a data payload like this:\n\n```json copy {6-9}\n{\n  \"content\": [\n    // ...\n  ],\n  \"root\": {\n    \"props\": {\n      \"title\": \"Hello, world\",\n      \"description\": \"Lorem ipsum\"\n    }\n  }\n}\n```\n\n### TypeScript\n\nGeneric types can be passed to the `Config` type to strictly type your root configuration:\n\n```tsx copy {1,3-5} /RootProps/2\nimport type { Config } from \"@puckeditor/core\";\n\ntype RootProps = {\n  description: string;\n};\n\nconst config: Config<{}, RootProps> = {\n  // ...\n};\n```\n\n## Setting default props\n\nProvide an object to the [`defaultProps`](/docs/api-reference/configuration/component-config#fields) parameter to configure default props for the root fields:\n\n```tsx showLineNumbers copy {8-11}\nconst config = {\n  // ...\n  root: {\n    fields: {\n      title: { type: \"text\" },\n      description: { type: \"textarea\" },\n    },\n    defaultProps: {\n      title: \"Hello, world\",\n      description: \"Lorem ipsum\",\n    },\n    render: ({ children, title, description }) => {\n      return (\n        <div>\n          <h1>{title}</h1>\n          <p>{description}</p>\n          {children}\n        </div>\n      );\n    },\n  },\n};\n```\n\nUnlike [default parameters](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Default_parameters), `defaultProps` are stored in the data payload and will populate the Puck fields.\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/server-components.mdx",
    "content": "import { Callout } from \"nextra/components\";\n\n# React Server Components\n\nPuck provides support for [React Server Components](https://react.dev/reference/react/use-server#use-server) (RSC), but the interactive-nature of Puck requires special consideration.\n\n## Environments\n\n### Server\n\nPuck supports the server environment for the following APIs:\n\n- The [`<Render>`](/docs/api-reference/components/render) component, for rendering pages produced by Puck\n- The [`resolveAllData`](/docs/api-reference/functions/resolve-all-data) lib, for running all [data resolvers](/docs/integrating-puck/dynamic-props)\n\nThese APIs can be used in an RSC environment, but in order to do so the Puck config that they reference must be RSC-friendly.\n\nThis can be done by either avoiding client-only code (React `useState`, etc), or split out client components with the `\"use client\";` directive.\n\n### Client\n\nAll other Puck APIs, including the core `<Puck>` component, cannot run in an RSC environment due to their high-degree of interactivity.\n\nAs these APIs render on the client, the Puck config provided must be safe for client-use, avoiding any server-specific logic.\n\n## Implementations\n\nSince the Puck config can be referenced on the client or the server, we need to consider how to satisfy both environments.\n\nThere are three approaches to this:\n\n1. Avoid using any client-specific functionality (like React `useState`) in your components\n2. Mark your components up with the `\"use client\";` directive if you need client-specific functionality\n3. Create separate configs for client and server rendering\n\n### 1. Avoid client-specific code\n\nAvoiding client-specific code is the easiest way to support RSC across both environments, but may not be realistic for all users. This normally means avoiding React hooks like `useState` or `useContext`.\n\n<Callout type=\"info\">If you're using the legacy [`<DropZone>` component](/docs/api-reference/components/drop-zone), you will may encounter issues with server components. We recommend migrating to the [`slot` field](/docs/api-reference/fields/slot) which provides native server component support. Alternatively, see [our previous server components guide](https://puckeditor.com/v/0.18.3/docs/integrating-puck/server-components) for information on handling DropZones.</Callout>\n\n### 2. Marking up components with `\"use client\";`\n\nMany modern component libraries will require some degree of client-side behaviour. For these cases, you'll need to mark them up with the `\"use client\";` directive.\n\nTo achieve this, you must import each of those component from a separate file:\n\n```tsx copy showLineNumbers filename=\"puck.config.tsx\"\nimport type { Config } from \"@puckeditor/core\";\nimport type { HeadingBlockProps } from \"./components/HeadingBlock\";\nimport HeadingBlock from \"./components/HeadingBlock\";\n\ntype Props = {\n  HeadingBlock: HeadingBlockProps;\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      // You must call the component, rather than passing it in directly. This will change in the future.\n      render: ({ title }) => <HeadingBlock title={title} />,\n    },\n  },\n};\n```\n\nAnd add the `\"use client\";` directive to the top of each component file:\n\n```tsx copy showLineNumbers filename=\"components/HeadingBlock.tsx\" {1}\n\"use client\";\n\nimport { useState } from \"react\";\n\nexport type HeadingBlockProps = {\n  title: string;\n};\n\nexport default ({ title }: { title: string }) => {\n  useState(); // useState fails on the server\n\n  return (\n    <div style={{ padding: 64 }}>\n      <h1>{title}</h1>\n    </div>\n  );\n};\n```\n\nThis config can now be rendered inside an RSC component, such as a Next.js app router page:\n\n```tsx copy showLineNumbers filename=\"app/page.tsx\"\nimport { config } from \"../puck.config.tsx\";\n\nexport default async function Page() {\n  const data = await getData(); // Some server function\n\n  const resolvedData = await resolveAllData(data, config); // Optional call to resolveAllData, if this needs to run server-side\n\n  return <Render data={resolvedData} config={config} />;\n}\n```\n\n### 3. Creating separate configs\n\nAlternatively, consider entirely separate configs for the `<Puck>` and `<Render>` components. This approach can enable you to have different rendering behavior for a component for when it renders on the client or the server.\n\nCreate a shared config type:\n\n```tsx copy showLineNumbers filename=\"puck-types.ts\"\nimport type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: {\n    title: string;\n  };\n};\n\nexport type UserConfig = Config<Props>;\n```\n\nDefine a client component config for use within the `<Puck>` component:\n\n```tsx copy showLineNumbers filename=\"puck.config.client.tsx\"\nimport type { UserConfig } from \"./puck-types.ts\";\n\nexport const config: UserConfig = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => {\n        useState(); // useState fails on the server\n\n        return (\n          <div style={{ padding: 64 }}>\n            <h1>{title}</h1>\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\nDefine a server config using the shared types for use within the `<Render>` component, excluding fields as they are unnecessary in this environment:\n\n```tsx copy showLineNumbers filename=\"puck.config.server.tsx\"\nimport type { UserConfig } from \"./puck-types.ts\";\n\nexport const config: UserConfig = {\n  components: {\n    HeadingBlock: {\n      render: ({ title }) => {\n        return (\n          <div style={{ padding: 64 }}>\n            <h1>{title}</h1>\n          </div>\n        );\n      },\n    },\n  },\n};\n```\n\nRender the appropriate config depending on the environment. Here's a Next.js app router example of a server render:\n\n```tsx copy showLineNumbers filename=\"app/page.tsx\"\nimport { config } from \"../puck.config.server.tsx\";\n\nexport default async function Page() {\n  const data = await getData(); // Some server function\n\n  return <Render data={data} config={config} />;\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/docs/integrating-puck/viewports.mdx",
    "content": "# Viewports\n\nThe Puck preview renders in a same-origin iframe that can be resized to simulate different viewports.\n\n## Default viewports\n\nPuck provides 4 viewports [by default](/docs/api-reference/components/puck#default-viewports):\n\n1. Small: 360px wide\n2. Medium: 768px wide\n3. Large: 1280px wide\n4. Full-width: Fit the container\n\nEach of the default viewports have 100% height, filling the available space (via the `auto` height parameter).\n\n## Customizing viewports\n\nCustomizing the available viewports using the [`viewports` API](/docs/api-reference/components/puck#viewports):\n\n```tsx\nexport function Editor() {\n  return (\n    <Puck\n      viewports={[\n        {\n          width: 1440,\n          height: \"auto\", // Optional height. Can be numeric or \"auto\". Defaults to \"auto\".\n          label: \"My Viewport\", // Optional. Shown in tooltip.\n          icon: <svg />, // Optional. Use lucide-icons to align with Puck UI.\n        },\n      ]}\n      // ...\n    />\n  );\n}\n```\n\n## Opting out of iframes\n\nOpt-out of iframe rendering by using the [`iframe` API](/docs/api-reference/components/puck#iframe):\n\n```tsx\nexport function Editor() {\n  return (\n    <Puck\n      iframe={{\n        enabled: false,\n      }}\n      // ...\n    />\n  );\n}\n```\n\nThis will disable all viewport functionality.\n\n## Controlling viewports with compositional interfaces\n\nWhen implementing a [compositional interface](/docs/extending-puck/composition), the `viewports` API will have no effect. Instead, the viewport size can be controlled by the dimensions of the wrapping element that contains [`<Puck.Preview />`](/docs/api-reference/components/puck-preview).\n\nCSS transforms can be used to zoom the viewport without impacting drag-and-drop behaviour.\n\n```tsx {6-8}\nimport { Puck } from \"@puckeditor/core\";\n\nexport function Editor() {\n  return (\n    <Puck>\n      <div style={{ transform: \"scale(0.5)\", width: 1280 }}>\n        <Puck.Preview />\n      </div>\n    </Puck>\n  );\n}\n```\n"
  },
  {
    "path": "apps/docs/pages/index.mdx",
    "content": "---\ntitle: Puck - The open-source visual editor for React\n---\n\nimport { Home } from \"../components/Home\";\n\n<Home />\n"
  },
  {
    "path": "apps/docs/public/manifest.webmanifest",
    "content": "{\n  \"name\": \"Puck\",\n  \"short_name\": \"Puck\",\n  \"start_url\": \"/\",\n  \"icons\": [\n    {\n      \"src\": \"/maskable_icon_x192.png\",\n      \"sizes\": \"192x192\",\n      \"type\": \"image/png\"\n    },\n    {\n      \"src\": \"/maskable_icon.png\",\n      \"sizes\": \"512x512\",\n      \"type\": \"image/png\"\n    }\n  ],\n  \"theme_color\": \"#111\",\n  \"background_color\": \"#ffffff\",\n  \"display\": \"standalone\"\n}\n"
  },
  {
    "path": "apps/docs/public/robots.txt",
    "content": "User-Agent: *\nDisallow: /v/*\n"
  },
  {
    "path": "apps/docs/releases.json",
    "content": "{\n  \"canary\": \"https://puck-docs-git-main-measured.vercel.app\",\n  \"0.18.3\": \"https://puck-docs-git-releases-v0183-measured.vercel.app\",\n  \"0.18.2\": \"https://puck-docs-git-releases-v0182-measured.vercel.app\",\n  \"0.18.1\": \"https://puck-docs-git-releases-v0181-measured.vercel.app\",\n  \"0.18.0\": \"https://puck-docs-git-releases-v0180-measured.vercel.app\",\n  \"0.17.4\": \"https://puck-docs-git-releases-v0174-measured.vercel.app\",\n  \"0.17.3\": \"https://puck-docs-git-releases-v0173-measured.vercel.app\",\n  \"0.17.2\": \"https://puck-docs-git-releases-v0172-measured.vercel.app\",\n  \"0.17.1\": \"https://puck-docs-git-releases-v0171-measured.vercel.app\",\n  \"0.17.0\": \"https://puck-docs-git-releases-v0170-measured.vercel.app\",\n  \"0.16.2\": \"https://puck-docs-git-releases-v0162-measured.vercel.app\",\n  \"0.16.1\": \"https://puck-docs-git-releases-v0161-measured.vercel.app\",\n  \"0.16.0\": \"https://puck-docs-git-releases-v0160-measured.vercel.app\",\n  \"0.15.0\": \"https://puck-docs-git-releases-v0150-measured.vercel.app\",\n  \"0.14.2\": \"https://puck-docs-git-releases-v0142-measured.vercel.app\",\n  \"0.14.1\": \"https://puck-docs-git-releases-v0141-measured.vercel.app\",\n  \"0.14.0\": \"https://puck-docs-git-releases-v0140-measured.vercel.app\",\n  \"0.13.1\": \"https://puck-docs-git-releases-v0131-measured.vercel.app\",\n  \"0.13.0\": \"https://puck-docs-git-releases-v0130-measured.vercel.app\",\n  \"0.12.0\": \"https://puck-docs-git-releases-v0120-measured.vercel.app\"\n}\n"
  },
  {
    "path": "apps/docs/styles.css",
    "content": "@import \"../../packages/core/styles.css\";\n\narticle p a,\narticle li a,\narticle td a {\n  color: var(--puck-color-azure-05) !important;\n}\n\narticle p a:hover,\narticle li a:hover,\narticle td a:hover {\n  color: var(--puck-color-azure-03) !important;\n}\n\nhtml.dark p a:hover,\nhtml.dark li a:hover {\n  color: var(--puck-color-azure-07) !important;\n}\n\nli > a {\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "apps/docs/theme.config.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport { useRouter } from \"next/router\";\nimport { DocsThemeConfig, useConfig } from \"nextra-theme-docs\";\n\nimport { ReleaseSwitcher } from \"./components/ReleaseSwitcher\";\nimport { FooterActions } from \"./components/FooterActions\";\nimport { Viewport } from \"./components/Viewport\";\n\nconst Head = () => {\n  const { asPath, defaultLocale, locale } = useRouter();\n  const { frontMatter, title } = useConfig();\n\n  const siteUrl = \"https://puckeditor.com\";\n  const url =\n    siteUrl + (defaultLocale === locale ? asPath : `/${locale}${asPath}`);\n\n  const defaultTitle = `Puck - The open-source visual editor for React`;\n  const description =\n    frontMatter.description ||\n    `Puck empowers developers to build amazing visual editing experiences into their own React applications, powering the next generation of content tools.`;\n\n  return (\n    <>\n      <link rel=\"canonical\" href={`${siteUrl}${asPath}`} />\n      <meta property=\"og:url\" content={url} />\n      <meta property=\"description\" content={description} />\n      <meta property=\"og:description\" content={description} />\n      <meta property=\"og:type\" content=\"website\" />\n      <meta property=\"og:image\" content={`${siteUrl}/social.png`} />\n      <meta property=\"og:image:height\" content=\"675\" />\n      <meta property=\"og:image:width\" content=\"1200\" />\n      <meta property=\"og:image:alt\" content=\"Puck\" />\n      <meta property=\"og:image:type\" content=\"image/png\" />\n      <meta property=\"og:locale\" content=\"en\" />\n      <meta property=\"og:site_name\" content={defaultTitle} />\n      <meta name=\"image\" content={`${siteUrl}/social.png`} />\n      <meta itemProp=\"image\" content={`${siteUrl}/social.png`} />\n      <meta name=\"twitter:card\" content=\"summary_large_image\" />\n      <meta name=\"twitter:image\" content={`${siteUrl}/social.png`} />\n      <meta name=\"twitter:description\" content={description} />\n      <meta name=\"twitter:image:alt\" content=\"Puck\" />\n      <meta name=\"twitter:image:height\" content=\"675\" />\n      <meta name=\"twitter:image:type\" content=\"image/png\" />\n      <meta name=\"twitter:image:width\" content=\"1200\" />\n      <meta name=\"twitter:site\" content=\"@puckeditor\" />\n      <meta\n        name=\"twitter:title\"\n        content={title !== defaultTitle ? `${title} - Puck` : defaultTitle}\n      />\n      <title>{title !== defaultTitle ? `${title} - Puck` : defaultTitle}</title>\n\n      <link rel=\"icon\" href=\"/favicon.ico\" sizes=\"48x48\" />\n      <link rel=\"icon\" href=\"/favicon.svg\" type=\"image/svg+xml\" />\n      <link rel=\"apple-touch-icon\" href=\"/apple-touch-icon.png\" />\n      <link rel=\"manifest\" href=\"/manifest.webmanifest\" />\n      <script\n        type=\"application/ld+json\"\n        dangerouslySetInnerHTML={{\n          __html: `{\n      \"@context\" : \"https://schema.org\",\n      \"@type\" : \"WebSite\",\n      \"name\" : \"Puck\",\n      \"url\" : \"https://puckeditor.com/\"\n    }`,\n        }}\n      />\n      {asPath == \"/\" && (\n        <script\n          type=\"application/ld+json\"\n          dangerouslySetInnerHTML={{\n            __html: `${JSON.stringify({\n              \"@context\": \"https://schema.org\",\n              \"@type\": \"WebSite\",\n              name: \"Puck\",\n              url: siteUrl,\n            })}`,\n          }}\n        />\n      )}\n    </>\n  );\n};\n\nconst theme: DocsThemeConfig = {\n  head: Head,\n  logo: (\n    <div>\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        viewBox=\"0 0 1300 326\"\n        width=\"1300\"\n        height=\"326\"\n        style={{ width: 128, marginBottom: -5, height: 32 }}\n        fill=\"currentColor\"\n      >\n        <path d=\"M368.9 5.9H455c48.1 0 88.1 15.4 88.1 70.4 0 54.4-37 71.1-85.8 71.1H420v90.4h-51.1V5.9zm51.1 98.2h34c18 0 36-6.2 36-27.8 0-23.9-24.2-27.2-43.9-27.2H420v55zM786 148.3c0 54.7-33.4 95.3-97.6 95.3-64.5 0-97.9-40.6-97.9-95.3V5.9h51.1v140.5c0 28.5 19.6 50.1 46.8 50.1 26.8 0 46.5-21.6 46.5-50.1V5.9H786v142.4zM997.1 66.1c-10.1-12.1-24.9-19-43.9-19-38.6 0-67.1 31.4-67.1 74.7s28.5 74.7 65.5 74.7c20.6 0 37.3-9.2 47.8-24.9l42.6 31.8c-19.3 27.5-52.1 40.3-83.8 40.3-72.4 0-125.1-47.5-125.1-121.8C833.1 47.5 885.8 0 958.2 0c25.9 0 58.6 8.8 78.3 34.1l-39.4 32zM1083.2 5.9h51.1v96.3l90-96.3h66.8L1188 113.6l112 124.1h-71.4l-94.3-110v110h-51.1V5.9zM149.3 237.7H82.5v-24.4h66.9v24.4zm82.5-82.5h-24.4V88.4h24.4v66.8zm-207.4 0H0V88.4h24.4v66.8zM149.3 30.3H82.5V5.9h66.9v24.4zM45.6 237.7H0v-45.6h24.4v21.2h21.2v24.4zM231.8 51.5h-24.4V30.3h-21.2V5.9h45.6v45.6zm-207.4 0H0V5.9h45.6v24.4H24.4v21.2zM164.8 170.7l27.5 155.2L320 198.2l-155.2-27.5z\" />\n      </svg>\n    </div>\n  ),\n  project: {\n    link: \"https://github.com/puckeditor/puck\",\n  },\n  footer: {\n    content: (\n      <div className=\"flex w-full flex-col items-center sm:items-start\">\n        <p className=\"mt-6 text-xs\">\n          MIT © {new Date().getFullYear()}{\" \"}\n          <a\n            style={{ textDecoration: \"underline\" }}\n            href=\"https://github.com/puckeditor/puck/graphs/contributors\"\n          >\n            The Puck Contributors\n          </a>\n        </p>\n      </div>\n    ),\n  },\n  chat: {\n    link: \"https://discord.gg/D9e4E3MQVZ\",\n    icon: (\n      <svg\n        xmlns=\"http://www.w3.org/2000/svg\"\n        width=\"24\"\n        height=\"24\"\n        preserveAspectRatio=\"xMidYMid\"\n        viewBox=\"0 -28.5 256 256\"\n        fill=\"currentColor\"\n      >\n        <path d=\"M216.856 16.597A208.502 208.502 0 0 0 164.042 0c-2.275 4.113-4.933 9.645-6.766 14.046-19.692-2.961-39.203-2.961-58.533 0-1.832-4.4-4.55-9.933-6.846-14.046a207.809 207.809 0 0 0-52.855 16.638C5.618 67.147-3.443 116.4 1.087 164.956c22.169 16.555 43.653 26.612 64.775 33.193A161.094 161.094 0 0 0 79.735 175.3a136.413 136.413 0 0 1-21.846-10.632 108.636 108.636 0 0 0 5.356-4.237c42.122 19.702 87.89 19.702 129.51 0a131.66 131.66 0 0 0 5.355 4.237 136.07 136.07 0 0 1-21.886 10.653c4.006 8.02 8.638 15.67 13.873 22.848 21.142-6.58 42.646-16.637 64.815-33.213 5.316-56.288-9.08-105.09-38.056-148.36ZM85.474 135.095c-12.645 0-23.015-11.805-23.015-26.18s10.149-26.2 23.015-26.2c12.867 0 23.236 11.804 23.015 26.2.02 14.375-10.148 26.18-23.015 26.18Zm85.051 0c-12.645 0-23.014-11.805-23.014-26.18s10.148-26.2 23.014-26.2c12.867 0 23.236 11.804 23.015 26.2 0 14.375-10.148 26.18-23.015 26.18Z\" />\n      </svg>\n    ),\n  },\n  toc: {\n    backToTop: true,\n  },\n  banner:\n    process.env.NEXT_PUBLIC_IS_LATEST === \"true\"\n      ? {\n          dismissible: true,\n          key: \"v0.18.0\",\n          content: (\n            <a\n              href=\"https://github.com/puckeditor/puck/releases\"\n              target=\"_blank\"\n            >\n              <b>🎈 Puck 0.18</b>: The new drag-and-drop engine is here, with\n              CSS grid & flexbox support →\n            </a>\n          ),\n        }\n      : {},\n  docsRepositoryBase: \"https://github.com/puckeditor/puck/tree/main/apps/docs\",\n  navbar: {\n    extraContent: () => (\n      <Viewport desktop>\n        <ReleaseSwitcher />\n      </Viewport>\n    ),\n  },\n  themeSwitch: {\n    component: FooterActions,\n  },\n};\n\nexport default theme;\n"
  },
  {
    "path": "apps/docs/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Default\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"inlineSources\": false,\n    \"isolatedModules\": true,\n    \"moduleResolution\": \"node\",\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"preserveWatchOutput\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/docs/tsconfig/nextjs.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Next.js\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"allowJs\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"incremental\": true,\n    \"jsx\": \"preserve\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"strict\": false,\n    \"target\": \"es6\"\n  },\n  \"include\": [\"src\", \"next-env.d.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "apps/docs/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig/nextjs.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"paths\": {\n      \"@/puck\": [\"../../packages/core\"],\n      \"@/core\": [\"../../packages/core\"],\n      \"@/core/*\": [\"../../packages/core/*\"],\n      \"@/plugin-heading-analyzer\": [\"../../packages/plugin-heading-analyzer\"],\n      \"@/plugin-heading-analyzer/*\": [\n        \"../../packages/plugin-heading-analyzer/*\"\n      ],\n      \"@/docs/*\": [\"./*\"]\n    }\n  },\n  \"include\": [\n    \"next-env.d.ts\",\n    \"**/*.ts\",\n    \"**/*.tsx\",\n    \".next/types/**/*.ts\",\n    \"pages/index.mdx\"\n  ],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "lerna.json",
    "content": "{\n  \"packages\": [\n    \"apps/docs\",\n    \"packages/core\",\n    \"packages/create-puck-app\",\n    \"packages/field-contentful\",\n    \"packages/plugin-emotion-cache\",\n    \"packages/plugin-heading-analyzer\"\n  ],\n  \"version\": \"0.21.1\",\n  \"npmClient\": \"yarn\",\n  \"$schema\": \"node_modules/lerna/schemas/lerna-schema.json\"\n}"
  },
  {
    "path": "package.json",
    "content": "{\n  \"private\": true,\n  \"scripts\": {\n    \"build\": \"turbo run build\",\n    \"dev\": \"rimraf packages/core/dist && turbo run dev --filter=demo\",\n    \"lint\": \"turbo run lint\",\n    \"format:check\": \"prettier --check \\\"**/*.{ts,tsx,md,mdx,css}\\\"\",\n    \"format\": \"prettier --write \\\"**/*.{ts,tsx,md,mdx,css}\\\"\",\n    \"release\": \"yarn release:prepare && yarn changelog && yarn release-commit\",\n    \"release:prepare\": \"git fetch --tags && conventional-recommended-bump -p angular | xargs yarn version:auto $1\",\n    \"release:canary\": \"yarn release:prepare && node scripts/get-unstable-version canary | xargs yarn version:auto $1\",\n    \"release-commit\": \"git add -u && git commit -m \\\"release: v${npm_package_version}\\\"\",\n    \"smoke\": \"node scripts/e2e/smoke.mjs\",\n    \"test\": \"turbo run test\",\n    \"version\": \"lerna version --force-publish -y --no-push --no-changelog --no-git-tag-version $npm_package_version\",\n    \"version:auto\": \"yarn version --no-git-tag-version --new-version $1\",\n    \"changelog\": \"node scripts/create-changelog\"\n  },\n  \"devDependencies\": {\n    \"@turbo/gen\": \"^2.3.3\",\n    \"asciichart\": \"^1.5.25\",\n    \"conventional-changelog-angular\": \"^4.0.0\",\n    \"conventional-recommended-bump\": \"^6.0.5\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-custom\": \"*\",\n    \"lerna\": \"^9.0.0\",\n    \"prettier\": \"^2.5.1\",\n    \"puppeteer\": \"^24.8.2\",\n    \"rimraf\": \"^6.1.2\",\n    \"standard-changelog\": \"^2.0.21\",\n    \"turbo\": \"^2.5.8\"\n  },\n  \"name\": \"puck-repo\",\n  \"packageManager\": \"yarn@1.22.19\",\n  \"workspaces\": [\n    \"apps/*\",\n    \"recipes/*\",\n    \"packages/*\"\n  ],\n  \"version\": \"0.21.1\",\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/core/.gitignore",
    "content": "README.md"
  },
  {
    "path": "packages/core/bundle/core.css",
    "content": "@import \"../styles/color.css\";\n@import \"../styles/typography.css\";\n\n#frame-root {\n  height: 1px; /* Enables DropZone to inherit min-height */\n  min-height: 100vh;\n}\n\n[data-puck-entry] {\n  /* Reset stacking context */\n  position: relative;\n  z-index: 0;\n}\n"
  },
  {
    "path": "packages/core/bundle/core.ts",
    "content": "export type { PuckAction } from \"../reducer/actions\";\n\nexport * from \"../types/API\";\nexport * from \"../types\";\nexport * from \"../types/Data\";\nexport * from \"../types/Props\";\nexport * from \"../types/Fields\";\n\nexport * from \"../components/ActionBar\";\nexport { AutoField, FieldLabel } from \"../components/AutoField\";\n\nexport * from \"../components/Button\";\nexport { Drawer } from \"../components/Drawer\";\n\nexport { DropZone } from \"../components/DropZone\";\nexport * from \"../components/IconButton\";\nexport { Puck } from \"../components/Puck\";\nexport * from \"../components/Render\";\nexport { RichTextMenu } from \"../components/RichTextMenu/inner\";\n\nexport * from \"../lib/migrate\";\nexport * from \"../lib/transform-props\";\nexport { registerOverlayPortal } from \"../lib/overlay-portal\";\nexport * from \"../lib/resolve-all-data\";\nexport { setDeep } from \"../lib/data/set-deep\";\nexport { walkTree } from \"../lib/data/walk-tree\";\nexport {\n  createUsePuck,\n  usePuck,\n  useGetPuck,\n  type UsePuckData,\n  type PuckApi,\n} from \"../lib/use-puck\";\n\nexport * from \"../plugins/blocks\";\nexport * from \"../plugins/fields\";\nexport * from \"../plugins/outline\";\nexport * from \"../plugins/legacy-side-bar\";\n"
  },
  {
    "path": "packages/core/bundle/index.css",
    "content": "@import url(\"https://rsms.me/inter/inter.css\");\n@import \"./core.css\";\n"
  },
  {
    "path": "packages/core/bundle/index.ts",
    "content": "import \"./index.css\";\n\nexport * from \"./core\";\n"
  },
  {
    "path": "packages/core/bundle/internal.ts",
    "content": "export { createReducer } from \"../reducer\";\n"
  },
  {
    "path": "packages/core/bundle/no-external.css",
    "content": "@import \"./core.css\";\n"
  },
  {
    "path": "packages/core/bundle/no-external.ts",
    "content": "import \"./no-external.css\";\n\nexport * from \"./core\";\n"
  },
  {
    "path": "packages/core/bundle/rsc.tsx",
    "content": "export { Render } from \"../components/ServerRender\";\n\nexport * from \"../lib/resolve-all-data\";\nexport * from \"../lib/transform-props\";\nexport * from \"../lib/migrate\";\nexport { walkTree } from \"../lib/data/walk-tree\";\n"
  },
  {
    "path": "packages/core/components/ActionBar/index.tsx",
    "content": "import { ReactNode, SyntheticEvent } from \"react\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\nconst getClassName = getClassNameFactory(\"ActionBar\", styles);\nconst getActionClassName = getClassNameFactory(\"ActionBarAction\", styles);\n\nexport const ActionBar = ({\n  label,\n  children,\n}: {\n  label?: string;\n  children?: ReactNode;\n}) => (\n  <div\n    className={getClassName()}\n    onClick={(e) => {\n      e.stopPropagation();\n    }}\n  >\n    {label && (\n      <ActionBar.Group>\n        <div className={getClassName(\"label\")}>{label}</div>\n      </ActionBar.Group>\n    )}\n    {children}\n  </div>\n);\n\nexport const Action = ({\n  children,\n  label,\n  onClick,\n  active = false,\n  disabled,\n}: {\n  children: ReactNode;\n  label?: string;\n  onClick: (e: SyntheticEvent) => void;\n  active?: boolean;\n  disabled?: boolean;\n}) => (\n  <button\n    type=\"button\"\n    className={getActionClassName({ active, disabled })}\n    onClick={onClick}\n    title={label}\n    tabIndex={0}\n    disabled={disabled}\n  >\n    {children}\n  </button>\n);\n\nexport const Group = ({ children }: { children: ReactNode }) => (\n  <div className={getClassName(\"group\")}>{children}</div>\n);\n\nexport const Label = ({ label }: { label: string }) => (\n  <div className={getClassName(\"label\")}>{label}</div>\n);\n\nexport const Separator = () => <div className={getClassName(\"separator\")} />;\n\nActionBar.Action = Action;\nActionBar.Label = Label;\nActionBar.Group = Group;\nActionBar.Separator = Separator;\n"
  },
  {
    "path": "packages/core/components/ActionBar/styles.module.css",
    "content": ".ActionBar {\n  align-items: center;\n  cursor: default;\n  display: flex;\n  width: auto;\n  padding: 4px;\n  padding-inline-start: 0;\n  padding-inline-end: 0;\n  border-top-left-radius: 8px;\n  border-top-right-radius: 8px;\n  border-radius: 8px;\n  background: var(--puck-color-grey-01);\n  color: var(--puck-color-white);\n  font-family: var(--puck-font-family);\n  min-height: 26px;\n}\n\n.ActionBar-label {\n  color: var(--puck-color-grey-08);\n  font-size: var(--puck-font-size-xxxs);\n  font-weight: 500;\n  padding-inline-start: 8px;\n  padding-inline-end: 8px;\n  margin-inline-start: 4px;\n  margin-inline-end: 4px;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.ActionBarAction + .ActionBar-label {\n  padding-inline-start: 0;\n}\n\n.ActionBar-label + .ActionBarAction {\n  margin-inline-start: -4px;\n}\n\n.ActionBar-group {\n  align-items: center;\n  border-inline-start: 0.5px solid var(--puck-color-grey-05); /* Fractional value required due to scaling */\n  display: flex;\n  height: 100%;\n  padding-inline-start: 4px;\n  padding-inline-end: 4px;\n}\n\n.ActionBar-group:first-of-type {\n  border-inline-start: 0;\n}\n\n.ActionBar-group:empty {\n  display: none;\n}\n\n.ActionBarAction {\n  background: transparent;\n  border: none;\n  color: var(--puck-color-grey-08);\n  cursor: pointer;\n  padding: 6px;\n  margin-inline-start: 4px;\n  margin-inline-end: 4px;\n  border-radius: 4px;\n  overflow: hidden;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transition: color 50ms ease-in;\n}\n\n.ActionBarAction--disabled {\n  cursor: auto;\n  color: var(--puck-color-grey-06);\n}\n\n.ActionBarAction svg {\n  max-width: none !important; /* Explicit definition to prevent some SVG style pollution */\n}\n\n.ActionBarAction:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: -2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ActionBarAction:hover:not(.ActionBarAction--disabled) {\n    color: var(--puck-color-azure-06);\n    transition: none;\n  }\n}\n\n.ActionBarAction:active:not(.ActionBarAction--disabled),\n.ActionBarAction--active {\n  color: var(--puck-color-azure-07);\n  transition: none;\n}\n\n.ActionBar-group * {\n  margin: 0;\n}\n\n.ActionBar-separator {\n  background: var(--puck-color-grey-05);\n  margin-inline: 4px;\n  width: 0.5px; /* Fractional value required due to scaling */\n  height: 100%;\n}\n"
  },
  {
    "path": "packages/core/components/AutoField/FieldLabel.tsx",
    "content": "import getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { Field, FieldProps } from \"../../types\";\n\nimport styles from \"./styles.module.css\";\nimport { ReactNode, useMemo } from \"react\";\nimport { Lock } from \"lucide-react\";\nimport { useAppStore } from \"../../store\";\n\nconst getClassName = getClassNameFactory(\"Input\", styles);\n\nexport const FieldLabel = ({\n  children,\n  icon,\n  label,\n  el = \"label\",\n  readOnly,\n  className,\n}: {\n  children?: ReactNode;\n  icon?: ReactNode;\n  label: string;\n  el?: \"label\" | \"div\";\n  readOnly?: boolean;\n  className?: string;\n}) => {\n  const El = el;\n  return (\n    <El className={className}>\n      <div className={getClassName(\"label\")}>\n        {icon ? <div className={getClassName(\"labelIcon\")}>{icon}</div> : <></>}\n        {label}\n\n        {readOnly && (\n          <div className={getClassName(\"disabledIcon\")} title=\"Read-only\">\n            <Lock size=\"12\" />\n          </div>\n        )}\n      </div>\n      {children}\n    </El>\n  );\n};\n\nexport type FieldLabelPropsInternal = {\n  children?: ReactNode;\n  icon?: ReactNode;\n  label?: string;\n  el?: \"label\" | \"div\";\n  readOnly?: boolean;\n};\n\nexport const FieldLabelInternal = ({\n  children,\n  icon,\n  label,\n  el = \"label\",\n  readOnly,\n}: FieldLabelPropsInternal) => {\n  const overrides = useAppStore((s) => s.overrides);\n\n  const Wrapper = useMemo(\n    () => overrides.fieldLabel || FieldLabel,\n    [overrides]\n  );\n\n  if (!label) {\n    return <>{children}</>;\n  }\n\n  return (\n    <Wrapper\n      label={label}\n      icon={icon}\n      className={getClassName({ readOnly })}\n      readOnly={readOnly}\n      el={el}\n    >\n      {children}\n    </Wrapper>\n  );\n};\n\nexport type FieldPropsInternalOptional<ValueType = any, F = Field<any>> = Omit<\n  FieldProps<F, ValueType>,\n  \"value\"\n> & {\n  Label?: React.FC<FieldLabelPropsInternal>;\n  label?: string;\n  labelIcon?: ReactNode;\n  name?: string;\n};\n\nexport type FieldPropsInternal<ValueType = any, F = Field<any>> = FieldProps<\n  F,\n  ValueType\n> & {\n  Label: React.FC<FieldLabelPropsInternal>;\n  label?: string;\n  labelIcon?: ReactNode;\n  id: string;\n  name?: string;\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/context.tsx",
    "content": "import { createContext, PropsWithChildren, useContext, useMemo } from \"react\";\n\ntype NestedFieldContext = {\n  localName?: string;\n  readOnlyFields?: Partial<Record<string | number | symbol, boolean>>;\n};\n\nexport const NestedFieldContext = createContext<NestedFieldContext>({});\n\nexport const useNestedFieldContext = () => {\n  const context = useContext(NestedFieldContext);\n\n  return {\n    ...context,\n    readOnlyFields: context.readOnlyFields || {},\n  };\n};\n\nexport const NestedFieldProvider = ({\n  children,\n  name,\n  subName,\n  wildcardName = name,\n  readOnlyFields,\n}: PropsWithChildren<{\n  name: string;\n  subName: string;\n  wildcardName?: string;\n  readOnlyFields: Partial<Record<string | number | symbol, boolean>>;\n}>) => {\n  const subPath = `${name}.${subName}`;\n  const wildcardSubPath = `${wildcardName}.${subName}`;\n\n  const subReadOnlyFields = useMemo(\n    () =>\n      Object.keys(readOnlyFields).reduce((acc, readOnlyKey) => {\n        const isLocal =\n          readOnlyKey.indexOf(subPath) > -1 ||\n          readOnlyKey.indexOf(wildcardSubPath) > -1;\n\n        if (isLocal) {\n          const subPathPattern = new RegExp(\n            `^(${name}|${wildcardName})\\.`\n              .replace(/\\[/g, \"\\\\[\")\n              .replace(/\\]/g, \"\\\\]\")\n              .replace(/\\./g, \"\\\\.\")\n              .replace(/\\*/g, \"\\\\*\")\n          );\n\n          const localName = readOnlyKey.replace(subPathPattern, \"\");\n\n          return {\n            ...acc,\n            [localName]: readOnlyFields[readOnlyKey],\n          };\n        }\n\n        return acc;\n      }, {}),\n    [name, subName, wildcardName, readOnlyFields]\n  );\n\n  return (\n    <NestedFieldContext.Provider\n      value={{ readOnlyFields: subReadOnlyFields, localName: subName }}\n    >\n      {children}\n    </NestedFieldContext.Provider>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/ArrayField/index.tsx",
    "content": "import getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\nimport { Copy, List, Plus, Trash } from \"lucide-react\";\nimport type { FieldPropsInternal } from \"../..\";\nimport { useFieldStore, useFieldStoreApi } from \"../../store\";\nimport { IconButton } from \"../../../IconButton\";\nimport { reorder, replace } from \"../../../../lib\";\nimport {\n  memo,\n  ReactNode,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { DragIcon } from \"../../../DragIcon\";\nimport {\n  ArrayField as ArrayFieldType,\n  ArrayState,\n  Content,\n  ItemWithId,\n} from \"../../../../types\";\nimport { useAppStore, useAppStoreApi } from \"../../../../store\";\nimport { Sortable, SortableProvider } from \"../../../Sortable\";\nimport { useNestedFieldContext } from \"../../context\";\nimport { walkField } from \"../../../../lib/data/map-fields\";\nimport { populateIds } from \"../../../../lib/data/populate-ids\";\nimport { defaultSlots } from \"../../../../lib/data/default-slots\";\nimport { getDeep } from \"../../../../lib/data/get-deep\";\nimport { SubField } from \"../../subfield\";\nimport { setDeep } from \"../../../../lib/data/set-deep\";\n\nconst getClassName = getClassNameFactory(\"ArrayField\", styles);\nconst getClassNameItem = getClassNameFactory(\"ArrayFieldItem\", styles);\n\nconst ItemSummaryInner = ({\n  index,\n  originalIndex,\n  field,\n  name,\n}: {\n  index: number;\n  originalIndex: number;\n  field: ArrayFieldType;\n  name?: string;\n}) => {\n  const data = useFieldStore((s) => {\n    const path = `${[name]}[${index}]`;\n    return getDeep(s, path);\n  });\n\n  const itemSummary = useMemo(() => {\n    if (data && field.getItemSummary) {\n      return field.getItemSummary(data, index);\n    }\n\n    return `Item #${originalIndex}`;\n  }, [data, field, originalIndex, index]);\n\n  return itemSummary;\n};\n\nconst ItemSummary = memo(ItemSummaryInner);\n\nconst ArrayFieldItemInternal = ({\n  id,\n  arrayId,\n  index,\n  dragIndex,\n  originalIndex,\n  field,\n  onChange,\n  onToggleExpand,\n  readOnly,\n  actions,\n  name,\n  localName,\n}: {\n  id: string;\n  arrayId: string;\n  index: number;\n  dragIndex: number;\n  originalIndex: number;\n  field: ArrayFieldType;\n  onChange: (val: any, ui: any, subName: string) => void;\n  onToggleExpand: (id: string, isExpanded: boolean) => void;\n  readOnly?: boolean;\n  actions: ReactNode;\n  name?: string;\n  localName?: string;\n}) => {\n  // NB this will prevent array fields from being used outside of Puck\n  const isExpanded = useAppStore((s) => {\n    return s.state.ui.arrayState[arrayId]?.openId === id;\n  });\n\n  // NB this will prevent array fields from being used outside of Puck\n  const canEdit = useAppStore(\n    (s) => s.permissions.getPermissions({ item: s.selectedItem }).edit\n  );\n\n  const hasVisibleFields = useMemo(() => {\n    if (!field.arrayFields) {\n      return false;\n    }\n\n    return Object.values(field.arrayFields).some(\n      (subField) => subField.type !== \"slot\" && subField.visible !== false\n    );\n  }, [field.arrayFields]);\n\n  return (\n    <Sortable id={id} index={dragIndex} disabled={readOnly}>\n      {({ isDragging, ref, handleRef }) => (\n        <div\n          ref={ref}\n          className={getClassNameItem({\n            isExpanded: isExpanded && hasVisibleFields,\n            isDragging,\n            noFields: !hasVisibleFields,\n          })}\n        >\n          <div\n            ref={handleRef}\n            onClick={(e) => {\n              if (isDragging) return;\n\n              e.preventDefault();\n              e.stopPropagation();\n\n              if (!hasVisibleFields) return;\n\n              onToggleExpand(id, isExpanded);\n            }}\n            className={getClassNameItem(\"summary\")}\n          >\n            <ItemSummary\n              index={index}\n              originalIndex={originalIndex}\n              field={field}\n              name={name}\n            />\n            <div className={getClassNameItem(\"rhs\")}>\n              {!readOnly && (\n                <div className={getClassNameItem(\"actions\")}>{actions}</div>\n              )}\n              <div>\n                <DragIcon />\n              </div>\n            </div>\n          </div>\n          <div className={getClassNameItem(\"body\")}>\n            {isExpanded && hasVisibleFields && (\n              <fieldset className={getClassNameItem(\"fieldset\")}>\n                {Object.keys(field.arrayFields!).map((subName) => {\n                  const subField = field.arrayFields![subName];\n\n                  return (\n                    <SubField\n                      key={`${id}_${subName}_${index}`} // Ensure to key on index, as ID may not update when reordering\n                      id={`${id}_${subName}`}\n                      name={name}\n                      index={index}\n                      subName={subName}\n                      localName={localName}\n                      field={subField}\n                      onChange={onChange}\n                      forceReadOnly={!canEdit}\n                    />\n                  );\n                })}\n              </fieldset>\n            )}\n          </div>\n        </div>\n      )}\n    </Sortable>\n  );\n};\n\nconst ArrayFieldItem = memo(ArrayFieldItemInternal);\n\nexport const ArrayField = ({\n  field,\n  onChange,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  readOnly,\n  Label = (props) => <div {...props} />,\n}: FieldPropsInternal<object[], ArrayFieldType>) => {\n  const setUi = useAppStore((s) => s.setUi);\n  const appStoreApi = useAppStoreApi();\n  const fieldStore = useFieldStoreApi();\n  const { localName = name } = useNestedFieldContext();\n\n  const getValue = () => getDeep(fieldStore.getState(), name) ?? [];\n\n  const getArrayState = useCallback(() => {\n    const { state } = appStoreApi.getState();\n\n    const thisState = state.ui.arrayState[id];\n\n    if (thisState?.items?.length) return thisState;\n\n    const value = getValue();\n\n    return {\n      items: Array.from(value || []).map((item, idx) => {\n        return {\n          _originalIndex: idx,\n          _currentIndex: idx,\n          _arrayId: `${id}-${idx}`,\n        };\n      }),\n      openId: \"\",\n    };\n  }, [appStoreApi, id, getValue, name]);\n\n  const numItems = useFieldStore(() => {\n    return getValue().length;\n  });\n\n  const defaultArrayState = useMemo(getArrayState, [getArrayState]);\n\n  const mirror = useAppStore((s) => {\n    const thisArrayState = s.state.ui.arrayState[id];\n\n    return thisArrayState ?? defaultArrayState;\n  });\n\n  const appStore = useAppStoreApi();\n\n  const mapArrayStateToUi = useCallback(\n    (partialArrayState: Partial<ArrayState>) => {\n      const state = appStore.getState().state;\n\n      return {\n        arrayState: {\n          ...state.ui.arrayState,\n          [id]: { ...getArrayState(), ...partialArrayState },\n        },\n      };\n    },\n    [appStore]\n  );\n\n  const getHighestIndex = useCallback(() => {\n    return getArrayState().items.reduce(\n      (acc, item) => (item._originalIndex > acc ? item._originalIndex : acc),\n      -1\n    );\n  }, []);\n\n  const regenerateArrayState = useCallback((value: object[]) => {\n    let highestIndex = getHighestIndex();\n\n    const arrayState = getArrayState();\n\n    const newItems = Array.from(value || []).map((item, idx) => {\n      const arrayStateItem = arrayState.items[idx];\n\n      const newItem = {\n        _originalIndex: arrayStateItem?._originalIndex ?? highestIndex + 1,\n        _currentIndex: arrayStateItem?._currentIndex ?? idx,\n        _arrayId:\n          arrayState.items[idx]?._arrayId || `${id}-${highestIndex + 1}`,\n      };\n\n      if (newItem._originalIndex > highestIndex) {\n        highestIndex = newItem._originalIndex;\n      }\n\n      return newItem;\n    });\n\n    // We don't need to record history during this useEffect, as the history has already been set by onDragEnd\n    return { ...arrayState, items: newItems };\n  }, []);\n\n  const [draggedItem, setDraggedItem] = useState(\"\");\n  const isDraggingAny = !!draggedItem;\n\n  const valueRef = useRef<object[]>([]);\n\n  useEffect(() => {\n    valueRef.current = getValue();\n  }, []);\n\n  /**\n   * Walk the item and ensure all slotted items have unique IDs\n   */\n  const uniqifyItem = useCallback(\n    (val: any) => {\n      if (field.type !== \"array\" || !field.arrayFields) return;\n\n      const config = appStore.getState().config;\n\n      return walkField({\n        value: val,\n        fields: field.arrayFields,\n        mappers: {\n          slot: ({ value }) => {\n            const content = value as Content;\n\n            return content.map((item) => populateIds(item, config, true));\n          },\n        },\n        config,\n      });\n    },\n    [appStore, field]\n  );\n\n  const syncCurrentIndexes = useCallback(() => {\n    const arrayState = getArrayState();\n\n    const newArrayStateItems = arrayState.items.map((item, index) => ({\n      ...item,\n      _currentIndex: index,\n    }));\n\n    const state = appStore.getState().state;\n\n    const newUi = {\n      arrayState: {\n        ...state.ui.arrayState,\n        [id]: { ...arrayState, items: newArrayStateItems },\n      },\n    };\n\n    setUi(newUi, false);\n  }, []);\n\n  const updateValue = useCallback(\n    (newValue: object[]) => {\n      const newArrayState = regenerateArrayState(newValue);\n\n      setUi(mapArrayStateToUi(newArrayState), false);\n      onChange(newValue);\n    },\n    [regenerateArrayState, setUi, mapArrayStateToUi, onChange]\n  );\n\n  // Reset array state if number of items changes\n  useEffect(() => {\n    const newArrayState = regenerateArrayState(getValue());\n    setUi(mapArrayStateToUi(newArrayState), false);\n  }, [numItems]);\n\n  if (field.type !== \"array\" || !field.arrayFields) {\n    return null;\n  }\n\n  const addDisabled =\n    (field.max !== undefined && mirror?.items.length >= field.max) || readOnly;\n\n  return (\n    <Label\n      label={label || name}\n      icon={labelIcon || <List size={16} />}\n      el=\"div\"\n      readOnly={readOnly}\n    >\n      <SortableProvider\n        onDragStart={(id) => {\n          valueRef.current = getValue();\n\n          setDraggedItem(id);\n\n          syncCurrentIndexes();\n        }}\n        onDragEnd={() => {\n          setDraggedItem(\"\");\n\n          onChange(valueRef.current);\n\n          // Write directly to fieldStore to prevent flicker\n          const currentFieldVal = fieldStore.getState();\n          fieldStore.setState(setDeep(currentFieldVal, name, valueRef.current));\n\n          syncCurrentIndexes();\n        }}\n        onMove={(move) => {\n          const arrayState = getArrayState();\n\n          // A race condition means we can sometimes have the wrong source element\n          // so we double double check before proceeding\n          if (arrayState.items[move.source]._arrayId !== draggedItem) {\n            return;\n          }\n\n          const newValue = reorder(valueRef.current, move.source, move.target);\n\n          const newArrayStateItems: ItemWithId[] = reorder(\n            arrayState.items,\n            move.source,\n            move.target\n          );\n\n          const state = appStore.getState().state;\n\n          const newUi = {\n            arrayState: {\n              ...state.ui.arrayState,\n              [id]: { ...arrayState, items: newArrayStateItems },\n            },\n          };\n\n          setUi(newUi, false);\n          valueRef.current = newValue;\n        }}\n      >\n        <div\n          className={getClassName({\n            hasItems: numItems > 0,\n            addDisabled,\n          })}\n        >\n          {mirror.items.length > 0 && (\n            <div className={getClassName(\"inner\")} data-dnd-container>\n              {mirror.items.map((item, index) => {\n                const {\n                  _arrayId = `${id}-${index}`,\n                  _originalIndex = index,\n                  _currentIndex = index,\n                } = item;\n\n                return (\n                  <ArrayFieldItem\n                    key={_arrayId}\n                    index={_currentIndex} // Get actual index for data\n                    dragIndex={index}\n                    originalIndex={_originalIndex}\n                    arrayId={id}\n                    id={_arrayId}\n                    readOnly={readOnly}\n                    field={field}\n                    name={name}\n                    localName={localName}\n                    onChange={(val, ui, subName) => {\n                      const value = getValue();\n\n                      const data: any = Array.from(value || [])[index] || {};\n\n                      onChange(\n                        replace(value, index, {\n                          ...data,\n                          [subName]: val,\n                        }),\n                        ui\n                      );\n                    }}\n                    onToggleExpand={(id, isExpanded) => {\n                      if (isExpanded) {\n                        setUi(\n                          mapArrayStateToUi({\n                            openId: \"\",\n                          })\n                        );\n                      } else {\n                        setUi(\n                          mapArrayStateToUi({\n                            openId: id,\n                          })\n                        );\n                      }\n                    }}\n                    actions={\n                      <>\n                        <div className={getClassNameItem(\"action\")}>\n                          <IconButton\n                            type=\"button\"\n                            disabled={!!addDisabled}\n                            onClick={(e) => {\n                              e.stopPropagation();\n\n                              const value = getValue();\n                              const existingValue = [...(value || [])];\n                              const newItem = uniqifyItem(existingValue[index]);\n\n                              existingValue.splice(index, 0, newItem);\n\n                              updateValue(existingValue);\n                            }}\n                            title=\"Duplicate\"\n                          >\n                            <Copy size={16} />\n                          </IconButton>\n                        </div>\n                        <div className={getClassNameItem(\"action\")}>\n                          <IconButton\n                            type=\"button\"\n                            disabled={\n                              field.min !== undefined &&\n                              field.min >= mirror.items.length\n                            }\n                            onClick={(e) => {\n                              e.stopPropagation();\n\n                              const value = getValue();\n                              const existingValue = [...(value || [])];\n\n                              existingValue.splice(index, 1);\n\n                              updateValue(existingValue);\n                            }}\n                            title=\"Delete\"\n                          >\n                            <Trash size={16} />\n                          </IconButton>\n                        </div>\n                      </>\n                    }\n                  />\n                );\n              })}\n            </div>\n          )}\n\n          {!addDisabled && (\n            <button\n              type=\"button\"\n              className={getClassName(\"addButton\")}\n              onClick={() => {\n                if (isDraggingAny) return;\n\n                const value = getValue();\n\n                const existingValue = value || [];\n\n                // Support defaultItemProps as a function so we can generate dynamic defaults based on the current length of the array\n                const defaultProps =\n                  typeof field.defaultItemProps === \"function\"\n                    ? field.defaultItemProps(existingValue.length)\n                    : field.defaultItemProps ?? {};\n\n                const newItem = defaultSlots(\n                  uniqifyItem(defaultProps),\n                  field.arrayFields\n                );\n                const newValue = [...existingValue, newItem];\n\n                updateValue(newValue);\n              }}\n            >\n              <Plus size={21} />\n            </button>\n          )}\n        </div>\n      </SortableProvider>\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/ArrayField/styles.module.css",
    "content": "/**\n * ArrayField\n */\n\n.ArrayField {\n  display: flex;\n  flex-direction: column;\n  background: var(--puck-color-azure-11);\n  border: 1px solid var(--puck-color-grey-09);\n  border-radius: 4px;\n}\n\n.ArrayField--isDraggingFrom {\n  background-color: var(--puck-color-azure-11);\n  overflow: hidden;\n}\n\n.ArrayField-addButton {\n  background-color: var(--puck-color-white);\n  border: none;\n  border-radius: 3px;\n  display: flex;\n  color: var(--puck-color-azure-05);\n  justify-content: center;\n  cursor: pointer;\n  width: 100%;\n  margin: 0;\n  padding: 14px; /* Retain same height as other items */\n  text-align: left;\n  transition: background-color 50ms ease-in;\n}\n\n.ArrayField--hasItems > .ArrayField-addButton {\n  border-top: 1px solid var(--puck-color-grey-09);\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n\n.ArrayField-addButton:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n  position: relative;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ArrayField:not(.ArrayField--isDraggingFrom) > .ArrayField-addButton:hover {\n    background: var(--puck-color-azure-12);\n    color: var(--puck-color-azure-04);\n    transition: none;\n  }\n}\n\n.ArrayField:not(.ArrayField--isDraggingFrom) > .ArrayField-addButton:active {\n  background: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-04);\n  transition: none;\n}\n\n.ArrayField-inner {\n  margin-top: -1px;\n}\n\n/**\n * ArrayFieldItem\n */\n\n.ArrayFieldItem {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n  display: block;\n  position: relative;\n}\n\n.ArrayFieldItem {\n  border-top: 1px solid var(--puck-color-grey-09);\n}\n\n.ArrayFieldItem--isDragging {\n  border-top: transparent;\n}\n\n.ArrayFieldItem--isExpanded::before {\n  display: none;\n}\n\n.ArrayFieldItem--isExpanded {\n  border-bottom: 0;\n  outline-offset: 0px !important; /* Important helps to override Nextra docs */\n  outline: 1px solid var(--puck-color-azure-07) !important; /* Important helps to override Nextra docs */\n  z-index: 2;\n}\n\n.ArrayFieldItem--isDragging {\n  outline: 2px var(--puck-color-azure-09) solid !important;\n}\n\n.ArrayFieldItem--isDragging .ArrayFieldItem-summary:active {\n  background-color: var(--puck-color-white);\n}\n\n.ArrayFieldItem + .ArrayFieldItem {\n  border-top-left-radius: 0;\n  border-top-right-radius: 0;\n}\n\n.ArrayFieldItem-summary {\n  background: var(--puck-color-white);\n  color: var(--puck-color-grey-04);\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  gap: 2px;\n  justify-content: space-between;\n  font-size: var(--puck-font-size-xxs);\n  list-style: none;\n  padding: 12px 15px;\n  position: relative;\n  overflow: hidden;\n  transition: background-color 50ms ease-in;\n}\n\n.ArrayFieldItem--noFields > .ArrayFieldItem-summary {\n  cursor: grab;\n}\n\n.ArrayFieldItem:first-of-type > .ArrayFieldItem-summary {\n  border-top-left-radius: 3px;\n  border-top-right-radius: 3px;\n}\n\n.ArrayField--addDisabled\n  > .ArrayField-inner\n  > .ArrayFieldItem:last-of-type:not(.ArrayFieldItem--isExpanded)\n  > .ArrayFieldItem-summary {\n  border-bottom-left-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n\n.ArrayField--addDisabled\n  > .ArrayField-inner\n  > .ArrayFieldItem--isExpanded:last-of-type {\n  border-bottom-left-radius: 3px;\n  border-bottom-right-radius: 3px;\n}\n\n.ArrayFieldItem-summary:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ArrayFieldItem-summary:hover {\n    background-color: var(--puck-color-azure-12);\n    transition: none;\n  }\n}\n\n.ArrayFieldItem-summary:active {\n  background-color: var(--puck-color-azure-11);\n  transition: none;\n}\n\n.ArrayFieldItem--isExpanded > .ArrayFieldItem-summary {\n  background: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-04);\n  font-weight: 600;\n  transition: none;\n}\n\n.ArrayFieldItem-body {\n  background: var(--puck-color-white);\n  display: none;\n}\n\n.ArrayFieldItem--isExpanded > .ArrayFieldItem-body {\n  display: block;\n}\n\n.ArrayFieldItem-fieldset {\n  border: none;\n  border-top: 1px solid var(--puck-color-grey-09);\n  margin: 0;\n  min-width: 0; /* Needed to ensure External input doesn't overflow, see https://stackoverflow.com/a/53784508 */\n  padding: 16px 15px;\n}\n\n.ArrayFieldItem-rhs {\n  display: flex;\n  gap: 4px;\n  align-items: center;\n}\n\n.ArrayFieldItem-actions {\n  color: var(--puck-color-grey-04);\n  display: flex;\n  gap: 4px;\n  opacity: 0;\n}\n\n.ArrayFieldItem-summary:focus-within\n  > .ArrayFieldItem-rhs\n  > .ArrayFieldItem-actions,\n.ArrayFieldItem-summary:hover > .ArrayFieldItem-rhs > .ArrayFieldItem-actions {\n  opacity: 1;\n}\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/DefaultField/index.tsx",
    "content": "import getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"../../styles.module.css\";\nimport { Hash, Type } from \"lucide-react\";\nimport { FieldPropsInternal } from \"../..\";\n\nimport { useLocalValue } from \"../../lib/use-local-value\";\n\nconst getClassName = getClassNameFactory(\"Input\", styles);\n\nexport const DefaultField = ({\n  field,\n  onChange,\n  readOnly,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  Label,\n}: FieldPropsInternal) => {\n  const [localValue, onChangeLocal] = useLocalValue(name, onChange);\n\n  return (\n    <Label\n      label={label || name}\n      icon={\n        labelIcon || (\n          <>\n            {field.type === \"text\" && <Type size={16} />}\n            {field.type === \"number\" && <Hash size={16} />}\n          </>\n        )\n      }\n      readOnly={readOnly}\n    >\n      <input\n        className={getClassName(\"input\")}\n        autoComplete=\"off\"\n        type={field.type}\n        title={label || name}\n        name={name}\n        value={localValue}\n        onChange={(e) => {\n          if (field.type === \"number\") {\n            const numberValue = Number(e.currentTarget.value);\n\n            if (typeof field.min !== \"undefined\" && numberValue < field.min) {\n              return;\n            }\n\n            if (typeof field.max !== \"undefined\" && numberValue > field.max) {\n              return;\n            }\n\n            onChangeLocal(numberValue);\n          } else {\n            onChangeLocal(e.currentTarget.value);\n          }\n        }}\n        readOnly={readOnly}\n        tabIndex={readOnly ? -1 : undefined}\n        id={id}\n        min={field.type === \"number\" ? field.min : undefined}\n        max={field.type === \"number\" ? field.max : undefined}\n        placeholder={\n          field.type === \"text\" || field.type === \"number\"\n            ? field.placeholder\n            : undefined\n        }\n        step={field.type === \"number\" ? field.step : undefined}\n      />\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/ExternalField/index.tsx",
    "content": "import { useEffect } from \"react\";\nimport type { FieldPropsInternal } from \"../..\";\nimport type {\n  ExternalField as ExternalFieldType,\n  ExternalFieldWithAdaptor,\n} from \"../../../../types\";\nimport { ExternalInput } from \"../../../ExternalInput\";\nimport { Link } from \"lucide-react\";\nimport { useDeepField } from \"../../lib/use-deep-field\";\n\nexport const ExternalField = ({\n  field,\n  onChange,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  Label,\n  readOnly,\n}: FieldPropsInternal) => {\n  const value = useDeepField(name);\n\n  // DEPRECATED\n  const validField = field as ExternalFieldType;\n  const deprecatedField = field as ExternalFieldWithAdaptor;\n\n  useEffect(() => {\n    if (deprecatedField.adaptor) {\n      console.error(\n        \"Warning: The `adaptor` API is deprecated. Please use updated APIs on the `external` field instead. This will be a breaking change in a future release.\"\n      );\n    }\n  }, []);\n\n  if (field.type !== \"external\") {\n    return null;\n  }\n\n  return (\n    <Label\n      label={label || name}\n      icon={labelIcon || <Link size={16} />}\n      el=\"div\"\n    >\n      <ExternalInput\n        name={name}\n        field={{\n          ...validField,\n          // DEPRECATED\n\n          placeholder: deprecatedField.adaptor?.name\n            ? `Select from ${deprecatedField.adaptor.name}`\n            : validField.placeholder || \"Select data\",\n          mapProp: deprecatedField.adaptor?.mapProp || validField.mapProp,\n          mapRow: validField.mapRow,\n          fetchList: deprecatedField.adaptor?.fetchList\n            ? async () =>\n                await deprecatedField.adaptor.fetchList(\n                  deprecatedField.adaptorParams\n                )\n            : validField.fetchList,\n        }}\n        onChange={onChange}\n        value={value}\n        id={id}\n        readOnly={readOnly}\n      />\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/ObjectField/index.tsx",
    "content": "import getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\nimport { MoreVertical } from \"lucide-react\";\nimport { FieldPropsInternal } from \"../..\";\nimport { useNestedFieldContext } from \"../../context\";\nimport { useAppStore } from \"../../../../store\";\nimport { getDeep } from \"../../../../lib/data/get-deep\";\nimport { SubField } from \"../../subfield\";\nimport { useFieldStoreApi } from \"../../store\";\n\nconst getClassName = getClassNameFactory(\"ObjectField\", styles);\n\nexport const ObjectField = ({\n  field,\n  onChange,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  Label,\n  readOnly,\n}: FieldPropsInternal) => {\n  const { localName = name } = useNestedFieldContext();\n\n  const fieldStore = useFieldStoreApi();\n\n  const canEdit = useAppStore(\n    (s) => s.permissions.getPermissions({ item: s.selectedItem }).edit\n  );\n\n  const getValue = () => getDeep(fieldStore.getState(), name) ?? {};\n\n  if (field.type !== \"object\" || !field.objectFields) {\n    return null;\n  }\n\n  return (\n    <Label\n      label={label || name}\n      icon={labelIcon || <MoreVertical size={16} />}\n      el=\"div\"\n      readOnly={readOnly}\n    >\n      <div className={getClassName()}>\n        <fieldset className={getClassName(\"fieldset\")}>\n          {Object.keys(field.objectFields!).map((subName) => {\n            const subField = field.objectFields![subName];\n            const subPath = `${localName}.${subName}`;\n\n            return (\n              <SubField\n                key={subPath}\n                id={`${id}_${subName}`}\n                name={name}\n                subName={subName}\n                localName={localName}\n                field={subField}\n                forceReadOnly={!canEdit}\n                onChange={(subValue, ui, subName) => {\n                  const value = getValue();\n\n                  // Skip onChange if value hasn't changed\n                  if (value[subName] === subValue) {\n                    return;\n                  }\n\n                  onChange({ ...value, [subName]: subValue }, ui);\n                }}\n              />\n            );\n          })}\n        </fieldset>\n      </div>\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/ObjectField/styles.module.css",
    "content": "/**\n * ObjectField\n */\n\n.ObjectField {\n  display: flex;\n  flex-direction: column;\n  background-color: var(--puck-color-white);\n  border: 1px solid var(--puck-color-grey-09);\n  border-radius: 4px;\n}\n\n.ObjectField-fieldset {\n  border: none;\n  margin: 0;\n  min-width: 0; /* Needed to ensure External input doesn't overflow, see https://stackoverflow.com/a/53784508 */\n  padding: 16px 15px;\n}\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/RadioField/index.tsx",
    "content": "import getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"../../styles.module.css\";\nimport { CheckCircle } from \"lucide-react\";\nimport { FieldPropsInternal } from \"../..\";\nimport { useDeepField } from \"../../lib/use-deep-field\";\n\nconst getClassName = getClassNameFactory(\"Input\", styles);\n\nexport const RadioField = ({\n  field,\n  onChange,\n  readOnly,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  Label,\n}: FieldPropsInternal) => {\n  const value = useDeepField(name);\n\n  if (field.type !== \"radio\" || !field.options) {\n    return null;\n  }\n\n  return (\n    <Label\n      icon={labelIcon || <CheckCircle size={16} />}\n      label={label || name}\n      readOnly={readOnly}\n      el=\"div\"\n    >\n      <div className={getClassName(\"radioGroupItems\")} id={id}>\n        {field.options.map((option) => (\n          <label\n            key={option.label + option.value}\n            className={getClassName(\"radio\")}\n          >\n            <input\n              type=\"radio\"\n              className={getClassName(\"radioInput\")}\n              value={JSON.stringify({ value: option.value })}\n              name={name}\n              onChange={(e) => {\n                onChange(JSON.parse(e.target.value).value);\n              }}\n              disabled={readOnly}\n              checked={value === option.value}\n            />\n            <div className={getClassName(\"radioInner\")}>\n              {option.label || option.value?.toString()}\n            </div>\n          </label>\n        ))}\n      </div>\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/RichtextField/index.tsx",
    "content": "import { lazy, Suspense } from \"react\";\nimport { Type } from \"lucide-react\";\nimport { FieldPropsInternal } from \"../..\";\nimport { RichtextField as RichtextFieldType } from \"../../../../types\";\nimport { EditorFallback } from \"../../../RichTextEditor/components/EditorFallback\";\nimport { useDeepField } from \"../../lib/use-deep-field\";\n\nconst Editor = lazy(() =>\n  import(\"../../../RichTextEditor/components/Editor\").then((m) => ({\n    default: m.Editor,\n  }))\n);\n\nexport const RichtextField = ({\n  onChange,\n  readOnly = false,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  Label,\n  field,\n}: FieldPropsInternal) => {\n  const content = useDeepField(name);\n\n  const editorProps = {\n    onChange: onChange,\n    content,\n    readOnly: readOnly,\n    field: field as RichtextFieldType,\n    id: id,\n    name: name,\n  };\n\n  return (\n    <>\n      <Label\n        label={label || name}\n        icon={labelIcon || <Type size={16} />}\n        readOnly={readOnly}\n        el=\"div\"\n      >\n        <Suspense fallback={<EditorFallback {...editorProps} />}>\n          <Editor {...editorProps} />\n        </Suspense>\n      </Label>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/SelectField/index.tsx",
    "content": "import getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"../../styles.module.css\";\nimport { ChevronDown } from \"lucide-react\";\nimport { FieldPropsInternal } from \"../..\";\nimport { useDeepField } from \"../../lib/use-deep-field\";\n\nconst getClassName = getClassNameFactory(\"Input\", styles);\n\nexport const SelectField = ({\n  field,\n  onChange,\n  label,\n  labelIcon,\n  Label,\n  id,\n  name = id,\n  readOnly,\n}: FieldPropsInternal) => {\n  const value = useDeepField(name);\n\n  if (field.type !== \"select\" || !field.options) {\n    return null;\n  }\n\n  return (\n    <Label\n      label={label || name}\n      icon={labelIcon || <ChevronDown size={16} />}\n      readOnly={readOnly}\n    >\n      <select\n        id={id}\n        title={label || name}\n        className={getClassName(\"input\")}\n        disabled={readOnly}\n        onChange={(e) => {\n          onChange(JSON.parse(e.target.value).value);\n        }}\n        value={JSON.stringify({ value })}\n      >\n        {field.options.map((option) => (\n          <option\n            key={option.label + JSON.stringify(option.value)}\n            label={option.label}\n            value={JSON.stringify({ value: option.value })}\n          />\n        ))}\n      </select>\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/TextareaField/index.tsx",
    "content": "import getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"../../styles.module.css\";\nimport { Type } from \"lucide-react\";\nimport { FieldPropsInternal } from \"../..\";\nimport { useLocalValue } from \"../../lib/use-local-value\";\n\nconst getClassName = getClassNameFactory(\"Input\", styles);\n\nexport const TextareaField = ({\n  field,\n  onChange,\n  readOnly,\n  id,\n  name = id,\n  label,\n  labelIcon,\n  Label,\n}: FieldPropsInternal) => {\n  const [localValue, onChangeLocal] = useLocalValue(name, onChange);\n\n  return (\n    <Label\n      label={label || name}\n      icon={labelIcon || <Type size={16} />}\n      readOnly={readOnly}\n    >\n      <textarea\n        id={id}\n        className={getClassName(\"input\")}\n        autoComplete=\"off\"\n        name={name}\n        value={typeof localValue === \"undefined\" ? \"\" : localValue}\n        onChange={(e) => onChangeLocal(e.currentTarget.value)}\n        readOnly={readOnly}\n        tabIndex={readOnly ? -1 : undefined}\n        rows={5}\n        placeholder={field.type === \"textarea\" ? field.placeholder : undefined}\n      />\n    </Label>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/fields/index.tsx",
    "content": "export * from \"./ArrayField\";\nexport * from \"./DefaultField\";\nexport * from \"./ExternalField\";\nexport * from \"./RadioField\";\nexport * from \"./SelectField\";\nexport * from \"./TextareaField\";\nexport * from \"./RichtextField\";\n"
  },
  {
    "path": "packages/core/components/AutoField/index.tsx",
    "content": "import getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { Field, FieldProps } from \"../../types\";\n\nimport styles from \"./styles.module.css\";\nimport {\n  ReactElement,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from \"react\";\nimport {\n  RadioField,\n  SelectField,\n  ExternalField,\n  ArrayField,\n  DefaultField,\n  TextareaField,\n  RichtextField,\n} from \"./fields\";\nimport { ObjectField } from \"./fields/ObjectField\";\nimport { useAppStore } from \"../../store\";\nimport { useSafeId } from \"../../lib/use-safe-id\";\nimport { NestedFieldContext } from \"./context\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { getDeep } from \"../../lib/data/get-deep\";\nimport type {\n  FieldLabelPropsInternal,\n  FieldPropsInternalOptional,\n} from \"./FieldLabel\";\nimport { FieldLabelInternal } from \"./FieldLabel\";\nimport { useFieldStore, useFieldStoreApi, fieldContextStore } from \"./store\";\n\nconst getClassName = getClassNameFactory(\"Input\", styles);\nconst getClassNameWrapper = getClassNameFactory(\"InputWrapper\", styles);\n\nexport type FieldPropsInternal<ValueType = any, F = Field<any>> = FieldProps<\n  F,\n  ValueType\n> & {\n  Label: React.FC<FieldLabelPropsInternal>;\n  label?: string;\n  labelIcon?: ReactNode;\n  id: string;\n  name?: string;\n};\n\nexport { FieldLabel } from \"./FieldLabel\";\n\nconst defaultFields = {\n  array: ArrayField,\n  external: ExternalField,\n  object: ObjectField,\n  select: SelectField,\n  textarea: TextareaField,\n  radio: RadioField,\n  text: DefaultField,\n  number: DefaultField,\n  richtext: RichtextField,\n};\n\nfunction AutoFieldInternal<\n  ValueType = any,\n  FieldType extends FieldNoLabel<ValueType> = FieldNoLabel<ValueType>\n>(\n  props: FieldPropsInternalOptional<ValueType, FieldType> & {\n    Label?: React.FC<FieldLabelPropsInternal>;\n  }\n) {\n  const dispatch = useAppStore((s) => s.dispatch);\n  const overrides = useAppStore((s) => s.overrides);\n  const readOnly = useAppStore(useShallow((s) => s.selectedItem?.readOnly));\n  const nestedFieldContext = useContext(NestedFieldContext);\n\n  const { id, Label = FieldLabelInternal } = props;\n\n  const field = props.field as Field<ValueType>;\n  const label = field.label;\n  const labelIcon = field.labelIcon;\n\n  const defaultId = useSafeId();\n  const resolvedId = id || defaultId;\n\n  const render = useMemo(\n    () => ({\n      ...overrides.fieldTypes,\n      custom: overrides.fieldTypes?.custom,\n      array: overrides.fieldTypes?.array || defaultFields.array,\n      external: overrides.fieldTypes?.external || defaultFields.external,\n      object: overrides.fieldTypes?.object || defaultFields.object,\n      select: overrides.fieldTypes?.select || defaultFields.select,\n      textarea: overrides.fieldTypes?.textarea || defaultFields.textarea,\n      radio: overrides.fieldTypes?.radio || defaultFields.radio,\n      text: overrides.fieldTypes?.text || defaultFields.text,\n      number: overrides.fieldTypes?.number || defaultFields.number,\n      richtext: overrides.fieldTypes?.richtext || defaultFields.richtext,\n    }),\n    [overrides]\n  );\n\n  const fieldValue = useFieldStore((s) => {\n    // Always set a value for custom fields, or when an override is provided\n    if (field.type === \"custom\" || overrides.fieldTypes?.[field.type]) {\n      return getDeep(s, props.name ?? resolvedId);\n    }\n  });\n\n  const mergedProps = useMemo(\n    () => ({\n      ...props,\n      field,\n      label,\n      labelIcon,\n      Label,\n      id: resolvedId,\n      value: fieldValue,\n    }),\n    [props, field, label, labelIcon, Label, resolvedId, fieldValue]\n  );\n\n  const onFocus = useCallback(\n    (e: React.FocusEvent) => {\n      if (\n        mergedProps.name &&\n        (e.target.nodeName === \"INPUT\" || e.target.nodeName === \"TEXTAREA\")\n      ) {\n        e.stopPropagation();\n\n        dispatch({\n          type: \"setUi\",\n          ui: {\n            field: { focus: mergedProps.name },\n          },\n        });\n      }\n    },\n    [mergedProps.name]\n  );\n\n  const onBlur = useCallback((e: React.FocusEvent) => {\n    if (\"name\" in e.target) {\n      dispatch({\n        type: \"setUi\",\n        ui: {\n          field: { focus: null },\n        },\n      });\n    }\n  }, []);\n\n  let Children = useMemo(() => {\n    if (field.type !== \"custom\" && field.type !== \"slot\") {\n      return defaultFields[field.type];\n    }\n\n    return (_props: any) => null;\n  }, [field.type]);\n\n  const fieldKey = field.type === \"custom\" ? field.key : undefined;\n\n  let FieldComponent: React.ComponentType<any> = useMemo(() => {\n    // if there's an override provided for custom fields, fallback to standard behavior\n    if (field.type === \"custom\" && !render[field.type]) {\n      if (!field.render) {\n        return null;\n      }\n      return field.render as any;\n    } else if (field.type !== \"slot\") {\n      return render[field.type] as (props: FieldProps) => ReactElement;\n    }\n  }, [field.type, fieldKey, render]);\n\n  const { visible = true } = props.field;\n\n  if (!visible) {\n    return null;\n  }\n\n  if (field.type === \"slot\") {\n    return null;\n  }\n\n  if (!FieldComponent) {\n    throw new Error(`Field type for ${field.type} did not exist.`);\n  }\n\n  return (\n    <NestedFieldContext.Provider\n      value={{\n        readOnlyFields: nestedFieldContext.readOnlyFields || readOnly || {},\n        localName: nestedFieldContext.localName ?? mergedProps.name,\n      }}\n    >\n      <div\n        className={getClassNameWrapper()}\n        onFocus={onFocus}\n        onBlur={onBlur}\n        onClick={(e) => {\n          // Prevent propagation of any click events to parent field.\n          // For example, a field within an array may bubble an event\n          // and fail to stop propagation.\n          e.stopPropagation();\n        }}\n      >\n        <FieldComponent {...mergedProps}>\n          <Children {...(mergedProps as any)} />\n        </FieldComponent>\n      </div>\n    </NestedFieldContext.Provider>\n  );\n}\n\ntype FieldNoLabel<Props extends any = any> = Omit<Field<Props>, \"label\">;\n\nexport function AutoFieldPrivate<\n  ValueType = any,\n  FieldType extends FieldNoLabel<ValueType> = FieldNoLabel<ValueType>\n>(\n  props: Omit<FieldPropsInternalOptional<ValueType, FieldType>, \"value\"> & {\n    Label?: React.FC<FieldLabelPropsInternal>;\n    value?: any;\n  }\n) {\n  return <AutoFieldInternal<ValueType, FieldType> {...props} />;\n}\n\nfunction AutoFieldPublicInternal<\n  ValueType = any,\n  FieldType extends FieldNoLabel<ValueType> = FieldNoLabel<ValueType>\n>({ value, ...props }: FieldProps<FieldType, ValueType> & { value: any }) {\n  const DefaultLabel = useMemo(() => {\n    const DefaultLabel = (labelProps: any) => (\n      <div\n        {...labelProps}\n        className={getClassName({ readOnly: props.readOnly })}\n      />\n    );\n\n    return DefaultLabel;\n  }, [props.readOnly]);\n\n  const fieldStore = useFieldStoreApi();\n\n  const onChange = useCallback(\n    (value: any) => {\n      if (!props.id) return;\n\n      fieldStore.setState({ [props.id]: value });\n\n      props.onChange(value);\n    },\n    [fieldStore, props.onChange, props.id]\n  );\n\n  useEffect(() => {\n    if (!props.id) return;\n\n    fieldStore.setState({ [props.id]: value });\n  }, [props.id, value, fieldStore]);\n\n  return (\n    <AutoFieldInternal<ValueType, FieldType>\n      {...props}\n      onChange={onChange}\n      Label={DefaultLabel}\n    />\n  );\n}\n\nexport function AutoField<\n  ValueType = any,\n  FieldType extends FieldNoLabel<ValueType> = FieldNoLabel<ValueType>\n>(props: FieldProps<FieldType, ValueType> & { value: any }) {\n  const id = useSafeId();\n\n  if (props.field.type === \"slot\") {\n    return null;\n  }\n\n  return (\n    <fieldContextStore.Provider value={{ [id]: props.value }}>\n      <AutoFieldPublicInternal<ValueType, FieldType> {...props} id={id} />\n    </fieldContextStore.Provider>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/AutoField/lib/use-deep-field.ts",
    "content": "import { getDeep } from \"../../../lib/data/get-deep\";\nimport { useFieldStore } from \"../store\";\n\nexport const useDeepField = (path: string) => {\n  return useFieldStore((s) => getDeep(s, path));\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/lib/use-is-focused.ts",
    "content": "import { useAppStore } from \"../../../store\";\n\nexport const useIsFocused = (path: string) => {\n  return useAppStore((s) => s.state.ui.field.focus === path);\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/lib/use-local-value.ts",
    "content": "import { useCallback, useEffect, useState } from \"react\";\nimport { useDeepField } from \"./use-deep-field\";\nimport { useIsFocused } from \"./use-is-focused\";\n\nexport const useLocalValue = (path: string, onChange: (val: any) => void) => {\n  const value = useDeepField(path);\n  const isFocused = useIsFocused(path);\n\n  const [localValue, setLocalValue] = useState(value?.toString());\n\n  const onChangeLocal = useCallback((val: any) => {\n    setLocalValue(val);\n    onChange(val);\n  }, []);\n\n  useEffect(() => {\n    // Prevent global state from setting local state if this field is focused\n    if (!isFocused) {\n      setLocalValue(value);\n    }\n  }, [isFocused, value]);\n\n  return [localValue ?? \"\", onChangeLocal];\n};\n"
  },
  {
    "path": "packages/core/components/AutoField/store.ts",
    "content": "import { ReactNode, useContext } from \"react\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { createContextStore } from \"../../lib/use-context-store\";\nimport { ExtractState, StoreApi, useStore } from \"zustand\";\n\ntype FieldStore = Record<string, any>;\n\nexport const fieldContextStore = createContextStore<FieldStore>({});\n\nexport const useFieldStoreApi = () => useContext(fieldContextStore.ctx);\n\nexport function useFieldStore<U>(\n  selector: (s: ExtractState<StoreApi<FieldStore>>) => U\n): U {\n  const store = useContext(fieldContextStore.ctx);\n\n  if (!store) {\n    throw new Error(\"useContextStore must be used inside context\");\n  }\n\n  return useStore<typeof store, U>(store, useShallow(selector));\n}\n"
  },
  {
    "path": "packages/core/components/AutoField/styles.module.css",
    "content": ".InputWrapper + .InputWrapper {\n  margin-top: 12px;\n}\n\n.Input-label {\n  align-items: center;\n  color: var(--puck-color-grey-04);\n  display: flex;\n  padding-bottom: 12px;\n  font-size: var(--puck-font-size-xxs);\n  font-weight: 600;\n}\n\n.Input-labelIcon {\n  color: var(--puck-color-grey-07);\n  display: flex;\n  margin-inline-end: 4px;\n  padding-inline-start: 4px;\n}\n\n.Input-disabledIcon {\n  color: var(--puck-color-grey-05);\n  margin-inline-start: auto;\n}\n\n.Input-input {\n  background: var(--puck-color-white);\n  border-width: 1px;\n  border-style: solid;\n  border-color: var(--puck-color-grey-09);\n  border-radius: 4px;\n  box-sizing: border-box;\n  font-family: inherit;\n  font-size: 16px;\n  padding: 12px 15px;\n  transition: border-color 50ms ease-in;\n  width: 100%;\n  max-width: 100%;\n}\n\n@media (min-width: 458px) {\n  .Input-input {\n    font-size: 14px;\n  }\n}\n\nselect.Input-input {\n  appearance: none; /* Safari */\n  background: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23c3c3c3'><polygon points='0,0 100,0 50,50'/></svg>\")\n    no-repeat;\n  background-size: 12px;\n  background-position: calc(100% - 12px) calc(50% + 3px);\n  background-repeat: no-repeat;\n  background-color: var(--puck-color-white);\n  cursor: pointer;\n}\n\nselect.Input-input:dir(rtl) {\n  background-position: 12px calc(50% + 3px);\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Input:has(> input):hover .Input-input:not([readonly]),\n  .Input:has(> textarea):hover .Input-input:not([readonly]) {\n    border-color: var(--puck-color-grey-05);\n    transition: none;\n  }\n  .Input:has(> select):hover .Input-input:not([disabled]) {\n    background-color: var(--puck-color-azure-12);\n    background-image: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%235a5a5a'><polygon points='0,0 100,0 50,50'/></svg>\");\n    border-color: var(--puck-color-grey-05);\n    transition: none;\n  }\n}\n\n.Input-input:focus {\n  border-color: var(--puck-color-grey-05);\n  outline: 2px solid var(--puck-color-azure-05);\n  transition: none;\n}\n\n.Input--readOnly > .Input-input,\n.Input--readOnly > select.Input-input {\n  background-color: var(--puck-color-grey-11);\n  border-color: var(--puck-color-grey-09);\n  color: var(--puck-color-grey-04);\n  cursor: default;\n  opacity: 1;\n  outline: 0;\n  transition: none;\n}\n\n.Input-radioGroupItems {\n  display: flex;\n  border: 1px solid var(--puck-color-grey-09);\n  border-radius: 4px;\n  flex-wrap: wrap;\n}\n\n.Input-radio {\n  border-inline-end: 1px solid var(--puck-color-grey-09);\n  flex-grow: 1;\n}\n\n.Input-radio:first-of-type {\n  border-bottom-left-radius: 4px;\n  border-top-left-radius: 4px;\n}\n\n.Input-radio:first-of-type .Input-radioInner {\n  border-bottom-left-radius: 3px;\n  border-top-left-radius: 3px;\n}\n\n.Input-radio:last-of-type {\n  border-bottom-right-radius: 4px;\n  border-inline-end: 0;\n  border-top-right-radius: 4px;\n}\n\n.Input-radio:last-of-type .Input-radioInner {\n  border-bottom-right-radius: 3px;\n  border-top-right-radius: 3px;\n}\n\n.Input-radioInner {\n  background-color: var(--puck-color-white);\n  color: var(--puck-color-grey-04);\n  cursor: pointer;\n  font-size: var(--puck-font-size-xxxs);\n  padding: 8px 12px;\n  text-align: center;\n  transition: background-color 50ms ease-in;\n}\n\n.Input-radio:has(:focus-visible) {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n  position: relative;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Input-radioInner:hover {\n    background-color: var(--puck-color-azure-12);\n    transition: none;\n  }\n}\n\n.Input--readOnly .Input-radioInner {\n  background-color: var(--puck-color-white);\n  color: var(--puck-color-grey-04);\n  cursor: default;\n}\n\n.Input-radio .Input-radioInput:checked ~ .Input-radioInner {\n  background-color: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-04);\n  font-weight: 500;\n}\n\n.Input--readOnly .Input-radioInput:checked ~ .Input-radioInner {\n  background-color: var(--puck-color-grey-11);\n  color: var(--puck-color-grey-04);\n}\n\n.Input-radio .Input-radioInput {\n  clip: rect(0 0 0 0);\n  clip-path: inset(100%);\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  width: 1px;\n}\n\ntextarea.Input-input {\n  margin-bottom: -4px; /* Remove strange bottom border */\n}\n"
  },
  {
    "path": "packages/core/components/AutoField/subfield.tsx",
    "content": "import { memo } from \"react\";\nimport { AutoFieldPrivate } from \".\";\nimport { NestedFieldProvider, useNestedFieldContext } from \"./context\";\nimport { Field } from \"../../types\";\n\nconst SubFieldInternal = ({\n  field,\n  id,\n  index,\n  name,\n  subName,\n  localName,\n  onChange,\n  forceReadOnly,\n}: {\n  id: string;\n  index?: number;\n  field: Field;\n  subName: string;\n  name?: string;\n  localName?: string;\n  onChange: (val: any, ui: any, subName: string) => void;\n  forceReadOnly: boolean;\n}) => {\n  const indexName = typeof index !== \"undefined\" ? `${name}[${index}]` : name;\n  const subPath = name ? `${indexName}.${subName}` : subName;\n  const localIndexName =\n    typeof index !== \"undefined\"\n      ? `${localName}[${index}]`\n      : localName ?? subName;\n  const localWildcardName =\n    typeof index !== \"undefined\" ? `${localName}[*]` : localName;\n  const localSubPath = `${localIndexName}.${subName}`;\n  const localWildcardSubPath = `${localWildcardName}.${subName}`;\n\n  const { readOnlyFields } = useNestedFieldContext();\n\n  const subReadOnly = forceReadOnly\n    ? forceReadOnly\n    : typeof readOnlyFields[subPath] !== \"undefined\"\n    ? readOnlyFields[localSubPath]\n    : readOnlyFields[localWildcardSubPath];\n\n  const label = field.label || subName;\n\n  return (\n    <NestedFieldProvider\n      name={localIndexName}\n      wildcardName={localWildcardName}\n      subName={subName}\n      readOnlyFields={readOnlyFields}\n    >\n      <AutoFieldPrivate\n        name={subPath}\n        label={label}\n        id={id}\n        readOnly={subReadOnly}\n        field={{\n          ...field,\n          label, // May be used by custom fields\n        }}\n        onChange={(val, ui) => {\n          onChange(val, ui, subName);\n        }}\n      />\n    </NestedFieldProvider>\n  );\n};\n\nexport const SubField = memo(SubFieldInternal);\n"
  },
  {
    "path": "packages/core/components/AutoFrame/index.tsx",
    "content": "import {\n  createContext,\n  ReactNode,\n  RefObject,\n  useContext,\n  useEffect,\n  useState,\n} from \"react\";\nimport hash from \"object-hash\";\nimport { createPortal } from \"react-dom\";\n\nconst styleSelector = 'style, link[rel=\"stylesheet\"]';\n\nconst collectStyles = (doc: Document) => {\n  const collected: HTMLElement[] = [];\n\n  doc.querySelectorAll(styleSelector).forEach((style) => {\n    if (style.tagName === \"STYLE\") {\n      const hasContent = !!style.innerHTML.trim();\n\n      if (hasContent) {\n        collected.push(style as HTMLElement);\n      }\n    } else {\n      collected.push(style as HTMLElement);\n    }\n  });\n\n  return collected;\n};\n\nconst getStyleSheet = (el: HTMLElement) => {\n  return Array.from(document.styleSheets).find((ss) => {\n    const ownerNode = ss.ownerNode as HTMLLinkElement;\n\n    return ownerNode.href === (el as HTMLLinkElement).href;\n  });\n};\n\nconst getStyles = (styleSheet?: CSSStyleSheet) => {\n  if (styleSheet) {\n    try {\n      return Array.from(styleSheet.cssRules)\n        .map((rule) => rule.cssText)\n        .join(\"\");\n    } catch (e) {\n      console.warn(\n        \"Access to stylesheet %s is denied. Ignoring…\",\n        styleSheet.href\n      );\n    }\n  }\n\n  return \"\";\n};\n\n// Sync attributes from parent window to iFrame\nconst syncAttributes = (sourceElement: Element, targetElement: Element) => {\n  const attributes = sourceElement.attributes;\n  if (attributes?.length > 0) {\n    Array.from(attributes).forEach((attribute: Attr) => {\n      targetElement.setAttribute(attribute.name, attribute.value);\n    });\n  }\n};\n\nconst defer = (fn: () => void) => setTimeout(fn, 0);\n\nconst CopyHostStyles = ({\n  children,\n  debug = false,\n  onStylesLoaded = () => null,\n}: {\n  children: ReactNode;\n  debug?: boolean;\n  onStylesLoaded?: () => void;\n}) => {\n  const { document: doc, window: win } = useFrame();\n\n  useEffect(() => {\n    if (!win || !doc) {\n      return () => {};\n    }\n\n    let elements: { original: HTMLElement; mirror: HTMLElement }[] = [];\n    const hashes: Record<string, boolean> = {};\n\n    const lookupEl = (el: HTMLElement) =>\n      elements.findIndex((elementMap) => elementMap.original === el);\n\n    const mirrorEl = async (el: HTMLElement, inlineStyles = false) => {\n      let mirror: HTMLStyleElement;\n\n      if (el.nodeName === \"LINK\" && inlineStyles) {\n        mirror = document.createElement(\"style\") as HTMLStyleElement;\n        mirror.type = \"text/css\";\n\n        let styleSheet = getStyleSheet(el);\n\n        if (!styleSheet) {\n          await new Promise<void>((resolve) => {\n            const fn = () => {\n              resolve();\n              el.removeEventListener(\"load\", fn);\n            };\n\n            el.addEventListener(\"load\", fn);\n          });\n          styleSheet = getStyleSheet(el);\n        }\n\n        const styles = getStyles(styleSheet);\n\n        if (!styles) {\n          if (debug) {\n            console.warn(\n              `Tried to load styles for link element, but couldn't find them. Skipping...`\n            );\n          }\n\n          return;\n        }\n\n        mirror.innerHTML = styles;\n\n        mirror.setAttribute(\"data-href\", el.getAttribute(\"href\")!);\n      } else {\n        mirror = el.cloneNode(true) as HTMLStyleElement;\n      }\n\n      return mirror;\n    };\n\n    const addEl = async (el: HTMLElement) => {\n      const index = lookupEl(el);\n      if (index > -1) {\n        if (debug)\n          console.log(\n            `Tried to add an element that was already mirrored. Updating instead...`\n          );\n\n        elements[index].mirror.innerText = el.innerText;\n\n        return;\n      }\n\n      const mirror = await mirrorEl(el);\n\n      if (!mirror) {\n        return;\n      }\n\n      const elHash = hash(mirror.outerHTML);\n\n      if (hashes[elHash]) {\n        if (debug)\n          console.log(\n            `iframe already contains element that is being mirrored. Skipping...`\n          );\n\n        return;\n      }\n\n      hashes[elHash] = true;\n\n      doc.head.append(mirror as HTMLElement);\n      elements.push({ original: el, mirror: mirror });\n\n      if (debug) console.log(`Added style node ${el.outerHTML}`);\n    };\n\n    const removeEl = (el: HTMLElement) => {\n      const index = lookupEl(el);\n      if (index === -1) {\n        if (debug)\n          console.log(\n            `Tried to remove an element that did not exist. Skipping...`\n          );\n\n        return;\n      }\n\n      const elHash = hash(el.outerHTML);\n\n      elements[index]?.mirror?.remove();\n      delete hashes[elHash];\n\n      if (debug) console.log(`Removed style node ${el.outerHTML}`);\n    };\n\n    const observer = new MutationObserver((mutations) => {\n      mutations.forEach((mutation) => {\n        if (mutation.type === \"childList\") {\n          mutation.addedNodes.forEach((node) => {\n            if (\n              node.nodeType === Node.TEXT_NODE ||\n              node.nodeType === Node.ELEMENT_NODE\n            ) {\n              const el =\n                node.nodeType === Node.TEXT_NODE\n                  ? node.parentElement\n                  : (node as HTMLElement);\n\n              if (el && el.matches(styleSelector)) {\n                defer(() => addEl(el));\n              }\n            }\n          });\n\n          mutation.removedNodes.forEach((node) => {\n            if (\n              node.nodeType === Node.TEXT_NODE ||\n              node.nodeType === Node.ELEMENT_NODE\n            ) {\n              const el =\n                node.nodeType === Node.TEXT_NODE\n                  ? node.parentElement\n                  : (node as HTMLElement);\n\n              if (el && el.matches(styleSelector)) {\n                defer(() => removeEl(el));\n              }\n            }\n          });\n        }\n      });\n    });\n\n    const parentDocument = win!.parent.document;\n\n    const collectedStyles = collectStyles(parentDocument);\n    const hrefs: string[] = [];\n    let stylesLoaded = 0;\n\n    // Sync attributes for the HTML tag\n    const parentHtml = parentDocument.getElementsByTagName(\"html\")[0];\n    syncAttributes(parentHtml, doc.documentElement);\n\n    // Sync attributes for the Body tag\n    const parentBody = parentDocument.getElementsByTagName(\"body\")[0];\n    syncAttributes(parentBody, doc.body);\n\n    Promise.all(\n      collectedStyles.map(async (styleNode, i) => {\n        if (styleNode.nodeName === \"LINK\") {\n          const linkHref = (styleNode as HTMLLinkElement).href;\n\n          // Don't process link elements with identical hrefs more than once\n          if (hrefs.indexOf(linkHref) > -1) {\n            return;\n          }\n\n          hrefs.push(linkHref);\n        }\n\n        const mirror = await mirrorEl(styleNode);\n\n        if (!mirror) return;\n\n        elements.push({ original: styleNode, mirror });\n\n        return mirror;\n      })\n    ).then((mirrorStyles) => {\n      const filtered = mirrorStyles.filter(\n        (el) => typeof el !== \"undefined\"\n      ) as HTMLStyleElement[];\n\n      filtered.forEach((mirror) => {\n        mirror.onload = () => {\n          stylesLoaded = stylesLoaded + 1;\n\n          if (stylesLoaded >= filtered.length) {\n            onStylesLoaded();\n          }\n        };\n        mirror.onerror = () => {\n          console.warn(`AutoFrame couldn't load a stylesheet`);\n          stylesLoaded = stylesLoaded + 1;\n\n          if (stylesLoaded >= filtered.length) {\n            onStylesLoaded();\n          }\n        };\n      });\n\n      // Reset HTML (inside the promise) so in case running twice (i.e. for React Strict mode)\n      doc.head.innerHTML = \"\";\n\n      // Inject initial values in bulk\n      doc.head.append(...filtered);\n\n      // Count <style> elements as immediately loaded (they don't fire onload)\n      filtered.forEach((mirror) => {\n        if (mirror.nodeName === \"STYLE\") {\n          stylesLoaded = stylesLoaded + 1;\n        }\n      });\n\n      if (stylesLoaded >= filtered.length) {\n        onStylesLoaded();\n      }\n\n      observer.observe(parentDocument.head, { childList: true, subtree: true });\n\n      filtered.forEach((el) => {\n        const elHash = hash(el.outerHTML);\n\n        hashes[elHash] = true;\n      });\n    });\n\n    return () => {\n      observer.disconnect();\n    };\n  }, []);\n\n  return <>{children}</>;\n};\n\nexport type AutoFrameProps = {\n  children: ReactNode;\n  className: string;\n  debug?: boolean;\n  id?: string;\n  onReady?: () => void;\n  onNotReady?: () => void;\n  frameRef: RefObject<HTMLIFrameElement | null>;\n};\n\ntype AutoFrameContext = {\n  document?: Document;\n  window?: Window;\n};\n\nexport const autoFrameContext = createContext<AutoFrameContext>({});\n\nexport const useFrame = () => useContext(autoFrameContext);\n\nfunction AutoFrame({\n  children,\n  className,\n  debug,\n  id,\n  onReady = () => {},\n  onNotReady = () => {},\n  frameRef,\n  ...props\n}: AutoFrameProps) {\n  const [loaded, setLoaded] = useState(false);\n  const [ctx, setCtx] = useState<AutoFrameContext>({});\n  const [mountTarget, setMountTarget] = useState<HTMLElement | null>();\n  const [stylesLoaded, setStylesLoaded] = useState(false);\n\n  useEffect(() => {\n    if (frameRef.current) {\n      const doc = frameRef.current.contentDocument;\n      const win = frameRef.current.contentWindow;\n\n      setCtx({\n        document: doc || undefined,\n        window: win || undefined,\n      });\n\n      setMountTarget(\n        frameRef.current.contentDocument?.getElementById(\"frame-root\")\n      );\n\n      if (doc && win && stylesLoaded) {\n        onReady();\n      } else {\n        onNotReady();\n      }\n    }\n  }, [frameRef, loaded, stylesLoaded]);\n\n  return (\n    <iframe\n      {...props}\n      className={className}\n      id={id}\n      srcDoc='<!DOCTYPE html><html><head></head><body><div id=\"frame-root\" data-puck-entry></div></body></html>'\n      ref={frameRef}\n      onLoad={() => {\n        setLoaded(true);\n      }}\n    >\n      <autoFrameContext.Provider value={ctx}>\n        {loaded && mountTarget && (\n          <CopyHostStyles\n            debug={debug}\n            onStylesLoaded={() => setStylesLoaded(true)}\n          >\n            {createPortal(children, mountTarget)}\n          </CopyHostStyles>\n        )}\n      </autoFrameContext.Provider>\n    </iframe>\n  );\n}\n\nAutoFrame.displayName = \"AutoFrame\";\n\nexport default AutoFrame;\n"
  },
  {
    "path": "packages/core/components/Breadcrumbs/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { ChevronRight } from \"lucide-react\";\nimport { useBreadcrumbs } from \"../../lib/use-breadcrumbs\";\nimport { useAppStore } from \"../../store\";\nimport { ReactNode } from \"react\";\n\nconst getClassName = getClassNameFactory(\"Breadcrumbs\", styles);\n\nexport const Breadcrumbs = ({\n  children,\n  numParents = 1,\n}: {\n  children?: ReactNode;\n  numParents?: number;\n}) => {\n  const setUi = useAppStore((s) => s.setUi);\n  const breadcrumbs = useBreadcrumbs(numParents);\n\n  return (\n    <div className={getClassName()}>\n      {breadcrumbs.map((breadcrumb, i) => (\n        <div key={i} className={getClassName(\"breadcrumb\")}>\n          <button\n            type=\"button\"\n            className={getClassName(\"breadcrumbLabel\")}\n            onClick={() => setUi({ itemSelector: breadcrumb.selector })}\n          >\n            {breadcrumb.label}\n          </button>\n          <ChevronRight size={16} />\n        </div>\n      ))}\n      {children}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Breadcrumbs/styles.module.css",
    "content": ".Breadcrumbs {\n  align-items: center;\n  display: flex;\n  gap: 4px;\n}\n\n.Breadcrumbs-breadcrumbLabel {\n  background: none;\n  border: 0;\n  border-radius: 2px;\n  color: var(--puck-color-azure-04);\n  cursor: pointer;\n  font: inherit;\n  flex-shrink: 0;\n  padding: 0;\n  transition: color 50ms ease-in;\n}\n\n.Breadcrumbs-breadcrumbLabel:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Breadcrumbs-breadcrumbLabel:hover {\n    color: var(--puck-color-azure-03);\n    transition: none;\n  }\n}\n\n.Breadcrumbs-breadcrumbLabel:active {\n  color: var(--puck-color-azure-02);\n  transition: none;\n}\n\n.Breadcrumbs-breadcrumb {\n  align-items: center;\n  display: flex;\n  gap: 4px;\n}\n"
  },
  {
    "path": "packages/core/components/Button/Button.module.css",
    "content": ".Button {\n  appearance: none;\n  background: none;\n  border: 1px solid transparent;\n  border-radius: 4px;\n  color: var(--puck-color-white);\n  display: inline-flex;\n  align-items: center;\n  gap: 8px;\n  letter-spacing: 0.05ch;\n  font-family: var(--puck-font-family);\n  font-size: 14px;\n  font-weight: 400;\n  box-sizing: border-box;\n  line-height: 1;\n  text-align: center;\n  text-decoration: none;\n  transition: background-color 50ms ease-in;\n  cursor: pointer;\n  white-space: nowrap;\n  margin: 0;\n}\n\n.Button:hover,\n.Button:active {\n  transition: none;\n}\n\n.Button--medium {\n  min-height: 34px;\n  padding-bottom: 7px;\n  padding-inline-start: 19px;\n  padding-inline-end: 19px;\n  padding-top: 7px;\n}\n\n.Button--large {\n  padding-bottom: 11px;\n  padding-inline-start: 19px;\n  padding-inline-end: 19px;\n  padding-top: 11px;\n}\n\n.Button-icon {\n  margin-top: 2px;\n}\n\n.Button--primary {\n  background: var(--puck-color-azure-04);\n}\n\n.Button:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Button--primary:hover {\n    background-color: var(--puck-color-azure-03);\n  }\n}\n\n.Button--primary:active {\n  background-color: var(--puck-color-azure-02);\n}\n\n.Button--secondary {\n  border: 1px solid currentColor;\n  color: currentColor;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Button--secondary:hover {\n    background-color: var(--puck-color-azure-12);\n    color: var(--puck-color-black);\n  }\n}\n\n.Button--secondary:active {\n  background-color: var(--puck-color-azure-11);\n  color: var(--puck-color-black);\n}\n\n.Button--flush {\n  border-radius: 0;\n}\n\n.Button--disabled,\n.Button--disabled:hover {\n  background-color: var(--puck-color-grey-07);\n  color: var(--puck-color-grey-03);\n  cursor: not-allowed;\n}\n\n.Button--fullWidth {\n  justify-content: center;\n  width: 100%;\n}\n\n.Button-spinner {\n  padding-inline-start: 8px;\n}\n"
  },
  {
    "path": "packages/core/components/Button/Button.tsx",
    "content": "\"use client\";\n\nimport { ReactNode, useEffect, useState } from \"react\";\nimport styles from \"./Button.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { Loader } from \"../Loader\";\nimport { filterDataAttrs } from \"../../lib/filter-data-attrs\";\n\nconst getClassName = getClassNameFactory(\"Button\", styles);\n\nexport const Button = ({\n  children,\n  href,\n  onClick,\n  variant = \"primary\",\n  type,\n  disabled,\n  tabIndex,\n  newTab,\n  fullWidth,\n  icon,\n  size = \"medium\",\n  loading: loadingProp = false,\n  ...props\n}: {\n  children: ReactNode;\n  href?: string;\n  onClick?: (e: any) => void | Promise<void>;\n  variant?: \"primary\" | \"secondary\";\n  type?: \"button\" | \"submit\" | \"reset\";\n  disabled?: boolean;\n  tabIndex?: number;\n  newTab?: boolean;\n  fullWidth?: boolean;\n  icon?: ReactNode;\n  size?: \"medium\" | \"large\";\n  loading?: boolean;\n}) => {\n  const [loading, setLoading] = useState(loadingProp);\n\n  useEffect(() => setLoading(loadingProp), [loadingProp]);\n\n  const ElementType = href ? \"a\" : type ? \"button\" : \"span\";\n  const dataAttrs = filterDataAttrs(props);\n\n  const el = (\n    <ElementType\n      className={getClassName({\n        primary: variant === \"primary\",\n        secondary: variant === \"secondary\",\n        disabled,\n        fullWidth,\n        [size]: true,\n      })}\n      onClick={(e) => {\n        if (!onClick) return;\n\n        setLoading(true);\n        Promise.resolve(onClick(e)).then(() => {\n          setLoading(false);\n        });\n      }}\n      type={type}\n      disabled={disabled || loading}\n      tabIndex={tabIndex}\n      target={newTab ? \"_blank\" : undefined}\n      rel={newTab ? \"noreferrer\" : undefined}\n      href={href}\n      {...dataAttrs}\n    >\n      {icon && <div className={getClassName(\"icon\")}>{icon}</div>}\n      {children}\n      {loading && (\n        <div className={getClassName(\"spinner\")}>\n          <Loader size={14} />\n        </div>\n      )}\n    </ElementType>\n  );\n\n  return el;\n};\n"
  },
  {
    "path": "packages/core/components/Button/index.ts",
    "content": "export * from \"./Button\";\n"
  },
  {
    "path": "packages/core/components/ComponentList/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { ReactNode, useEffect } from \"react\";\nimport { useAppStore } from \"../../store\";\nimport { ChevronDown, ChevronUp } from \"lucide-react\";\nimport { Drawer } from \"../Drawer\";\n\nconst getClassName = getClassNameFactory(\"ComponentList\", styles);\n\nconst ComponentListItem = ({\n  name,\n  label,\n}: {\n  name: string;\n  label?: string;\n  index?: number; // TODO deprecate\n}) => {\n  const overrides = useAppStore((s) => s.overrides);\n  const canInsert = useAppStore(\n    (s) =>\n      s.permissions.getPermissions({\n        type: name,\n      }).insert\n  );\n\n  // DEPRECATED\n  useEffect(() => {\n    if (overrides.componentItem) {\n      console.warn(\n        \"The `componentItem` override has been deprecated and renamed to `drawerItem`\"\n      );\n    }\n  }, [overrides]);\n\n  return (\n    <Drawer.Item label={label} name={name} isDragDisabled={!canInsert}>\n      {overrides.componentItem ?? overrides.drawerItem}\n    </Drawer.Item>\n  );\n};\n\nconst ComponentList = ({\n  children,\n  title,\n  id,\n}: {\n  id: string;\n  children?: ReactNode;\n  title?: string;\n}) => {\n  const config = useAppStore((s) => s.config);\n  const setUi = useAppStore((s) => s.setUi);\n  const componentList = useAppStore((s) => s.state.ui.componentList);\n\n  const { expanded = true } = componentList[id] || {};\n\n  return (\n    <div className={getClassName({ isExpanded: expanded })}>\n      {title && (\n        <button\n          type=\"button\"\n          className={getClassName(\"title\")}\n          onClick={() =>\n            setUi({\n              componentList: {\n                ...componentList,\n                [id]: {\n                  ...componentList[id],\n                  expanded: !expanded,\n                },\n              },\n            })\n          }\n          title={\n            expanded\n              ? `Collapse${title ? ` ${title}` : \"\"}`\n              : `Expand${title ? ` ${title}` : \"\"}`\n          }\n        >\n          <div>{title}</div>\n          <div className={getClassName(\"titleIcon\")}>\n            {expanded ? <ChevronUp size={12} /> : <ChevronDown size={12} />}\n          </div>\n        </button>\n      )}\n      <div className={getClassName(\"content\")}>\n        <Drawer>\n          {children ||\n            Object.keys(config.components).map((componentKey) => {\n              return (\n                <ComponentListItem\n                  key={componentKey}\n                  label={\n                    config.components[componentKey][\"label\"] ?? componentKey\n                  }\n                  name={componentKey}\n                />\n              );\n            })}\n        </Drawer>\n      </div>\n    </div>\n  );\n};\n\nComponentList.Item = ComponentListItem;\n\nexport { ComponentList };\n"
  },
  {
    "path": "packages/core/components/ComponentList/styles.module.css",
    "content": ".ComponentList {\n  max-width: 100%;\n}\n\n.ComponentList--isExpanded + .ComponentList {\n  margin-top: 12px;\n}\n\n.ComponentList-content {\n  display: none;\n}\n\n.ComponentList--isExpanded > .ComponentList-content {\n  display: block;\n}\n\n.ComponentList-title {\n  background-color: transparent;\n  border: 0;\n  color: var(--puck-color-grey-05);\n  cursor: pointer;\n  display: flex;\n  font: inherit;\n  font-size: var(--puck-font-size-xxxs);\n  list-style: none;\n  margin-bottom: 6px;\n  padding: 8px;\n  text-transform: uppercase;\n  transition: background-color 50ms ease-in, color 50ms ease-in;\n  gap: 4px;\n  border-radius: 4px;\n  width: 100%;\n}\n\n.ComponentList-title:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ComponentList-title:hover {\n    background-color: var(--puck-color-azure-11);\n    color: var(--puck-color-azure-04);\n    transition: none;\n  }\n}\n\n.ComponentList-title:active {\n  background-color: var(--puck-color-azure-10);\n  transition: none;\n}\n\n.ComponentList-titleIcon {\n  margin-inline-start: auto;\n}\n"
  },
  {
    "path": "packages/core/components/DefaultOverride/index.tsx",
    "content": "import { ReactNode } from \"react\";\n\nexport const DefaultOverride = ({ children }: { children?: ReactNode }) => (\n  <>{children}</>\n);\n"
  },
  {
    "path": "packages/core/components/DragDropContext/index.tsx",
    "content": "import { DragDropProvider } from \"@dnd-kit/react\";\nimport { useAppStore, useAppStoreApi } from \"../../store\";\nimport {\n  createContext,\n  Dispatch,\n  ReactNode,\n  SetStateAction,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { AutoScroller, defaultPreset, DragDropManager } from \"@dnd-kit/dom\";\nimport { DragDropEvents } from \"@dnd-kit/abstract\";\nimport { DropZoneProvider } from \"../DropZone\";\nimport type { Draggable, Droppable } from \"@dnd-kit/dom\";\nimport { getItem } from \"../../lib/data/get-item\";\nimport {\n  DropZoneContext,\n  Preview,\n  ZoneStore,\n  ZoneStoreProvider,\n} from \"../DropZone/context\";\nimport { createNestedDroppablePlugin } from \"../../lib/dnd/NestedDroppablePlugin\";\nimport { insertComponent } from \"../../lib/insert-component\";\nimport { moveComponent } from \"../../lib/move-component\";\nimport { useDebouncedCallback } from \"use-debounce\";\nimport { ComponentDndData } from \"../DraggableComponent\";\n\nimport { collisionStore } from \"../../lib/dnd/collision/dynamic/store\";\nimport { generateId } from \"../../lib/generate-id\";\nimport { createStore } from \"zustand\";\nimport { getDeepDir } from \"../../lib/get-deep-dir\";\nimport { useSensors } from \"../../lib/dnd/use-sensors\";\nimport { useSafeId } from \"../../lib/use-safe-id\";\nimport { getFrame } from \"../../lib/get-frame\";\nimport { effect } from \"@dnd-kit/state\";\n\nconst DEBUG = false;\n\ntype Events = DragDropEvents<Draggable, Droppable, DragDropManager>;\ntype DragCbs = Partial<{ [eventName in keyof Events]: Events[eventName][] }>;\n\nconst dragListenerContext = createContext<{\n  dragListeners: DragCbs;\n  setDragListeners?: Dispatch<SetStateAction<DragCbs>>;\n}>({\n  dragListeners: {},\n});\n\ntype EventKeys = keyof Events;\n\nexport function useDragListener(\n  type: EventKeys,\n  fn: Events[EventKeys],\n  deps: any[] = []\n) {\n  const { setDragListeners } = useContext(dragListenerContext);\n\n  useEffect(() => {\n    if (setDragListeners) {\n      setDragListeners((old) => ({\n        ...old,\n        [type]: [...(old[type] || []), fn],\n      }));\n    }\n  }, deps);\n}\n\ntype DeepestParams = {\n  zone: string | null;\n  area: string | null;\n};\n\nconst AREA_CHANGE_DEBOUNCE_MS = 100;\n\ntype DragDropContextProps = {\n  children: ReactNode;\n  disableAutoScroll?: boolean;\n};\n\n/**\n * Temporarily disable fallback collisions types, which\n * can cause issues during a zone switch.\n *\n * @param timeout the time in ms to disable the fallback collision for\n * @returns a function that temporarily disables the collision\n */\nconst useTempDisableFallback = (timeout: number) => {\n  const lastFallbackDisable = useRef<string>(null);\n\n  return useCallback((manager: DragDropManager) => {\n    collisionStore.setState({ fallbackEnabled: false });\n\n    // Track an ID in case called more than once, so only last call re-enables\n    const fallbackId = generateId();\n    lastFallbackDisable.current = fallbackId;\n\n    setTimeout(() => {\n      if (lastFallbackDisable.current === fallbackId) {\n        collisionStore.setState({ fallbackEnabled: true });\n        manager.collisionObserver.forceUpdate(true);\n      }\n    }, timeout);\n  }, []);\n};\n\nconst DragDropContextClient = ({\n  children,\n  disableAutoScroll,\n}: DragDropContextProps) => {\n  const dispatch = useAppStore((s) => s.dispatch);\n  const instanceId = useAppStore((s) => s.instanceId);\n  const appStore = useAppStoreApi();\n\n  const debouncedParamsRef = useRef<DeepestParams | null>(null);\n\n  const tempDisableFallback = useTempDisableFallback(100);\n\n  const [zoneStore] = useState(() =>\n    createStore<ZoneStore>(() => ({\n      zoneDepthIndex: {},\n      nextZoneDepthIndex: {},\n      areaDepthIndex: {},\n      nextAreaDepthIndex: {},\n      draggedItem: null,\n      previewIndex: {},\n      enabledIndex: {},\n      hoveringComponent: null,\n    }))\n  );\n\n  const getChanged = useCallback(\n    (params: DeepestParams) => {\n      const { zoneDepthIndex = {}, areaDepthIndex = {} } =\n        zoneStore.getState() || {};\n\n      const stateHasZone = Object.keys(zoneDepthIndex).length > 0;\n      const stateHasArea = Object.keys(areaDepthIndex).length > 0;\n\n      let zoneChanged = false;\n      let areaChanged = false;\n\n      if (params.zone && !zoneDepthIndex[params.zone]) {\n        zoneChanged = true;\n      } else if (!params.zone && stateHasZone) {\n        zoneChanged = true;\n      }\n\n      if (params.area && !areaDepthIndex[params.area]) {\n        areaChanged = true;\n      } else if (!params.area && stateHasArea) {\n        areaChanged = true;\n      }\n\n      return { zoneChanged, areaChanged };\n    },\n    [zoneStore]\n  );\n\n  const setDeepestAndCollide = useCallback(\n    (params: DeepestParams, manager: DragDropManager) => {\n      const { zoneChanged, areaChanged } = getChanged(params);\n\n      if (!zoneChanged && !areaChanged) return;\n\n      zoneStore.setState({\n        zoneDepthIndex: params.zone ? { [params.zone]: true } : {},\n        areaDepthIndex: params.area ? { [params.area]: true } : {},\n      });\n\n      // Disable fallback collisions temporarily after zone change,\n      // as these can cause unexpected collisions\n      tempDisableFallback(manager);\n\n      setTimeout(() => {\n        // Force update after debounce\n        manager.collisionObserver.forceUpdate(true);\n      }, 50);\n\n      debouncedParamsRef.current = null;\n    },\n    [zoneStore]\n  );\n\n  const setDeepestDb = useDebouncedCallback(\n    setDeepestAndCollide,\n    AREA_CHANGE_DEBOUNCE_MS\n  );\n\n  const cancelDb = () => {\n    setDeepestDb.cancel();\n    debouncedParamsRef.current = null;\n  };\n\n  useEffect(() => {\n    if (DEBUG) {\n      zoneStore.subscribe((s) =>\n        console.log(\n          s.previewIndex,\n          Object.entries(s.zoneDepthIndex || {})[0]?.[0],\n          Object.entries(s.areaDepthIndex || {})[0]?.[0]\n        )\n      );\n    }\n  }, []);\n\n  const [plugins] = useState(() => [\n    ...(disableAutoScroll\n      ? defaultPreset.plugins.filter((plugin) => plugin !== AutoScroller)\n      : defaultPreset.plugins),\n    createNestedDroppablePlugin(\n      {\n        onChange: (params, manager) => {\n          const state = zoneStore.getState();\n\n          const { zoneChanged, areaChanged } = getChanged(params);\n\n          const isDragging = manager.dragOperation.status.dragging;\n\n          if (areaChanged || zoneChanged) {\n            let nextZoneDepthIndex: Record<string, boolean> = {};\n            let nextAreaDepthIndex: Record<string, boolean> = {};\n\n            if (params.zone) {\n              nextZoneDepthIndex = { [params.zone]: true };\n            }\n\n            if (params.area) {\n              nextAreaDepthIndex = { [params.area]: true };\n            }\n\n            zoneStore.setState({ nextZoneDepthIndex, nextAreaDepthIndex });\n          }\n\n          if (params.zone !== \"void\" && state?.zoneDepthIndex[\"void\"]) {\n            setDeepestAndCollide(params, manager);\n            return;\n          }\n\n          if (areaChanged) {\n            if (isDragging) {\n              // Only call the debounced function if these params differ from the last pending call\n              const debouncedParams = debouncedParamsRef.current;\n              const isSameParams =\n                debouncedParams &&\n                debouncedParams.area === params.area &&\n                debouncedParams.zone === params.zone;\n\n              if (!isSameParams) {\n                cancelDb(); // NB we always cancel the debounce if the params change, so we could just use a timer\n                setDeepestDb(params, manager);\n                debouncedParamsRef.current = params;\n              }\n            } else {\n              cancelDb();\n              setDeepestAndCollide(params, manager);\n            }\n\n            return;\n          }\n\n          if (zoneChanged) {\n            setDeepestAndCollide(params, manager);\n          }\n\n          cancelDb();\n        },\n      },\n      instanceId\n    ),\n  ]);\n\n  const sensors = useSensors();\n\n  const [dragListeners, setDragListeners] = useState<DragCbs>({});\n\n  const dragMode = useRef<\"new\" | \"existing\" | null>(null);\n\n  const initialSelector = useRef<{ zone: string; index: number }>(undefined);\n\n  const nextContextValue = useMemo<DropZoneContext>(\n    () => ({\n      mode: \"edit\",\n      areaId: \"root\",\n      depth: 0,\n    }),\n    []\n  );\n\n  return (\n    <dragListenerContext.Provider\n      value={{\n        dragListeners,\n        setDragListeners,\n      }}\n    >\n      <DragDropProvider\n        plugins={plugins}\n        sensors={sensors}\n        onDragEnd={(event, manager) => {\n          const entryEl = getFrame()?.querySelector(\"[data-puck-entry]\");\n          entryEl?.removeAttribute(\"data-puck-dragging\");\n\n          const { source, target } = event.operation;\n\n          if (!source) {\n            zoneStore.setState({ draggedItem: null });\n\n            return;\n          }\n\n          const { zone, index } = source.data as ComponentDndData;\n\n          const { previewIndex = {} } = zoneStore.getState() || {};\n\n          const thisPreview: Preview | null =\n            previewIndex[zone]?.props.id === source.id\n              ? previewIndex[zone]\n              : null;\n\n          const onAnimationEnd = () => {\n            zoneStore.setState({ draggedItem: null });\n\n            // Tidy up cancellation\n            if (event.canceled || target?.type === \"void\") {\n              zoneStore.setState({ previewIndex: {} });\n\n              // Finalise the drag\n              if (thisPreview) {\n                zoneStore.setState({ previewIndex: {} });\n\n                if (thisPreview.type === \"insert\") {\n                  insertComponent(\n                    thisPreview.componentType,\n                    thisPreview.zone,\n                    thisPreview.index,\n                    appStore\n                  );\n                } else if (initialSelector.current) {\n                  moveComponent(\n                    thisPreview.props.id,\n                    initialSelector.current,\n                    thisPreview,\n                    appStore\n                  );\n                }\n              }\n\n              dispatch({\n                type: \"setUi\",\n                ui: {\n                  itemSelector: null,\n                  isDragging: false,\n                },\n              });\n\n              dragListeners.dragend?.forEach((fn) => {\n                fn(event, manager);\n              });\n\n              return;\n            }\n\n            // Finalise the drag\n            if (thisPreview) {\n              zoneStore.setState({ previewIndex: {} });\n\n              if (thisPreview.type === \"insert\") {\n                insertComponent(\n                  thisPreview.componentType,\n                  thisPreview.zone,\n                  thisPreview.index,\n                  appStore\n                );\n              } else if (initialSelector.current) {\n                dispatch({\n                  type: \"move\",\n                  sourceIndex: initialSelector.current.index,\n                  sourceZone: initialSelector.current.zone,\n                  destinationIndex: thisPreview.index,\n                  destinationZone: thisPreview.zone,\n                  recordHistory: false,\n                });\n              }\n            }\n\n            dispatch({\n              type: \"setUi\",\n              ui: {\n                itemSelector: { index, zone },\n                isDragging: false,\n              },\n              recordHistory: true,\n            });\n\n            dragListeners.dragend?.forEach((fn) => {\n              fn(event, manager);\n            });\n          };\n\n          // Delay insert until animation has finished\n          let dispose: () => void | undefined;\n\n          dispose = effect(() => {\n            if (source.status === \"idle\") {\n              onAnimationEnd();\n              dispose?.();\n            }\n          });\n        }}\n        onDragOver={(event, manager) => {\n          // Prevent the optimistic re-ordering\n          event.preventDefault();\n\n          const draggedItem = zoneStore.getState()?.draggedItem;\n\n          // Drag end can sometimes trigger after drag\n          if (!draggedItem) return;\n\n          // Cancel any stale debounces\n          cancelDb();\n\n          const { source, target } = event.operation;\n\n          if (!target || !source || target.type === \"void\") return;\n\n          const [sourceId] = (source.id as string).split(\":\");\n          const [targetId] = (target.id as string).split(\":\");\n\n          const sourceData = source.data as ComponentDndData;\n\n          let sourceZone = sourceData.zone;\n          let sourceIndex = sourceData.index;\n\n          let targetZone = \"\";\n          let targetIndex = 0;\n\n          if (target.type === \"component\") {\n            const targetData = target.data as ComponentDndData;\n\n            targetZone = targetData.zone;\n            targetIndex = targetData.index;\n\n            const collisionData = manager.collisionObserver.collisions[0]?.data;\n\n            const dir = getDeepDir(target.element);\n\n            const collisionPosition =\n              collisionData?.direction === \"up\" ||\n              (dir === \"ltr\" && collisionData?.direction === \"left\") ||\n              (dir === \"rtl\" && collisionData?.direction === \"right\")\n                ? \"before\"\n                : \"after\";\n\n            if (targetIndex >= sourceIndex && sourceZone === targetZone) {\n              targetIndex = targetIndex - 1;\n            }\n\n            if (collisionPosition === \"after\") {\n              targetIndex = targetIndex + 1;\n            }\n          } else {\n            targetZone = target.id.toString();\n            targetIndex = 0;\n          }\n\n          const path =\n            appStore.getState().state.indexes.nodes[target.id]?.path || [];\n\n          // Abort if dragging over self or descendant\n          if (\n            targetId === sourceId ||\n            path.find((path) => {\n              const [pathId] = (path as string).split(\":\");\n              return pathId === sourceId;\n            })\n          ) {\n            return;\n          }\n\n          if (dragMode.current === \"new\") {\n            zoneStore.setState({\n              previewIndex: {\n                [targetZone]: {\n                  componentType: sourceData.componentType,\n                  type: \"insert\",\n                  index: targetIndex,\n                  zone: targetZone,\n                  element: source.element,\n                  props: {\n                    id: source.id.toString(),\n                  },\n                },\n              },\n            });\n          } else {\n            if (!initialSelector.current) {\n              initialSelector.current = {\n                zone: sourceData.zone,\n                index: sourceData.index,\n              };\n            }\n\n            const item = getItem(\n              initialSelector.current,\n              appStore.getState().state\n            );\n\n            if (item) {\n              zoneStore.setState({\n                previewIndex: {\n                  [targetZone]: {\n                    componentType: sourceData.componentType,\n                    type: \"move\",\n                    index: targetIndex,\n                    zone: targetZone,\n                    props: item.props,\n                    element: source.element,\n                  },\n                },\n              });\n            }\n          }\n\n          dragListeners.dragover?.forEach((fn) => {\n            fn(event, manager);\n          });\n        }}\n        onDragStart={(event, manager) => {\n          const { source } = event.operation;\n\n          if (source && source.type !== \"void\") {\n            const sourceData = source.data as ComponentDndData;\n\n            const item = getItem(\n              {\n                zone: sourceData.zone,\n                index: sourceData.index,\n              },\n              appStore.getState().state\n            );\n\n            if (item) {\n              zoneStore.setState({\n                previewIndex: {\n                  [sourceData.zone]: {\n                    componentType: sourceData.componentType,\n                    type: \"move\",\n                    index: sourceData.index,\n                    zone: sourceData.zone,\n                    props: item.props,\n                    element: source.element,\n                  },\n                },\n              });\n            }\n          }\n\n          dragListeners.dragstart?.forEach((fn) => {\n            fn(event, manager);\n          });\n        }}\n        onBeforeDragStart={(event) => {\n          const isNewComponent = event.operation.source?.type === \"drawer\";\n\n          dragMode.current = isNewComponent ? \"new\" : \"existing\";\n          initialSelector.current = undefined;\n\n          zoneStore.setState({ draggedItem: event.operation.source });\n\n          if (\n            appStore.getState().selectedItem?.props.id !==\n            event.operation.source?.id\n          ) {\n            dispatch({\n              type: \"setUi\",\n              ui: {\n                itemSelector: null,\n                isDragging: true,\n              },\n              recordHistory: false,\n            });\n          } else {\n            dispatch({\n              type: \"setUi\",\n              ui: {\n                isDragging: true,\n              },\n              recordHistory: false,\n            });\n          }\n\n          const entryEl = getFrame()?.querySelector(\"[data-puck-entry]\");\n          entryEl?.setAttribute(\"data-puck-dragging\", \"true\");\n        }}\n      >\n        <ZoneStoreProvider store={zoneStore}>\n          <DropZoneProvider value={nextContextValue}>\n            {children}\n          </DropZoneProvider>\n        </ZoneStoreProvider>\n      </DragDropProvider>\n    </dragListenerContext.Provider>\n  );\n};\n\nexport const DragDropContext = ({\n  children,\n  disableAutoScroll,\n}: DragDropContextProps) => {\n  const status = useAppStore((s) => s.status);\n\n  if (status === \"LOADING\") {\n    return children;\n  }\n\n  return (\n    <DragDropContextClient disableAutoScroll={disableAutoScroll}>\n      {children}\n    </DragDropContextClient>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/DragIcon/index.tsx",
    "content": "import { getClassNameFactory } from \"../../lib\";\n\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"DragIcon\", styles);\n\nexport const DragIcon = ({ isDragDisabled }: { isDragDisabled?: boolean }) => (\n  <div className={getClassName({ disabled: isDragDisabled })}>\n    <svg viewBox=\"0 0 20 20\" width=\"12\" fill=\"currentColor\">\n      <path d=\"M7 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 2zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 7 14zm6-8a2 2 0 1 0-.001-4.001A2 2 0 0 0 13 6zm0 2a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 8zm0 6a2 2 0 1 0 .001 4.001A2 2 0 0 0 13 14z\"></path>\n    </svg>\n  </div>\n);\n"
  },
  {
    "path": "packages/core/components/DragIcon/styles.module.css",
    "content": ".DragIcon {\n  color: var(--puck-color-grey-05);\n  cursor: grab;\n  padding: 4px;\n  border-radius: 4px;\n}\n\n.DragIcon--disabled {\n  cursor: no-drop;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .DragIcon:not(.DragIcon--disabled):hover {\n    color: var(--puck-color-azure-05);\n    background-color: var(--puck-color-azure-12);\n  }\n}\n"
  },
  {
    "path": "packages/core/components/DraggableComponent/index.tsx",
    "content": "import {\n  CSSProperties,\n  ReactNode,\n  Ref,\n  SyntheticEvent,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n  useTransition,\n} from \"react\";\nimport styles from \"./styles.module.css\";\nimport \"./styles.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { Copy, CornerLeftUp, Trash } from \"lucide-react\";\nimport { useAppStore, useAppStoreApi } from \"../../store\";\nimport { Loader } from \"../Loader\";\nimport { ActionBar } from \"../ActionBar\";\n\nimport { createPortal } from \"react-dom\";\n\nimport { dropZoneContext, DropZoneProvider } from \"../DropZone\";\nimport { createDynamicCollisionDetector } from \"../../lib/dnd/collision/dynamic\";\nimport { DragAxis } from \"../../types\";\nimport { UniqueIdentifier } from \"@dnd-kit/abstract\";\nimport { getDeepScrollPosition } from \"../../lib/get-deep-scroll-position\";\nimport { DropZoneContext, ZoneStoreContext } from \"../DropZone/context\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { getItem } from \"../../lib/data/get-item\";\nimport { useSortable } from \"@dnd-kit/react/sortable\";\nimport { accumulateTransform } from \"../../lib/accumulate-transform\";\nimport { useContextStore } from \"../../lib/use-context-store\";\nimport { useOnDragFinished } from \"../../lib/dnd/use-on-drag-finished\";\nimport { LoadedRichTextMenu } from \"../RichTextMenu\";\n\nconst getClassName = getClassNameFactory(\"DraggableComponent\", styles);\n\nconst DEBUG = false;\n\n// Magic numbers are used to position actions overlay 8px from top of component, bottom of component (when sticky scrolling) and side of preview\nconst space = 8;\nconst actionsOverlayTop = space * 6.5;\nconst actionsTop = -(actionsOverlayTop - 8);\nconst actionsSide = space;\n\nconst DefaultActionBar = ({\n  label,\n  children,\n  parentAction,\n}: {\n  label: string | undefined;\n  children: ReactNode;\n  parentAction: ReactNode;\n}) => (\n  <ActionBar>\n    <ActionBar.Group>\n      {parentAction}\n      {label && <ActionBar.Label label={label} />}\n    </ActionBar.Group>\n    <ActionBar.Group>{children}</ActionBar.Group>\n  </ActionBar>\n);\n\nconst DefaultOverlay = ({\n  children,\n}: {\n  children: ReactNode;\n  hover: boolean;\n  isSelected: boolean;\n  componentId: string;\n  componentType: string;\n}) => <>{children}</>;\n\nexport type ComponentDndData = {\n  areaId?: string;\n  zone: string;\n  index: number;\n  componentType: string;\n  containsActiveZone: boolean;\n  depth: number;\n  path: UniqueIdentifier[];\n  inDroppableZone: boolean;\n};\n\nexport const DraggableComponent = ({\n  children,\n  depth,\n  componentType,\n  id,\n  index,\n  zoneCompound,\n  isLoading = false,\n  isSelected = false,\n  debug,\n  label,\n  autoDragAxis,\n  userDragAxis,\n  inDroppableZone = true,\n}: {\n  children: (ref: Ref<any>) => ReactNode;\n  componentType: string;\n  depth: number;\n  id: string;\n  index: number;\n  zoneCompound: string;\n  isSelected?: boolean;\n  debug?: string;\n  label?: string;\n  isLoading: boolean;\n  autoDragAxis: DragAxis;\n  userDragAxis?: DragAxis;\n  inDroppableZone: boolean;\n}) => {\n  const zoom = useAppStore((s) =>\n    s.selectedItem?.props.id === id ? s.zoomConfig.zoom : 1\n  );\n  const overrides = useAppStore((s) => s.overrides);\n  const dispatch = useAppStore((s) => s.dispatch);\n  const iframe = useAppStore((s) => s.iframe);\n\n  const ctx = useContext(dropZoneContext);\n\n  const [localZones, setLocalZones] = useState<Record<string, boolean>>({});\n\n  const registerLocalZone = useCallback(\n    (zoneCompound: string, active: boolean) => {\n      // Propagate local zone\n      ctx?.registerLocalZone?.(zoneCompound, active);\n\n      setLocalZones((obj) => ({\n        ...obj,\n        [zoneCompound]: active,\n      }));\n    },\n    [setLocalZones]\n  );\n\n  const unregisterLocalZone = useCallback(\n    (zoneCompound: string) => {\n      // Propagate local zone\n      ctx?.unregisterLocalZone?.(zoneCompound);\n\n      setLocalZones((obj) => {\n        const newLocalZones = {\n          ...obj,\n        };\n\n        delete newLocalZones[zoneCompound];\n\n        return newLocalZones;\n      });\n    },\n    [setLocalZones]\n  );\n\n  const containsActiveZone =\n    Object.values(localZones).filter(Boolean).length > 0;\n\n  const path = useAppStore(useShallow((s) => s.state.indexes.nodes[id]?.path));\n  const permissions = useAppStore(\n    useShallow((s) => {\n      const item = getItem({ index, zone: zoneCompound }, s.state);\n\n      return s.permissions.getPermissions({ item });\n    })\n  );\n\n  const zoneStore = useContext(ZoneStoreContext);\n\n  const [dragAxis, setDragAxis] = useState(userDragAxis || autoDragAxis);\n\n  const dynamicCollisionDetector = useMemo(\n    () => createDynamicCollisionDetector(dragAxis),\n    [dragAxis]\n  );\n\n  const {\n    ref: sortableRef,\n    isDragging: thisIsDragging,\n    sortable,\n  } = useSortable<ComponentDndData>({\n    id,\n    index,\n    group: zoneCompound,\n    type: \"component\",\n    data: {\n      areaId: ctx?.areaId,\n      zone: zoneCompound,\n      index,\n      componentType,\n      containsActiveZone,\n      depth,\n      path: path || [],\n      inDroppableZone,\n    },\n    collisionPriority: depth,\n    collisionDetector: dynamicCollisionDetector,\n    // \"Out of the way\" transition from react-beautiful-dnd\n    transition: {\n      duration: 200,\n      easing: \"cubic-bezier(0.2, 0, 0, 1)\",\n    },\n    feedback: \"clone\",\n  });\n\n  useEffect(() => {\n    const isEnabled = zoneStore.getState().enabledIndex[zoneCompound];\n\n    sortable.droppable.disabled = !isEnabled;\n    sortable.draggable.disabled = !permissions.drag;\n\n    const cleanup = zoneStore.subscribe((s) => {\n      sortable.droppable.disabled = !s.enabledIndex[zoneCompound];\n    });\n\n    if (ref.current && !permissions.drag) {\n      ref.current.setAttribute(\"data-puck-disabled\", \"\");\n\n      return () => {\n        ref.current?.removeAttribute(\"data-puck-disabled\");\n        cleanup();\n      };\n    }\n\n    return cleanup;\n  }, [permissions.drag, zoneCompound]);\n\n  const [, setRerender] = useState(0);\n\n  const ref = useRef<HTMLElement>(null);\n\n  const refSetter = useCallback(\n    (el: HTMLElement | null) => {\n      sortableRef(el);\n\n      if (ref.current !== el) {\n        ref.current = el;\n        setRerender((update) => update + 1);\n      }\n    },\n    [sortableRef]\n  );\n\n  const [portalEl, setPortalEl] = useState<HTMLElement>();\n\n  useEffect(() => {\n    setPortalEl(\n      iframe.enabled\n        ? ref.current?.ownerDocument.body\n        : ref.current?.closest<HTMLElement>(\"[data-puck-preview]\") ??\n            document.body\n    );\n  }, [iframe.enabled, ref.current]);\n\n  const getStyle = useCallback(() => {\n    if (!ref.current) return;\n\n    const rect = ref.current!.getBoundingClientRect();\n    const deepScrollPosition = getDeepScrollPosition(ref.current);\n\n    const portalContainerEl = iframe.enabled\n      ? null\n      : ref.current?.closest<HTMLElement>(\"[data-puck-preview]\");\n\n    const portalContainerRect = portalContainerEl?.getBoundingClientRect();\n    const portalScroll = portalContainerEl\n      ? getDeepScrollPosition(portalContainerEl)\n      : { x: 0, y: 0 };\n\n    const scroll = {\n      x:\n        deepScrollPosition.x -\n        portalScroll.x -\n        (portalContainerRect?.left ?? 0),\n      y:\n        deepScrollPosition.y - portalScroll.y - (portalContainerRect?.top ?? 0),\n    };\n\n    const untransformed = {\n      height: ref.current.offsetHeight,\n      width: ref.current.offsetWidth,\n    };\n\n    const transform = accumulateTransform(ref.current);\n\n    const style: CSSProperties = {\n      left: `${(rect.left + scroll.x) / transform.scaleX}px`,\n      top: `${(rect.top + scroll.y) / transform.scaleY}px`,\n      height: `${untransformed.height}px`,\n      width: `${untransformed.width}px`,\n    };\n\n    return style;\n  }, [ref.current]);\n\n  const [style, setStyle] = useState<CSSProperties>();\n\n  const sync = useCallback(() => {\n    setStyle(getStyle());\n  }, [ref.current, iframe]);\n\n  useEffect(() => {\n    if (ref.current) {\n      const observer = new ResizeObserver(sync);\n\n      observer.observe(ref.current);\n\n      return () => {\n        observer.disconnect();\n      };\n    }\n  }, [ref.current]);\n\n  const registerNode = useAppStore((s) => s.nodes.registerNode);\n\n  const hideOverlay = useCallback(() => {\n    setIsVisible(false);\n  }, []);\n\n  const showOverlay = useCallback(() => {\n    setIsVisible(true);\n  }, []);\n\n  useEffect(() => {\n    registerNode(id, {\n      methods: { sync, showOverlay, hideOverlay },\n      element: ref.current ?? null,\n    });\n\n    return () => {\n      registerNode(id, {\n        methods: {\n          sync: () => null,\n          hideOverlay: () => null,\n          showOverlay: () => null,\n        },\n        element: null,\n      });\n    };\n  }, [id, zoneCompound, index, componentType, sync]);\n\n  const CustomActionBar = useMemo(\n    () => overrides.actionBar || DefaultActionBar,\n    [overrides.actionBar]\n  );\n\n  const CustomOverlay = useMemo(\n    () => overrides.componentOverlay || DefaultOverlay,\n    [overrides.componentOverlay]\n  );\n\n  const onClick = useCallback(\n    (e: Event | SyntheticEvent) => {\n      const el = e.target as Element;\n\n      if (!el.closest(\"[data-puck-overlay-portal]\")) {\n        e.stopPropagation();\n      }\n\n      if (isSelected) {\n        dispatch({\n          type: \"setUi\",\n          ui: {\n            itemSelector: null,\n          },\n        });\n      } else {\n        dispatch({\n          type: \"setUi\",\n          ui: {\n            itemSelector: { index, zone: zoneCompound },\n          },\n        });\n      }\n    },\n    [index, zoneCompound, id, isSelected]\n  );\n\n  const appStore = useAppStoreApi();\n\n  const onSelectParent = useCallback(() => {\n    const { nodes, zones } = appStore.getState().state.indexes;\n    const node = nodes[id];\n\n    const parentNode = node?.parentId ? nodes[node?.parentId] : null;\n\n    if (!parentNode || !node.parentId) {\n      return;\n    }\n\n    const parentZoneCompound = `${parentNode.parentId}:${parentNode.zone}`;\n\n    const parentIndex = zones[parentZoneCompound].contentIds.indexOf(\n      node.parentId\n    );\n\n    dispatch({\n      type: \"setUi\",\n      ui: {\n        itemSelector: {\n          zone: parentZoneCompound,\n          index: parentIndex,\n        },\n      },\n    });\n  }, [ctx, path]);\n\n  const onDuplicate = useCallback(() => {\n    dispatch({\n      type: \"duplicate\",\n      sourceIndex: index,\n      sourceZone: zoneCompound,\n    });\n  }, [index, zoneCompound]);\n\n  const onDelete = useCallback(() => {\n    dispatch({\n      type: \"remove\",\n      index: index,\n      zone: zoneCompound,\n    });\n  }, [index, zoneCompound]);\n\n  const [hover, setHover] = useState(false);\n\n  const indicativeHover = useContextStore(\n    ZoneStoreContext,\n    (s) => s.hoveringComponent === id\n  );\n\n  useEffect(() => {\n    if (!ref.current) {\n      return;\n    }\n\n    const el = ref.current as HTMLElement;\n\n    const _onMouseOver = (e: Event) => {\n      const userIsDragging = !!zoneStore.getState().draggedItem;\n\n      if (userIsDragging) {\n        // User is dragging, and dragging this item\n        if (thisIsDragging) {\n          setHover(true);\n        } else {\n          setHover(false);\n        }\n      } else {\n        setHover(true);\n      }\n\n      e.stopPropagation();\n    };\n\n    const _onMouseOut = (e: Event) => {\n      e.stopPropagation();\n\n      setHover(false);\n    };\n\n    el.setAttribute(\"data-puck-component\", id);\n    el.setAttribute(\"data-puck-dnd\", id);\n    el.style.position = \"relative\";\n    el.addEventListener(\"click\", onClick);\n    el.addEventListener(\"mouseover\", _onMouseOver);\n    el.addEventListener(\"mouseout\", _onMouseOut);\n\n    return () => {\n      el.removeAttribute(\"data-puck-component\");\n      el.removeAttribute(\"data-puck-dnd\");\n      el.removeEventListener(\"click\", onClick);\n      el.removeEventListener(\"mouseover\", _onMouseOver);\n      el.removeEventListener(\"mouseout\", _onMouseOut);\n    };\n  }, [\n    ref.current, // Remount attributes if the element changes\n    onClick,\n    containsActiveZone,\n    zoneCompound,\n    id,\n    thisIsDragging,\n    inDroppableZone,\n  ]);\n\n  const [isVisible, setIsVisible] = useState(false);\n  const [dragFinished, setDragFinished] = useState(true);\n  const [_, startTransition] = useTransition();\n\n  useEffect(() => {\n    startTransition(() => {\n      if (hover || indicativeHover || isSelected) {\n        sync();\n        setIsVisible(true);\n        setThisWasDragging(false);\n      } else {\n        setIsVisible(false);\n      }\n    });\n  }, [hover, indicativeHover, isSelected, iframe]);\n\n  const [thisWasDragging, setThisWasDragging] = useState(false);\n\n  const onDragFinished = useOnDragFinished((finished) => {\n    if (finished) {\n      startTransition(() => {\n        sync();\n        setDragFinished(true);\n      });\n    } else {\n      setDragFinished(false);\n    }\n  });\n\n  useEffect(() => {\n    if (thisIsDragging) {\n      setThisWasDragging(true);\n    }\n  }, [thisIsDragging]);\n\n  useEffect(() => {\n    if (thisWasDragging) return onDragFinished();\n  }, [thisWasDragging, onDragFinished]);\n\n  const syncActionsPosition = useCallback(\n    (el: HTMLDivElement | null | undefined) => {\n      if (el) {\n        const view = el.ownerDocument.defaultView;\n\n        if (view) {\n          const rect = el.getBoundingClientRect();\n\n          const diffLeft = rect.x;\n          const exceedsBoundsLeft = diffLeft < 0;\n          const diffTop = rect.y;\n          const exceedsBoundsTop = diffTop < 0;\n\n          // Modify position if it spills over frame\n          if (exceedsBoundsLeft) {\n            el.style.transformOrigin = \"left top\";\n            el.style.left = \"0px\";\n          }\n\n          if (exceedsBoundsTop) {\n            el.style.top = \"12px\";\n            if (!exceedsBoundsLeft) {\n              el.style.transformOrigin = \"right top\";\n            }\n          }\n        }\n      }\n    },\n    [zoom]\n  );\n\n  useEffect(() => {\n    if (userDragAxis) {\n      setDragAxis(userDragAxis);\n      return;\n    }\n\n    if (ref.current) {\n      const computedStyle = window.getComputedStyle(ref.current);\n\n      if (\n        computedStyle.display === \"inline\" ||\n        computedStyle.display === \"inline-block\"\n      ) {\n        setDragAxis(\"x\");\n\n        return;\n      }\n    }\n\n    setDragAxis(autoDragAxis);\n  }, [ref, userDragAxis, autoDragAxis]);\n\n  const parentAction = useMemo(\n    () =>\n      ctx?.areaId &&\n      ctx?.areaId !== \"root\" && (\n        <ActionBar.Action onClick={onSelectParent} label=\"Select parent\">\n          <CornerLeftUp size={16} />\n        </ActionBar.Action>\n      ),\n    [ctx?.areaId]\n  );\n\n  const nextContextValue = useMemo<DropZoneContext>(\n    () => ({\n      ...ctx!,\n      areaId: id,\n      zoneCompound,\n      index,\n      depth: depth + 1,\n      registerLocalZone,\n      unregisterLocalZone,\n    }),\n    [\n      ctx,\n      id,\n      zoneCompound,\n      index,\n      depth,\n      registerLocalZone,\n      unregisterLocalZone,\n    ]\n  );\n\n  const richText = useAppStore((s) =>\n    s.currentRichText?.inlineComponentId === id ? s.currentRichText : null\n  );\n\n  const hasNormalActions = permissions.duplicate || permissions.delete;\n\n  return (\n    <DropZoneProvider value={nextContextValue}>\n      {dragFinished &&\n        isVisible &&\n        createPortal(\n          <div\n            className={getClassName({\n              isSelected,\n              isDragging: thisIsDragging,\n              hover: hover || indicativeHover,\n            })}\n            style={{ ...style }}\n            data-puck-overlay\n          >\n            {debug}\n            {isLoading && (\n              <div className={getClassName(\"loadingOverlay\")}>\n                <Loader />\n              </div>\n            )}\n            <div\n              className={getClassName(\"actionsOverlay\")}\n              style={{\n                top: actionsOverlayTop / zoom,\n              }}\n            >\n              <div\n                className={getClassName(\"actions\")}\n                style={{\n                  transform: `scale(${1 / zoom}`,\n                  top: actionsTop / zoom,\n                  right: 0,\n                  paddingLeft: actionsSide,\n                  paddingRight: actionsSide,\n                }}\n                ref={syncActionsPosition}\n              >\n                <CustomActionBar\n                  parentAction={parentAction}\n                  label={DEBUG ? id : label}\n                >\n                  {richText && (\n                    <>\n                      <LoadedRichTextMenu\n                        editor={richText.editor}\n                        field={richText.field}\n                        inline\n                        readOnly={false}\n                      />\n                      {hasNormalActions && <ActionBar.Separator />}\n                    </>\n                  )}\n\n                  {permissions.duplicate && (\n                    <ActionBar.Action onClick={onDuplicate} label=\"Duplicate\">\n                      <Copy size={16} />\n                    </ActionBar.Action>\n                  )}\n                  {permissions.delete && (\n                    <ActionBar.Action onClick={onDelete} label=\"Delete\">\n                      <Trash size={16} />\n                    </ActionBar.Action>\n                  )}\n                </CustomActionBar>\n              </div>\n            </div>\n            <div className={getClassName(\"overlayWrapper\")}>\n              <CustomOverlay\n                componentId={id}\n                componentType={componentType}\n                hover={hover}\n                isSelected={isSelected}\n              >\n                <div className={getClassName(\"overlay\")}></div>\n              </CustomOverlay>\n            </div>\n          </div>,\n          portalEl || document.body\n        )}\n      {children(refSetter)}\n    </DropZoneProvider>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/DraggableComponent/styles.css",
    "content": "/* Prevent user from interacting with underlying component */\n[data-puck-component] * {\n  pointer-events: none;\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n[data-puck-component] {\n  cursor: grab;\n  pointer-events: auto !important;\n  user-select: none;\n  -webkit-user-select: none;\n}\n\n[data-puck-dropzone] {\n  pointer-events: auto !important; /* Ensure DropZones still capture pointer events inside data-puck-components so elementsFromPoint triggers */\n}\n\n[data-puck-disabled] {\n  cursor: pointer;\n}\n\n/* Placeholder */\n[data-dnd-placeholder] {\n  background: var(--puck-color-azure-06) !important;\n  border: none !important;\n  color: #00000000 !important;\n  opacity: 0.3 !important;\n  outline: none !important;\n  transition: none !important;\n}\n\n[data-dnd-placeholder] *,\n[data-dnd-placeholder]::after,\n[data-dnd-placeholder]::before {\n  opacity: 0 !important;\n}\n\n[data-dnd-dragging][data-puck-component] {\n  pointer-events: none !important;\n  outline: 2px var(--puck-color-azure-09) solid !important;\n  outline-offset: -2px !important;\n}\n"
  },
  {
    "path": "packages/core/components/DraggableComponent/styles.module.css",
    "content": ".DraggableComponent {\n  position: absolute;\n  pointer-events: none;\n\n  --overlay-background: color-mix(\n    in srgb,\n    var(--puck-color-azure-08) 30%,\n    transparent\n  );\n}\n\n.DraggableComponent-overlayWrapper {\n  height: 100%;\n  width: 100%;\n  top: 0;\n  position: absolute;\n  pointer-events: none;\n  box-sizing: border-box;\n  z-index: 1;\n}\n\n.DraggableComponent-overlay {\n  cursor: pointer;\n  height: 100%;\n  outline: 2px var(--puck-color-azure-09) solid;\n  outline-offset: -2px;\n  width: 100%;\n}\n\n.DraggableComponent:focus-visible > .DraggableComponent-overlayWrapper {\n  outline: 1px solid var(--puck-color-azure-05);\n}\n\n.DraggableComponent-loadingOverlay {\n  background: var(--puck-color-white);\n  color: var(--puck-color-grey-03);\n  border-radius: 4px;\n  display: flex;\n  padding: 8px;\n  top: 8px;\n  right: 8px;\n  position: absolute;\n  z-index: 1;\n  pointer-events: all;\n  box-sizing: border-box;\n  opacity: 0.8;\n  z-index: 1;\n}\n\n.DraggableComponent--hover\n  > .DraggableComponent-overlayWrapper\n  > .DraggableComponent-overlay {\n  background: var(--overlay-background);\n  outline: 2px var(--puck-color-azure-09) solid;\n}\n\n.DraggableComponent--isSelected\n  > .DraggableComponent-overlayWrapper\n  > .DraggableComponent-overlay {\n  outline-color: var(--puck-color-azure-07);\n}\n\n/* Won't work in FF */\n.DraggableComponent:has(\n    .DraggableComponent--hover > .DraggableComponent-overlayWrapper\n  )\n  > .DraggableComponent-overlayWrapper {\n  display: none;\n}\n\n.DraggableComponent-actionsOverlay {\n  position: sticky;\n  opacity: 0;\n  pointer-events: none;\n  z-index: 2;\n}\n\n.DraggableComponent--isSelected .DraggableComponent-actionsOverlay {\n  opacity: 1;\n  pointer-events: auto;\n}\n\n.DraggableComponent-actions {\n  position: absolute;\n  width: auto;\n  cursor: grab;\n  display: flex;\n  box-sizing: border-box;\n  transform-origin: right top;\n  min-height: 36px;\n}\n"
  },
  {
    "path": "packages/core/components/Drawer/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { DragIcon } from \"../DragIcon\";\nimport { ReactElement, ReactNode, Ref, useMemo, useState } from \"react\";\nimport { generateId } from \"../../lib/generate-id\";\nimport { useDragListener } from \"../DragDropContext\";\nimport { useSafeId } from \"../../lib/use-safe-id\";\nimport { useDraggable, useDroppable } from \"@dnd-kit/react\";\n\nconst getClassName = getClassNameFactory(\"Drawer\", styles);\nconst getClassNameItem = getClassNameFactory(\"DrawerItem\", styles);\n\nexport const DrawerItemInner = ({\n  children,\n  name,\n  label,\n  dragRef,\n  isDragDisabled,\n}: {\n  children?: (props: { children: ReactNode; name: string }) => ReactElement;\n  name: string;\n  label?: string;\n  dragRef?: Ref<any>;\n  isDragDisabled?: boolean;\n}) => {\n  const CustomInner = useMemo(\n    () =>\n      children ||\n      (({ children }: { children: ReactNode; name: string }) => (\n        <div className={getClassNameItem(\"default\")}>{children}</div>\n      )),\n    [children]\n  );\n\n  return (\n    <div\n      className={getClassNameItem({ disabled: isDragDisabled })}\n      ref={dragRef}\n      onMouseDown={(e) => e.preventDefault()}\n      data-testid={dragRef ? `drawer-item:${name}` : \"\"}\n      data-puck-drawer-item\n    >\n      <CustomInner name={name}>\n        <div className={getClassNameItem(\"draggableWrapper\")}>\n          <div className={getClassNameItem(\"draggable\")}>\n            <div className={getClassNameItem(\"name\")}>{label ?? name}</div>\n            <div className={getClassNameItem(\"icon\")}>\n              <DragIcon />\n            </div>\n          </div>\n        </div>\n      </CustomInner>\n    </div>\n  );\n};\n\n/**\n * Wrap `useDraggable`, remounting it when the `id` changes.\n *\n * Could be removed by remounting `useDraggable` upstream in dndkit on `id` changes.\n */\nconst DrawerItemDraggable = ({\n  children,\n  name,\n  label,\n  id,\n  isDragDisabled,\n}: {\n  children?: (props: { children: ReactNode; name: string }) => ReactElement;\n  name: string;\n  label?: string;\n  id: string;\n  isDragDisabled?: boolean;\n}) => {\n  const { ref } = useDraggable({\n    id,\n    data: { componentType: name },\n    disabled: isDragDisabled,\n    type: \"drawer\",\n  });\n\n  return (\n    <div className={getClassName(\"draggable\")}>\n      <div className={getClassName(\"draggableBg\")}>\n        <DrawerItemInner name={name} label={label}>\n          {children}\n        </DrawerItemInner>\n      </div>\n      <div className={getClassName(\"draggableFg\")}>\n        <DrawerItemInner\n          name={name}\n          label={label}\n          dragRef={ref}\n          isDragDisabled={isDragDisabled}\n        >\n          {children}\n        </DrawerItemInner>\n      </div>\n    </div>\n  );\n};\n\nconst DrawerItem = ({\n  name,\n  children,\n  id,\n  label,\n  index,\n  isDragDisabled,\n}: {\n  name: string;\n  children?: (props: { children: ReactNode; name: string }) => ReactElement;\n  id?: string;\n  label?: string;\n  index?: number; // TODO deprecate\n  isDragDisabled?: boolean;\n}) => {\n  const resolvedId = id || name;\n  const [dynamicId, setDynamicId] = useState(generateId(resolvedId));\n\n  if (typeof index !== \"undefined\") {\n    console.error(\n      \"Warning: The `index` prop on Drawer.Item is deprecated and no longer required.\"\n    );\n  }\n\n  useDragListener(\n    \"dragend\",\n    () => {\n      setDynamicId(generateId(resolvedId));\n    },\n    [resolvedId]\n  );\n\n  return (\n    <div key={dynamicId}>\n      <DrawerItemDraggable\n        name={name}\n        label={label}\n        id={dynamicId}\n        isDragDisabled={isDragDisabled}\n      >\n        {children}\n      </DrawerItemDraggable>\n    </div>\n  );\n};\n\nexport const Drawer = ({\n  children,\n  droppableId,\n  direction,\n}: {\n  children: ReactNode;\n  droppableId?: string; // TODO deprecate\n  direction?: \"vertical\" | \"horizontal\"; // TODO deprecate\n}) => {\n  if (droppableId) {\n    console.error(\n      \"Warning: The `droppableId` prop on Drawer is deprecated and no longer required.\"\n    );\n  }\n\n  if (direction) {\n    console.error(\n      \"Warning: The `direction` prop on Drawer is deprecated and no longer required to achieve multi-directional dragging.\"\n    );\n  }\n\n  const id = useSafeId();\n\n  const { ref } = useDroppable({\n    id,\n    type: \"void\",\n    collisionPriority: 0, // Never collide with this, but we use it so NestedDroppablePlugin respects the Drawer\n  });\n\n  return (\n    <div\n      className={getClassName()}\n      ref={ref}\n      data-puck-dnd={id}\n      data-puck-drawer\n      data-puck-dnd-void\n    >\n      {children}\n    </div>\n  );\n};\n\nDrawer.Item = DrawerItem;\n"
  },
  {
    "path": "packages/core/components/Drawer/styles.module.css",
    "content": ".Drawer {\n  display: flex;\n  flex-direction: column;\n  font-family: var(--puck-font-family);\n  gap: 12px;\n}\n\n.Drawer-draggable {\n  position: relative;\n}\n\n.Drawer-draggableBg {\n  position: absolute;\n  top: 0;\n  right: 0;\n  bottom: 0;\n  left: 0;\n  pointer-events: none;\n  z-index: -1;\n}\n\n.DrawerItem-draggable {\n  background: var(--puck-color-white);\n  cursor: grab;\n  padding: 12px;\n  display: flex;\n  border: 1px var(--puck-color-grey-09) solid;\n  border-radius: 4px;\n  font-size: var(--puck-font-size-xxs);\n  justify-content: space-between;\n  align-items: center;\n  transition: background-color 50ms ease-in, color 50ms ease-in;\n}\n\n.DrawerItem--disabled .DrawerItem-draggable {\n  background: var(--puck-color-grey-11);\n  color: var(--puck-color-grey-05);\n  cursor: not-allowed; /** Move this out of inline styles */\n}\n\n.DrawerItem:focus-visible {\n  outline: 0;\n}\n\n.Drawer:not(.Drawer--isDraggingFrom)\n  .DrawerItem:focus-visible\n  .DrawerItem-draggable {\n  border-radius: 4px;\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Drawer:not(.Drawer--isDraggingFrom)\n    .DrawerItem:not(.DrawerItem--disabled)\n    .DrawerItem-draggable:hover {\n    background-color: var(--puck-color-azure-12);\n    color: var(--puck-color-azure-04);\n    transition: none;\n  }\n}\n\n.DrawerItem-name {\n  overflow-x: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n"
  },
  {
    "path": "packages/core/components/DropZone/context.tsx",
    "content": "import {\n  PropsWithChildren,\n  ReactNode,\n  createContext,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\nimport type { Draggable } from \"@dnd-kit/dom\";\nimport { useAppStore } from \"../../store\";\nimport { createStore, StoreApi } from \"zustand\";\n\nexport type PathData = Record<string, { path: string[]; label: string }>;\n\nexport type DropZoneContext = {\n  areaId?: string;\n  zoneCompound?: string;\n  index?: number;\n  registerZone?: (zoneCompound: string) => void;\n  unregisterZone?: (zoneCompound: string) => void;\n  mode?: \"edit\" | \"render\";\n  depth: number;\n  registerLocalZone?: (zone: string, active: boolean) => void; // A zone as it pertains to the current area\n  unregisterLocalZone?: (zone: string) => void;\n} | null;\n\nexport const dropZoneContext = createContext<DropZoneContext>(null);\n\nexport type Preview = {\n  componentType: string;\n  index: number;\n  zone: string;\n  props: Record<string, any>;\n  type: \"insert\" | \"move\";\n  element: Element | undefined;\n} | null;\n\nexport type ZoneStore = {\n  zoneDepthIndex: Record<string, boolean>;\n  areaDepthIndex: Record<string, boolean>;\n  nextZoneDepthIndex: Record<string, boolean>;\n  nextAreaDepthIndex: Record<string, boolean>;\n  enabledIndex: Record<string, boolean>;\n  previewIndex: Record<string, Preview>;\n  draggedItem?: Draggable | null;\n  hoveringComponent: string | null;\n};\n\nexport const ZoneStoreContext = createContext<StoreApi<ZoneStore>>(\n  createStore(() => ({\n    zoneDepthIndex: {},\n    nextZoneDepthIndex: {},\n    areaDepthIndex: {},\n    nextAreaDepthIndex: {},\n    draggedItem: null,\n    previewIndex: {},\n    enabledIndex: {},\n    hoveringComponent: null,\n  }))\n);\n\nexport const ZoneStoreProvider = ({\n  children,\n  store,\n}: PropsWithChildren<{ store: StoreApi<ZoneStore> }>) => {\n  return (\n    <ZoneStoreContext.Provider value={store}>\n      {children}\n    </ZoneStoreContext.Provider>\n  );\n};\n\nexport const DropZoneProvider = ({\n  children,\n  value,\n}: {\n  children: ReactNode;\n  value: DropZoneContext;\n}) => {\n  const dispatch = useAppStore((s) => s.dispatch);\n\n  const registerZone = useCallback(\n    (zoneCompound: string) => {\n      dispatch({\n        type: \"registerZone\",\n        zone: zoneCompound,\n      });\n    },\n    [dispatch]\n  );\n\n  const memoValue = useMemo(\n    () =>\n      ({\n        registerZone,\n        ...value,\n      } as DropZoneContext),\n    [value]\n  );\n\n  return (\n    <>\n      {memoValue && (\n        <dropZoneContext.Provider value={memoValue}>\n          {children}\n        </dropZoneContext.Provider>\n      )}\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/DropZone/index.tsx",
    "content": "import {\n  CSSProperties,\n  forwardRef,\n  memo,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n} from \"react\";\nimport { DraggableComponent } from \"../DraggableComponent\";\nimport { setupZone } from \"../../lib/data/setup-zone\";\nimport { rootDroppableId } from \"../../lib/root-droppable-id\";\nimport { getClassNameFactory } from \"../../lib\";\nimport styles from \"./styles.module.css\";\nimport {\n  DropZoneContext,\n  DropZoneProvider,\n  ZoneStoreContext,\n  dropZoneContext,\n} from \"./context\";\nimport { useAppStore, useAppStoreApi } from \"../../store\";\nimport { DropZoneProps } from \"./types\";\nimport {\n  ComponentData,\n  Config,\n  DragAxis,\n  Fields,\n  Metadata,\n  Overrides,\n  PuckContext,\n  WithPuckProps,\n} from \"../../types\";\n\nimport { useDroppable, UseDroppableInput } from \"@dnd-kit/react\";\nimport { DrawerItemInner } from \"../Drawer\";\nimport { pointerIntersection } from \"@dnd-kit/collision\";\nimport { UniqueIdentifier } from \"@dnd-kit/abstract\";\nimport { useMinEmptyHeight } from \"./lib/use-min-empty-height\";\nimport { assignRefs } from \"../../lib/assign-refs\";\nimport { useContentIdsWithPreview } from \"./lib/use-content-with-preview\";\nimport { useDragAxis } from \"./lib/use-drag-axis\";\nimport { useContextStore } from \"../../lib/use-context-store\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { renderContext } from \"../Render\";\nimport { useSlots } from \"../../lib/use-slots\";\nimport { ContextSlotRender, SlotRenderPure } from \"../SlotRender\";\nimport { expandNode } from \"../../lib/data/flatten-node\";\nimport { useFieldTransformsTracked } from \"../../lib/field-transforms/use-field-transforms-tracked\";\nimport { getInlineTextTransform } from \"../../lib/field-transforms/default-transforms/inline-text-transform\";\nimport { getSlotTransform } from \"../../lib/field-transforms/default-transforms/slot-transform\";\nimport { getRichTextTransform } from \"../../lib/field-transforms/default-transforms/rich-text-transform\";\nimport { FieldTransforms } from \"../../types/API/FieldTransforms\";\nimport { useRichtextProps } from \"../RichTextEditor/lib/use-richtext-props\";\nimport { MemoizeComponent } from \"../MemoizeComponent\";\n\nconst getClassName = getClassNameFactory(\"DropZone\", styles);\n\nexport { DropZoneProvider, dropZoneContext } from \"./context\";\n\nconst getRandomColor = () =>\n  `#${Math.floor(Math.random() * 16777215).toString(16)}`;\n\nconst RENDER_DEBUG = false;\n\nexport type DropZoneDndData = {\n  areaId?: string;\n  depth: number;\n  path: UniqueIdentifier[];\n  isDroppableTarget: boolean;\n};\n\nconst InsertPreview = ({\n  element,\n  label,\n  override,\n}: {\n  element?: Element;\n  label: string;\n  override?: Overrides[\"drawerItem\"];\n}) => {\n  if (element) {\n    return (\n      // Safe to use this since the HTML is set by the user\n      <div dangerouslySetInnerHTML={{ __html: element.outerHTML }} />\n    );\n  }\n\n  return <DrawerItemInner name={label}>{override}</DrawerItemInner>;\n};\n\nexport const DropZoneEditPure = (props: DropZoneProps) => (\n  <DropZoneEdit {...props} />\n);\n\nconst DropZoneChild = ({\n  zoneCompound,\n  componentId,\n  index,\n  dragAxis,\n  collisionAxis,\n  inDroppableZone,\n}: {\n  zoneCompound: string;\n  componentId: string;\n  index: number;\n  dragAxis: DragAxis;\n  collisionAxis?: DragAxis;\n  inDroppableZone: boolean;\n}) => {\n  const metadata = useAppStore((s) => s.metadata);\n\n  const ctx = useContext(dropZoneContext);\n  const { depth = 1 } = ctx ?? {};\n\n  const zoneStore = useContext(ZoneStoreContext);\n\n  const nodeProps = useAppStore(\n    useShallow((s) => {\n      return s.state.indexes.nodes[componentId]?.flatData.props;\n    })\n  );\n\n  const nodeType = useAppStore(\n    (s) => s.state.indexes.nodes[componentId]?.data.type\n  );\n\n  const nodeReadOnly = useAppStore(\n    useShallow((s) => s.state.indexes.nodes[componentId]?.data.readOnly)\n  );\n\n  const appStore = useAppStoreApi();\n\n  const item = useMemo(() => {\n    if (nodeProps) {\n      const expanded = expandNode({\n        type: nodeType,\n        props: nodeProps,\n      }) as ComponentData;\n\n      return expanded;\n    }\n\n    const preview = zoneStore.getState().previewIndex[zoneCompound];\n\n    if (componentId === preview?.props.id) {\n      return {\n        type: preview.componentType,\n        props: preview.props,\n        previewType: preview.type,\n        element: preview.element,\n      };\n    }\n\n    return null;\n  }, [appStore, componentId, zoneCompound, nodeType, nodeProps]);\n\n  const componentConfig = useAppStore((s) =>\n    item?.type ? s.config.components[item.type] : null\n  );\n\n  const puckProps: PuckContext = useMemo(\n    () => ({\n      renderDropZone: DropZoneEditPure,\n      isEditing: true,\n      dragRef: null,\n      metadata: { ...metadata, ...componentConfig?.metadata },\n    }),\n    [metadata, componentConfig?.metadata]\n  );\n\n  const overrides = useAppStore((s) => s.overrides);\n  const isLoading = useAppStore(\n    (s) => s.componentState[componentId]?.loadingCount > 0\n  );\n  const isSelected = useAppStore(\n    (s) => s.selectedItem?.props.id === componentId || false\n  );\n\n  let label = componentConfig?.label ?? item?.type.toString() ?? \"Component\";\n\n  const defaultsProps = useMemo(\n    () => ({\n      ...componentConfig?.defaultProps,\n      ...item?.props,\n      puck: puckProps,\n      editMode: true, // DEPRECATED\n    }),\n    [componentConfig?.defaultProps, item?.props, puckProps]\n  );\n\n  const defaultedNode = useMemo(\n    () => ({ type: item?.type ?? nodeType, props: defaultsProps }),\n    [item?.type, nodeType, defaultsProps]\n  );\n\n  const config = useAppStore((s) => s.config);\n\n  const plugins = useAppStore((s) => s.plugins);\n  const userFieldTransforms = useAppStore((s) => s.fieldTransforms);\n  const combinedFieldTransforms = useMemo(\n    () => ({\n      ...getSlotTransform(DropZoneEditPure, (slotProps) => (\n        <ContextSlotRender componentId={componentId} zone={slotProps.zone} />\n      )),\n      ...getInlineTextTransform(),\n      ...getRichTextTransform(),\n      ...plugins.reduce<FieldTransforms>(\n        (acc, plugin) => ({ ...acc, ...plugin.fieldTransforms }),\n        {}\n      ),\n      ...userFieldTransforms,\n    }),\n    [plugins, userFieldTransforms]\n  );\n\n  const transformedProps = useFieldTransformsTracked(\n    config,\n    defaultedNode,\n    combinedFieldTransforms,\n    nodeReadOnly,\n    isLoading\n  );\n\n  if (!item) return;\n\n  const Render = componentConfig\n    ? componentConfig.render\n    : () => (\n        <div style={{ padding: 48, textAlign: \"center\" }}>\n          No configuration for {item.type}\n        </div>\n      );\n\n  let componentType = item.type as string;\n\n  const isInserting =\n    \"previewType\" in item ? item.previewType === \"insert\" : false;\n\n  return (\n    <DraggableComponent\n      id={componentId}\n      componentType={componentType}\n      zoneCompound={zoneCompound}\n      depth={depth + 1}\n      index={index}\n      isLoading={isLoading}\n      isSelected={isSelected}\n      label={label}\n      autoDragAxis={dragAxis}\n      userDragAxis={collisionAxis}\n      inDroppableZone={inDroppableZone}\n    >\n      {(dragRef) => {\n        if (componentConfig?.inline && !isInserting) {\n          return (\n            <MemoizeComponent\n              Component={Render}\n              componentProps={{\n                ...transformedProps,\n                puck: { ...transformedProps.puck, dragRef },\n              }}\n            />\n          );\n        }\n\n        return (\n          <div ref={dragRef}>\n            {isInserting ? (\n              <InsertPreview\n                label={label}\n                override={overrides.componentItem ?? overrides.drawerItem}\n                element={\n                  \"element\" in item && item.element ? item.element : undefined\n                }\n              />\n            ) : (\n              <MemoizeComponent\n                Component={Render}\n                componentProps={transformedProps}\n              />\n            )}\n          </div>\n        );\n      }}\n    </DraggableComponent>\n  );\n};\n\nconst DropZoneChildMemo = memo(DropZoneChild);\n\nexport const DropZoneEdit = forwardRef<HTMLDivElement, DropZoneProps>(\n  function DropZoneEditInternal(\n    {\n      zone,\n      allow,\n      disallow,\n      style,\n      className,\n      minEmptyHeight: userMinEmptyHeight = \"128px\",\n      collisionAxis,\n      as,\n    },\n    userRef\n  ) {\n    const ctx = useContext(dropZoneContext);\n    const appStoreApi = useAppStoreApi();\n\n    const {\n      // These all need setting via context\n      areaId,\n      depth = 0,\n      registerLocalZone,\n      unregisterLocalZone,\n    } = ctx ?? {};\n\n    const path = useAppStore(\n      useShallow((s) => (areaId ? s.state.indexes.nodes[areaId]?.path : null))\n    );\n\n    let zoneCompound = rootDroppableId;\n\n    if (areaId) {\n      if (zone !== rootDroppableId) {\n        zoneCompound = `${areaId}:${zone}`;\n      }\n    }\n\n    const isRootZone =\n      zoneCompound === rootDroppableId ||\n      zone === rootDroppableId ||\n      areaId === \"root\";\n\n    const inNextDeepestArea = useContextStore(\n      ZoneStoreContext,\n      (s) => s.nextAreaDepthIndex[areaId || \"\"]\n    );\n\n    const zoneContentIds = useAppStore(\n      useShallow((s) => {\n        return s.state.indexes.zones[zoneCompound]?.contentIds;\n      })\n    );\n    const zoneType = useAppStore(\n      useShallow((s) => {\n        return s.state.indexes.zones[zoneCompound]?.type;\n      })\n    );\n\n    // Register zone on mount\n    useEffect(() => {\n      if (!zoneType || zoneType === \"dropzone\") {\n        if (ctx?.registerZone) {\n          ctx?.registerZone(zoneCompound);\n        }\n      }\n    }, [zoneType, appStoreApi]);\n\n    useEffect(() => {\n      if (zoneType === \"dropzone\") {\n        if (zoneCompound !== rootDroppableId) {\n          console.warn(\n            \"DropZones have been deprecated in favor of slot fields and will be removed in a future version of Puck. Please see the migration guide: https://www.puckeditor.com/docs/guides/migrations/dropzones-to-slots\"\n          );\n        }\n      }\n    }, [zoneType]);\n\n    const contentIds = useMemo(() => {\n      return zoneContentIds || [];\n    }, [zoneContentIds]);\n\n    const ref = useRef<HTMLDivElement | null>(null);\n\n    const acceptsTarget = useCallback(\n      (componentType: string | null | undefined) => {\n        if (!componentType) {\n          return true;\n        }\n\n        if (disallow) {\n          const defaultedAllow = allow || [];\n\n          // remove any explicitly allowed items from disallow\n          const filteredDisallow = (disallow || []).filter(\n            (item) => defaultedAllow.indexOf(item) === -1\n          );\n\n          if (filteredDisallow.indexOf(componentType) !== -1) {\n            return false;\n          }\n        } else if (allow) {\n          if (allow.indexOf(componentType) === -1) {\n            return false;\n          }\n        }\n\n        return true;\n      },\n      [allow, disallow]\n    );\n\n    const targetAccepted = useContextStore(ZoneStoreContext, (s) => {\n      const draggedComponentType = s.draggedItem?.data.componentType;\n      return acceptsTarget(draggedComponentType);\n    });\n\n    const hoveringOverArea = inNextDeepestArea || isRootZone;\n\n    const isEnabled = useContextStore(ZoneStoreContext, (s) => {\n      let _isEnabled = true;\n      const isDeepestZone = s.zoneDepthIndex[zoneCompound] ?? false;\n\n      _isEnabled = isDeepestZone;\n\n      if (_isEnabled) {\n        _isEnabled = targetAccepted;\n      }\n\n      return _isEnabled;\n    });\n\n    useEffect(() => {\n      if (registerLocalZone) {\n        registerLocalZone(zoneCompound, targetAccepted || isEnabled);\n      }\n\n      return () => {\n        if (unregisterLocalZone) {\n          unregisterLocalZone(zoneCompound);\n        }\n      };\n    }, [targetAccepted, isEnabled, zoneCompound]);\n\n    const [contentIdsWithPreview, preview] = useContentIdsWithPreview(\n      contentIds,\n      zoneCompound\n    );\n\n    const isDropEnabled =\n      isEnabled &&\n      (preview\n        ? contentIdsWithPreview.length === 1\n        : contentIdsWithPreview.length === 0);\n\n    const zoneStore = useContext(ZoneStoreContext);\n\n    useEffect(() => {\n      const { enabledIndex } = zoneStore.getState();\n      zoneStore.setState({\n        enabledIndex: { ...enabledIndex, [zoneCompound]: isEnabled },\n      });\n    }, [isEnabled, zoneStore, zoneCompound]);\n\n    const droppableConfig: UseDroppableInput<DropZoneDndData> = {\n      id: zoneCompound,\n      collisionPriority: isEnabled ? depth : 0,\n      disabled: !isDropEnabled,\n      collisionDetector: pointerIntersection,\n      type: \"dropzone\",\n      data: {\n        areaId,\n        depth,\n        isDroppableTarget: targetAccepted,\n        path: path || [],\n      },\n    };\n\n    const { ref: dropRef } = useDroppable(droppableConfig);\n\n    const isAreaSelected = useAppStore(\n      (s) => s?.selectedItem && areaId === s?.selectedItem.props.id\n    );\n\n    const [dragAxis] = useDragAxis(ref, collisionAxis);\n\n    const [minEmptyHeight, isAnimating] = useMinEmptyHeight({\n      zoneCompound,\n      userMinEmptyHeight,\n      ref,\n    });\n\n    const setRefs = useCallback(\n      (node: any) => {\n        assignRefs<any>([ref, dropRef, userRef], node);\n      },\n      [dropRef]\n    );\n\n    const El = as ?? \"div\";\n\n    return (\n      <El\n        className={`${getClassName({\n          isRootZone,\n          hoveringOverArea,\n          isEnabled,\n          isAreaSelected,\n          hasChildren: contentIds.length > 0,\n          isAnimating,\n        })}${className ? ` ${className}` : \"\"}`}\n        ref={setRefs}\n        data-testid={`dropzone:${zoneCompound}`}\n        data-puck-dropzone={zoneCompound}\n        style={\n          {\n            ...style,\n            \"--min-empty-height\": minEmptyHeight,\n            backgroundColor: RENDER_DEBUG\n              ? getRandomColor()\n              : style?.backgroundColor,\n          } as CSSProperties\n        }\n      >\n        {contentIdsWithPreview.map((componentId, i) => {\n          return (\n            <DropZoneChildMemo\n              key={componentId}\n              zoneCompound={zoneCompound}\n              componentId={componentId}\n              dragAxis={dragAxis}\n              index={i}\n              collisionAxis={collisionAxis}\n              inDroppableZone={targetAccepted}\n            />\n          );\n        })}\n      </El>\n    );\n  }\n);\n\nconst DropZoneRenderItem = ({\n  config,\n  item,\n  metadata,\n}: {\n  config: Config;\n  item: ComponentData;\n  metadata: Metadata;\n}) => {\n  const Component = config.components[item.type];\n\n  const props = useSlots(config, item, (slotProps) => (\n    <SlotRenderPure {...slotProps} config={config} metadata={metadata} />\n  )) as WithPuckProps<ComponentData[\"props\"]>;\n\n  const nextContextValue = useMemo<DropZoneContext>(\n    () => ({\n      areaId: props.id,\n      depth: 1,\n    }),\n    [props]\n  );\n\n  const richtextProps = useRichtextProps(Component.fields, props);\n\n  return (\n    <DropZoneProvider key={props.id} value={nextContextValue}>\n      <Component.render\n        {...props}\n        {...richtextProps}\n        puck={{\n          ...props.puck,\n          renderDropZone: DropZoneRenderPure,\n          metadata: { ...metadata, ...Component.metadata },\n        }}\n      />\n    </DropZoneProvider>\n  );\n};\n\nexport const DropZoneRenderPure = (props: DropZoneProps) => (\n  <DropZoneRender {...props} />\n);\n\nconst DropZoneRender = forwardRef<HTMLDivElement, DropZoneProps>(\n  function DropZoneRenderInternal({ className, style, zone, as }, ref) {\n    const ctx = useContext(dropZoneContext);\n    const { areaId = \"root\" } = ctx || {};\n    const { config, data, metadata } = useContext(renderContext);\n\n    let zoneCompound = `${areaId}:${zone}`;\n    let content = data?.content || [];\n\n    // Register zones if running Render mode inside editor (i.e. previewMode === \"interactive\")\n    useEffect(() => {\n      // Only register zones, not slots\n      if (!content) {\n        if (ctx?.registerZone) {\n          ctx?.registerZone(zoneCompound);\n        }\n      }\n    }, [content]);\n\n    const El = as ?? \"div\";\n\n    if (!data || !config) {\n      return null;\n    }\n\n    if (zoneCompound !== rootDroppableId) {\n      content = setupZone(data, zoneCompound).zones[zoneCompound];\n    }\n    return (\n      <El className={className} style={style} ref={ref}>\n        {content.map((item) => {\n          const Component = config.components[item.type];\n          if (Component) {\n            return (\n              <DropZoneRenderItem\n                key={item.props.id}\n                config={config}\n                item={item}\n                metadata={metadata}\n              />\n            );\n          }\n\n          return null;\n        })}\n      </El>\n    );\n  }\n);\n\nexport const DropZonePure = (props: DropZoneProps) => <DropZone {...props} />;\n\nexport const DropZone = forwardRef<HTMLDivElement, DropZoneProps>(\n  function DropZone(props: DropZoneProps, ref) {\n    const ctx = useContext(dropZoneContext);\n\n    if (ctx?.mode === \"edit\") {\n      return (\n        <>\n          <DropZoneEdit {...props} ref={ref} />\n        </>\n      );\n    }\n\n    return (\n      <>\n        <DropZoneRender {...props} ref={ref} />\n      </>\n    );\n  }\n);\n"
  },
  {
    "path": "packages/core/components/DropZone/lib/use-content-with-preview.ts",
    "content": "import { Preview } from \"./../context\";\nimport { useContext, useEffect, useState } from \"react\";\nimport { useRenderedCallback } from \"../../../lib/dnd/use-rendered-callback\";\nimport { insert } from \"../../../lib/data/insert\";\nimport { ZoneStoreContext } from \"../context\";\nimport { useContextStore } from \"../../../lib/use-context-store\";\nimport { useAppStore } from \"../../../store\";\n\nexport const useContentIdsWithPreview = (\n  contentIds: string[],\n  zoneCompound: string\n): [string[], Preview | undefined] => {\n  const zoneStore = useContext(ZoneStoreContext);\n  const preview = useContextStore(\n    ZoneStoreContext,\n    (s) => s.previewIndex[zoneCompound]\n  );\n\n  const isDragging = useAppStore((s) => s.state.ui.isDragging);\n\n  const [contentIdsWithPreview, setContentIdsWithPreview] =\n    useState(contentIds);\n  const [localPreview, setLocalPreview] = useState<Preview | undefined>(\n    preview\n  );\n\n  const updateContent = useRenderedCallback(\n    (\n      contentIds: string[],\n      preview: Preview | undefined,\n      isDragging: boolean,\n      draggedItemId?: string,\n      previewExists?: boolean\n    ) => {\n      // Preview is cleared but context hasn't yet caught up\n      // This is necessary because Zustand clears the preview before the dispatcher finishes\n      if (isDragging && !previewExists) {\n        return;\n      }\n\n      if (preview) {\n        if (preview.type === \"insert\") {\n          setContentIdsWithPreview(\n            insert(\n              contentIds.filter((id) => id !== preview.props.id),\n              preview.index,\n              preview.props.id\n            )\n          );\n        } else {\n          setContentIdsWithPreview(\n            insert(\n              contentIds.filter((id) => id !== preview.props.id),\n              preview.index,\n              preview.props.id\n            )\n          );\n        }\n      } else {\n        setContentIdsWithPreview(\n          previewExists\n            ? contentIds.filter((id) => id !== draggedItemId)\n            : contentIds\n        );\n      }\n\n      setLocalPreview(preview);\n    },\n    []\n  );\n\n  useEffect(() => {\n    // We MUST explicitly pass these in, otherwise mobile dragging fails\n    // due to hard-to-debug rendering race conditions. This must happen\n    // within this callback (after preview has updated), and not inside\n    // the renderedCallback.\n    const s = zoneStore.getState();\n    const draggedItemId = s.draggedItem?.id;\n    const previewExists = Object.keys(s.previewIndex || {}).length > 0;\n\n    updateContent(\n      contentIds,\n      preview,\n      isDragging,\n      draggedItemId,\n      previewExists\n    );\n  }, [contentIds, preview, isDragging]);\n\n  return [contentIdsWithPreview, localPreview];\n};\n"
  },
  {
    "path": "packages/core/components/DropZone/lib/use-drag-axis.ts",
    "content": "import { RefObject, useCallback, useEffect, useState } from \"react\";\nimport { DragAxis } from \"../../../types\";\nimport { useAppStore } from \"../../../store\";\n\nconst GRID_DRAG_AXIS: DragAxis = \"dynamic\";\nconst FLEX_ROW_DRAG_AXIS: DragAxis = \"x\";\nconst DEFAULT_DRAG_AXIS: DragAxis = \"y\";\n\nexport const useDragAxis = (\n  ref: RefObject<HTMLElement | null>,\n  collisionAxis?: DragAxis\n): [DragAxis, () => void] => {\n  const status = useAppStore((s) => s.status);\n\n  const [dragAxis, setDragAxis] = useState<DragAxis>(\n    collisionAxis || DEFAULT_DRAG_AXIS\n  );\n\n  const calculateDragAxis = useCallback(() => {\n    if (ref.current) {\n      const computedStyle = window.getComputedStyle(ref.current);\n\n      if (computedStyle.display === \"grid\") {\n        setDragAxis(GRID_DRAG_AXIS);\n      } else if (\n        computedStyle.display === \"flex\" &&\n        computedStyle.flexDirection === \"row\"\n      ) {\n        setDragAxis(FLEX_ROW_DRAG_AXIS);\n      } else {\n        setDragAxis(DEFAULT_DRAG_AXIS);\n      }\n    }\n  }, [ref.current]);\n\n  useEffect(() => {\n    const onViewportChange = () => {\n      calculateDragAxis();\n    };\n\n    window.addEventListener(\"viewportchange\", onViewportChange);\n\n    return () => {\n      window.removeEventListener(\"viewportchange\", onViewportChange);\n    };\n  }, []);\n\n  useEffect(calculateDragAxis, [status, collisionAxis]);\n\n  return [dragAxis, calculateDragAxis];\n};\n"
  },
  {
    "path": "packages/core/components/DropZone/lib/use-min-empty-height.ts",
    "content": "import { CSSProperties, RefObject, useEffect, useRef, useState } from \"react\";\nimport { ZoneStoreContext } from \"./../context\";\nimport { useContextStore } from \"../../../lib/use-context-store\";\nimport { AppStoreApi, useAppStoreApi } from \"../../../store\";\nimport { useOnDragFinished } from \"../../../lib/dnd/use-on-drag-finished\";\n\nconst getNumItems = (appStore: AppStoreApi, zoneCompound: string) =>\n  appStore.getState().state.indexes.zones[zoneCompound].contentIds.length;\n\nexport const useMinEmptyHeight = ({\n  zoneCompound,\n  userMinEmptyHeight,\n  ref,\n}: {\n  zoneCompound: string;\n  userMinEmptyHeight: CSSProperties[\"minHeight\"] | number;\n  ref: RefObject<HTMLDivElement | null>;\n}) => {\n  const appStore = useAppStoreApi();\n  const [prevHeight, setPrevHeight] = useState(0);\n  const [isAnimating, setIsAnimating] = useState(false);\n  const { draggedItem, isZone } = useContextStore(ZoneStoreContext, (s) => {\n    return {\n      draggedItem:\n        s.draggedItem?.data.zone === zoneCompound ? s.draggedItem : null,\n      isZone: s.draggedItem?.data.zone === zoneCompound,\n    };\n  });\n\n  const numItems = useRef(0);\n\n  const onDragFinished = useOnDragFinished(\n    (finished) => {\n      if (finished) {\n        const newNumItems = getNumItems(appStore, zoneCompound);\n\n        setPrevHeight(0);\n\n        if (newNumItems || numItems.current === 0) {\n          setIsAnimating(false);\n\n          return;\n        }\n\n        const selectedItem = appStore.getState().selectedItem;\n        const zones = appStore.getState().state.indexes.zones;\n        const nodes = appStore.getState().nodes;\n\n        nodes.nodes[selectedItem?.props.id]?.methods.hideOverlay();\n\n        setTimeout(() => {\n          const contentIds = zones[zoneCompound]?.contentIds || [];\n\n          contentIds.forEach((contentId) => {\n            const node = nodes.nodes[contentId];\n            node?.methods.sync();\n          });\n\n          if (selectedItem) {\n            setTimeout(() => {\n              nodes.nodes[selectedItem.props.id]?.methods.sync();\n              nodes.nodes[selectedItem.props.id]?.methods.showOverlay();\n            }, 200);\n          }\n\n          setIsAnimating(false);\n        }, 100);\n      }\n    },\n    [appStore, prevHeight, zoneCompound]\n  );\n\n  useEffect(() => {\n    if (draggedItem && ref.current) {\n      if (isZone) {\n        const rect = ref.current.getBoundingClientRect();\n\n        numItems.current = getNumItems(appStore, zoneCompound);\n\n        setPrevHeight(rect.height);\n        setIsAnimating(true);\n\n        return onDragFinished();\n      }\n    }\n  }, [ref.current, draggedItem, onDragFinished]);\n\n  const returnedMinHeight = isNaN(Number(userMinEmptyHeight))\n    ? userMinEmptyHeight\n    : `${userMinEmptyHeight}px`;\n\n  return [prevHeight ? `${prevHeight}px` : returnedMinHeight, isAnimating];\n};\n"
  },
  {
    "path": "packages/core/components/DropZone/styles.module.css",
    "content": ".DropZone {\n  --resize-animation-ms: 150ms;\n\n  position: relative;\n  height: 100%;\n  min-height: var(--min-empty-height);\n  outline-offset: -2px;\n  width: 100%;\n}\n\n.DropZone--hasChildren {\n  min-height: 0;\n}\n\n.DropZone:empty {\n  min-height: var(--min-empty-height);\n}\n\n/* We use global data-puck-dragging to avoid re-rendering DropZone */\n[data-puck-entry]:not([data-puck-dragging]) .DropZone {\n  transition: min-height var(--resize-animation-ms) ease-in;\n}\n\n.DropZone--isAreaSelected,\n.DropZone--hoveringOverArea:not(.DropZone--isRootZone) {\n  background: color-mix(in srgb, var(--puck-color-azure-09) 30%, transparent);\n  outline: 2px dashed var(--puck-color-azure-08);\n}\n\n.DropZone:empty {\n  background: color-mix(in srgb, var(--puck-color-azure-09) 30%, transparent);\n  outline: 2px dashed var(--puck-color-azure-08);\n}\n\n.DropZone--isDestination {\n  outline: 2px dashed var(--puck-color-azure-04) !important;\n}\n\n.DropZone--isDestination:not(.DropZone--isRootZone) {\n  background: color-mix(\n    in srgb,\n    var(--puck-color-azure-09) 30%,\n    transparent\n  ) !important;\n}\n\n.DropZone-item {\n  position: relative;\n}\n\n.DropZone-hitbox {\n  position: absolute;\n  bottom: -12px;\n  height: 24px;\n  width: 100%;\n  z-index: 1;\n}\n\n[data-puck-dragging] .DropZone--isEnabled {\n  outline: 2px dashed var(--puck-color-azure-06);\n}\n\n.DropZone > *:not([data-puck-component]) {\n  opacity: 0;\n}\n\n/* Hide overlays if DropZone is animating, which happens during a resize */\nbody:has(.DropZone--isAnimating:empty) [data-puck-overlay] {\n  opacity: 0 !important;\n}\n"
  },
  {
    "path": "packages/core/components/DropZone/types.ts",
    "content": "import { CSSProperties, ElementType, Ref } from \"react\";\nimport { DragAxis } from \"../../types\";\n\nexport type DropZoneProps = {\n  zone: string;\n  allow?: string[];\n  disallow?: string[];\n  style?: CSSProperties;\n  minEmptyHeight?: CSSProperties[\"minHeight\"] | number;\n  className?: string;\n  collisionAxis?: DragAxis;\n  as?: ElementType;\n  ref?: Ref<any>;\n};\n"
  },
  {
    "path": "packages/core/components/ExternalInput/index.tsx",
    "content": "import {\n  useMemo,\n  useEffect,\n  useState,\n  useCallback,\n  isValidElement,\n} from \"react\";\nimport styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { ExternalField } from \"../../types\";\nimport { Link, Search, SlidersHorizontal, Unlock } from \"lucide-react\";\nimport { Modal } from \"../Modal\";\nimport { Heading } from \"../Heading\";\nimport { Loader } from \"../Loader\";\nimport { Button } from \"../Button\";\nimport { AutoField, AutoFieldPrivate, FieldLabel } from \"../AutoField\";\nimport { IconButton } from \"../IconButton\";\n\nconst getClassName = getClassNameFactory(\"ExternalInput\", styles);\nconst getClassNameModal = getClassNameFactory(\"ExternalInputModal\", styles);\n\nconst dataCache: Record<string, any> = {};\n\nexport const ExternalInput = ({\n  field,\n  onChange,\n  value = null,\n  name,\n  id,\n  readOnly,\n}: {\n  field: ExternalField;\n  onChange: (value: any) => void;\n  value: any;\n  name?: string;\n  id: string;\n  readOnly?: boolean;\n}) => {\n  const {\n    mapProp = (val: any) => val,\n    mapRow = (val: any) => val,\n    filterFields,\n  } = field || {};\n  const { enabled: shouldCacheData } = field.cache ?? { enabled: true };\n\n  const [data, setData] = useState<Record<string, any>[]>([]);\n  const [isOpen, setOpen] = useState(false);\n  const [isLoading, setIsLoading] = useState(true);\n\n  const hasFilterFields = !!filterFields;\n\n  const [filters, setFilters] = useState(field.initialFilters || {});\n  const [filtersToggled, setFiltersToggled] = useState(hasFilterFields);\n\n  const mappedData = useMemo(() => {\n    return data.map(mapRow);\n  }, [data]);\n\n  const keys = useMemo(() => {\n    const validKeys: Set<string> = new Set();\n\n    for (const item of mappedData) {\n      for (const key of Object.keys(item)) {\n        if (\n          typeof item[key] === \"string\" ||\n          typeof item[key] === \"number\" ||\n          isValidElement(item[key])\n        ) {\n          validKeys.add(key);\n        }\n      }\n    }\n\n    return Array.from(validKeys);\n  }, [mappedData]);\n\n  const [searchQuery, setSearchQuery] = useState(field.initialQuery || \"\");\n\n  const search = useCallback(\n    async (query: string, filters: object) => {\n      setIsLoading(true);\n\n      const cacheKey = `${id}-${query}-${JSON.stringify(filters)}`;\n\n      let listData;\n\n      if (shouldCacheData && dataCache[cacheKey]) {\n        listData = dataCache[cacheKey];\n      } else {\n        listData = await field.fetchList({ query, filters });\n      }\n\n      if (listData) {\n        setData(listData);\n        setIsLoading(false);\n\n        if (shouldCacheData) {\n          dataCache[cacheKey] = listData;\n        }\n      }\n    },\n    [id, field]\n  );\n\n  const Footer = useCallback(\n    (props: { items: any[] }) =>\n      field.renderFooter ? (\n        field.renderFooter(props)\n      ) : (\n        <span className={getClassNameModal(\"footer\")}>\n          {props.items.length} result{props.items.length === 1 ? \"\" : \"s\"}\n        </span>\n      ),\n    [field.renderFooter]\n  );\n\n  useEffect(() => {\n    search(searchQuery, filters);\n  }, []);\n\n  return (\n    <div\n      className={getClassName({\n        dataSelected: !!value,\n        modalVisible: isOpen,\n        readOnly,\n      })}\n      id={id}\n    >\n      <div className={getClassName(\"actions\")}>\n        <button\n          type=\"button\"\n          onClick={() => setOpen(true)}\n          className={getClassName(\"button\")}\n          disabled={readOnly}\n        >\n          {/* NB this is hardcoded to strapi for now */}\n          {value ? (\n            field.getItemSummary ? (\n              field.getItemSummary(value)\n            ) : (\n              \"External item\"\n            )\n          ) : (\n            <>\n              <Link size=\"16\" />\n              <span>{field.placeholder}</span>\n            </>\n          )}\n        </button>\n        {value && (\n          <button\n            type=\"button\"\n            className={getClassName(\"detachButton\")}\n            onClick={() => {\n              onChange(null);\n            }}\n            disabled={readOnly}\n          >\n            <Unlock size={16} />\n          </button>\n        )}\n      </div>\n      <Modal onClose={() => setOpen(false)} isOpen={isOpen}>\n        <form\n          className={getClassNameModal({\n            isLoading,\n            loaded: !isLoading,\n            hasData: mappedData.length > 0,\n            filtersToggled,\n          })}\n          onSubmit={(e) => {\n            e.preventDefault();\n            e.stopPropagation();\n            search(searchQuery, filters);\n          }}\n        >\n          <div className={getClassNameModal(\"masthead\")}>\n            {field.showSearch ? (\n              <div className={getClassNameModal(\"searchForm\")}>\n                <label className={getClassNameModal(\"search\")}>\n                  <span className={getClassNameModal(\"searchIconText\")}>\n                    Search\n                  </span>\n                  <div className={getClassNameModal(\"searchIcon\")}>\n                    <Search size=\"18\" />\n                  </div>\n                  <input\n                    className={getClassNameModal(\"searchInput\")}\n                    name=\"q\"\n                    type=\"search\"\n                    placeholder={field.placeholder}\n                    onChange={(e) => {\n                      setSearchQuery(e.currentTarget.value);\n                    }}\n                    autoComplete=\"off\"\n                    value={searchQuery}\n                  ></input>\n                </label>\n                <div className={getClassNameModal(\"searchActions\")}>\n                  <Button type=\"submit\" loading={isLoading} fullWidth>\n                    Search\n                  </Button>\n                  {hasFilterFields && (\n                    <div className={getClassNameModal(\"searchActionIcon\")}>\n                      <IconButton\n                        type=\"button\"\n                        title=\"Toggle filters\"\n                        onClick={(e) => {\n                          e.preventDefault();\n                          e.stopPropagation();\n                          setFiltersToggled(!filtersToggled);\n                        }}\n                      >\n                        <SlidersHorizontal size={20} />\n                      </IconButton>\n                    </div>\n                  )}\n                </div>\n              </div>\n            ) : (\n              <Heading rank=\"2\" size=\"xs\">\n                {field.placeholder || \"Select data\"}\n              </Heading>\n            )}\n          </div>\n\n          <div className={getClassNameModal(\"grid\")}>\n            {hasFilterFields && (\n              <div className={getClassNameModal(\"filters\")}>\n                {hasFilterFields &&\n                  Object.keys(filterFields).map((fieldName) => {\n                    const filterField = filterFields[fieldName];\n                    return (\n                      <div\n                        className={getClassNameModal(\"field\")}\n                        key={fieldName}\n                      >\n                        <FieldLabel label={filterField.label || fieldName}>\n                          <AutoField\n                            field={filterField}\n                            id={`external_field_${fieldName}_filter`}\n                            value={filters[fieldName]}\n                            onChange={(value) => {\n                              setFilters((filters) => {\n                                const newFilters = {\n                                  ...filters,\n                                  [fieldName]: value,\n                                };\n\n                                search(searchQuery, newFilters);\n\n                                return newFilters;\n                              });\n                            }}\n                          />\n                        </FieldLabel>\n                      </div>\n                    );\n                  })}\n              </div>\n            )}\n\n            <div className={getClassNameModal(\"tableWrapper\")}>\n              <table className={getClassNameModal(\"table\")}>\n                <thead className={getClassNameModal(\"thead\")}>\n                  <tr className={getClassNameModal(\"tr\")}>\n                    {keys.map((key) => (\n                      <th\n                        key={key}\n                        className={getClassNameModal(\"th\")}\n                        style={{ textAlign: \"left\" }}\n                      >\n                        {key}\n                      </th>\n                    ))}\n                  </tr>\n                </thead>\n                <tbody className={getClassNameModal(\"tbody\")}>\n                  {mappedData.map((item, i) => {\n                    return (\n                      <tr\n                        key={i}\n                        style={{ whiteSpace: \"nowrap\" }}\n                        className={getClassNameModal(\"tr\")}\n                        onClick={() => {\n                          onChange(mapProp(data[i]));\n\n                          setOpen(false);\n                        }}\n                      >\n                        {keys.map((key) => (\n                          <td key={key} className={getClassNameModal(\"td\")}>\n                            {item[key]}\n                          </td>\n                        ))}\n                      </tr>\n                    );\n                  })}\n                </tbody>\n              </table>\n\n              <div className={getClassNameModal(\"loadingBanner\")}>\n                <Loader size={24} />\n              </div>\n            </div>\n          </div>\n          <div className={getClassNameModal(\"footerContainer\")}>\n            <Footer items={mappedData} />\n          </div>\n        </form>\n      </Modal>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/ExternalInput/styles.module.css",
    "content": ".ExternalInput-actions {\n  display: flex;\n}\n\n.ExternalInput-button {\n  display: flex;\n  gap: 8px;\n  align-items: center;\n  justify-content: center;\n  background-color: var(--puck-color-white);\n  border: 1px solid var(--puck-color-grey-09);\n  border-radius: 4px;\n  color: var(--puck-color-azure-04);\n  padding: 12px 16px;\n  font-weight: 500;\n  white-space: nowrap;\n  text-overflow: ellipsis;\n  transition: background-color 50ms ease-in;\n  position: relative;\n  overflow: hidden;\n  flex-grow: 1;\n}\n\n.ExternalInput--dataSelected .ExternalInput-button {\n  color: var(--puck-color-grey-03);\n  display: block;\n  border-top-right-radius: 0px;\n  border-bottom-right-radius: 0px;\n}\n\n.ExternalInput--readOnly .ExternalInput-button {\n  background-color: var(--puck-color-grey-11);\n}\n\n.ExternalInput-detachButton {\n  border: 1px solid var(--puck-color-grey-09);\n  border-top-right-radius: 4px;\n  border-bottom-right-radius: 4px;\n  background-color: var(--puck-color-grey-12);\n  color: var(--puck-color-grey-05);\n  display: flex;\n  gap: 8px;\n  align-items: center;\n  justify-content: center;\n  padding: 8px 12px;\n  position: relative;\n  transition: background-color 50ms ease-in, color 50ms ease-in;\n  margin-inline-start: -1px;\n}\n\n.ExternalInput-button:focus-visible,\n.ExternalInput-detachButton:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n  z-index: 1;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ExternalInput:not(.ExternalInput--readOnly) .ExternalInput-button:hover,\n  .ExternalInput:not(.ExternalInput--readOnly)\n    .ExternalInput-detachButton:hover {\n    background: var(--puck-color-azure-12);\n    transition: none;\n  }\n\n  .ExternalInput:not(.ExternalInput--readOnly)\n    .ExternalInput-detachButton:hover {\n    color: var(--puck-color-azure-04);\n  }\n}\n\n.ExternalInput:not(.ExternalInput--readOnly) .ExternalInput-button:active,\n.ExternalInput:not(.ExternalInput--readOnly)\n  .ExternalInput-detachButton:active {\n  background: var(--puck-color-azure-11);\n  transition: none;\n}\n\n.ExternalInputModal {\n  color: var(--puck-color-black);\n  display: grid;\n  grid-template-rows: min-content minmax(128px, 100%) min-content;\n  grid-template-columns: 100%;\n  position: relative;\n  min-height: 50dvh;\n  max-height: 90dvh;\n}\n\n.ExternalInputModal-grid {\n  display: flex;\n  flex-direction: column;\n}\n\n@media (min-width: 458px) {\n  .ExternalInputModal-grid {\n    display: grid;\n    grid-template-columns: 100%;\n  }\n\n  .ExternalInputModal--filtersToggled .ExternalInputModal-grid {\n    grid-template-columns: 25% 75%;\n  }\n}\n\n.ExternalInputModal-filters {\n  border-bottom: 1px solid var(--puck-color-grey-09);\n}\n\n.ExternalInputModal--filtersToggled .ExternalInputModal-filters {\n  display: none; /* Hide filters by default on smaller viewports */\n}\n\n@media (min-width: 458px) {\n  .ExternalInputModal-filters {\n    border-inline-end: 1px solid var(--puck-color-grey-09);\n    display: none;\n  }\n\n  .ExternalInputModal--filtersToggled .ExternalInputModal-filters {\n    display: block; /* Show filters by default on larger viewports */\n  }\n}\n\n.ExternalInputModal-masthead {\n  background-color: var(--puck-color-grey-12);\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  display: flex;\n  flex-wrap: wrap;\n  gap: 24px;\n  padding: 24px;\n}\n\n.ExternalInputModal-tableWrapper {\n  position: relative;\n  overflow-x: auto;\n  overflow-y: auto;\n  flex-grow: 1;\n}\n\n.ExternalInputModal-table {\n  border-collapse: unset;\n  border-spacing: 0px;\n  color: var(--puck-color-grey-02);\n  position: relative;\n  z-index: 0;\n  min-width: 100%;\n}\n\n.ExternalInputModal-thead {\n  background-color: var(--puck-color-white);\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.ExternalInputModal-th {\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  color: var(--puck-color-grey-04);\n  font-weight: 500;\n  font-size: 14px;\n  padding: 16px 24px;\n}\n\n.ExternalInputModal-td {\n  border-bottom: 1px solid var(--puck-color-grey-10);\n  padding: 16px 24px;\n}\n\n.ExternalInputModal-tr .ExternalInputModal-td:first-of-type {\n  font-weight: 500;\n  width: 1%; /* Prevent growing */\n  white-space: nowrap; /* Prevent growing */\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ExternalInputModal-tbody .ExternalInputModal-tr:hover {\n    background: var(--puck-color-azure-12);\n    color: var(--puck-color-azure-04);\n    cursor: pointer;\n    position: relative;\n    margin-inline-start: -5px;\n  }\n\n  .ExternalInputModal-tbody\n    .ExternalInputModal-tr:hover\n    .ExternalInputModal-td:first-of-type {\n    border-inline-start: 4px solid var(--puck-color-azure-04);\n    padding-inline-start: 20px;\n  }\n}\n\n.ExternalInputModal-tbody\n  .ExternalInputModal-tr:last-of-type\n  .ExternalInputModal-td {\n  border-bottom: none;\n}\n\n.ExternalInputModal-tableWrapper {\n  display: none;\n}\n\n.ExternalInputModal--hasData .ExternalInputModal-tableWrapper {\n  display: block;\n}\n\n.ExternalInputModal-loadingBanner {\n  display: none;\n  background-color: color-mix(\n    in srgb,\n    var(--puck-color-white) 90%,\n    transparent\n  );\n  padding: 64px;\n  align-items: center;\n  justify-content: center;\n  position: absolute;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n}\n\n.ExternalInputModal--isLoading .ExternalInputModal-loadingBanner {\n  display: flex;\n}\n\n.ExternalInputModal-searchForm {\n  display: flex;\n  flex-wrap: wrap;\n  gap: 12px;\n  flex-grow: 1;\n}\n\n@media (min-width: 458px) {\n  .ExternalInputModal-searchForm {\n    flex-wrap: nowrap;\n  }\n}\n\n.ExternalInputModal-search {\n  display: flex;\n  background: var(--puck-color-white);\n  border-width: 1px;\n  border-style: solid;\n  border-color: var(--puck-color-grey-09);\n  border-radius: 4px;\n  flex-grow: 1;\n  transition: border-color 50ms ease-in;\n}\n\n.ExternalInputModal-search:focus-within {\n  border-color: var(--puck-color-grey-05);\n  outline: 2px solid var(--puck-color-azure-05);\n  transition: none;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ExternalInputModal-search:hover {\n    border-color: var(--puck-color-grey-05);\n    transition: none;\n  }\n}\n\n.ExternalInputModal-searchIcon {\n  align-items: center;\n  background: var(--puck-color-grey-12);\n  border-bottom-left-radius: 4px;\n  border-top-left-radius: 4px;\n  border-inline-end: 1px solid var(--puck-color-grey-09);\n  color: var(--puck-color-grey-07);\n  display: flex;\n  justify-content: center;\n  padding: 12px 15px;\n  transition: color 50ms ease-in;\n}\n\n.ExternalInputModal-search:focus-within .ExternalInputModal-searchIcon {\n  color: var(--puck-color-grey-04);\n  transition: none;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .ExternalInputModal-search:hover .ExternalInputModal-searchIcon {\n    color: var(--puck-color-grey-04);\n    transition: none;\n  }\n}\n\n.ExternalInputModal-searchIconText {\n  clip: rect(0 0 0 0);\n  clip-path: inset(100%);\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  width: 1px;\n}\n\n.ExternalInputModal-searchInput {\n  border: none;\n  border-radius: 4px;\n  background: var(--puck-color-white);\n  font-family: inherit;\n  font-size: 14px;\n  padding: 12px 15px;\n  width: 100%;\n}\n\n.ExternalInputModal-searchInput:focus {\n  outline: 0;\n}\n\n.ExternalInputModal-searchActions {\n  display: flex;\n  gap: 8px;\n  height: 44px;\n  width: 100%;\n}\n\n@media (min-width: 458px) {\n  .ExternalInputModal-searchActions {\n    width: auto;\n  }\n}\n\n.ExternalInputModal-searchActionIcon {\n  align-self: center;\n}\n\n.ExternalInputModal-footerContainer {\n  background-color: var(--puck-color-grey-12);\n  border-top: 1px solid var(--puck-color-grey-09);\n  color: var(--puck-color-grey-04);\n  padding: 16px;\n}\n\n.ExternalInputModal-footer {\n  font-weight: 500;\n  font-size: 14px;\n  text-align: right;\n}\n\n.ExternalInputModal-field {\n  color: var(--puck-color-grey-04);\n  margin: 16px;\n  margin-bottom: 12px;\n  display: block;\n}\n"
  },
  {
    "path": "packages/core/components/Heading/index.tsx",
    "content": "import { ReactNode } from \"react\";\nimport styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\n\nconst getClassName = getClassNameFactory(\"Heading\", styles);\n\nexport type HeadingProps = {\n  children: ReactNode;\n  rank?: \"1\" | \"2\" | \"3\" | \"4\" | \"5\" | \"6\";\n  size?: \"xxxxl\" | \"xxxl\" | \"xxl\" | \"xl\" | \"l\" | \"m\" | \"s\" | \"xs\";\n};\n\nexport const Heading = ({ children, rank, size = \"m\" }: HeadingProps) => {\n  const Tag: any = rank ? `h${rank}` : \"span\";\n\n  return (\n    <Tag\n      className={getClassName({\n        [size]: true,\n      })}\n    >\n      {children}\n    </Tag>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Heading/styles.module.css",
    "content": ".Heading {\n  display: block;\n  color: var(--puck-color-black);\n  font-weight: 700;\n  margin: 0;\n}\n\n.Heading b {\n  font-weight: 700;\n}\n\n.Heading--xxxxl {\n  font-size: var(--puck-font-size-xxxxl);\n  letter-spacing: 0.08ch;\n  font-weight: 800;\n}\n\n.Heading--xxxl {\n  font-size: var(--puck-font-size-xxxl);\n}\n\n.Heading--xxl {\n  font-size: var(--puck-font-size-xxl);\n}\n\n.Heading--xl {\n  font-size: var(--puck-font-size-xl);\n}\n\n.Heading--l {\n  font-size: var(--puck-font-size-l);\n}\n\n.Heading--m {\n  font-size: var(--puck-font-size-m);\n}\n\n.Heading--s {\n  font-size: var(--puck-font-size-s);\n}\n\n.Heading--xs {\n  font-size: var(--puck-font-size-xs);\n}\n"
  },
  {
    "path": "packages/core/components/IconButton/IconButton.module.css",
    "content": ".IconButton {\n  align-items: center;\n  background: transparent;\n  border: none;\n  border-radius: 4px;\n  color: currentColor;\n  display: flex;\n  font-family: var(--puck-font-family);\n  justify-content: center;\n  padding: 4px;\n  transition: background-color 50ms ease-in, color 50ms ease-in;\n}\n\n.IconButton--active {\n  color: var(--puck-color-azure-04);\n}\n\n.IconButton:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: -2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .IconButton:hover:not(.IconButton--disabled) {\n    background: var(--puck-color-grey-10);\n    color: var(--puck-color-azure-04);\n    cursor: pointer;\n    transition: none;\n  }\n}\n\n.IconButton:active {\n  background: var(--puck-color-azure-11);\n  transition: none;\n}\n\n.IconButton-title {\n  clip: rect(0 0 0 0);\n  clip-path: inset(100%);\n  height: 1px;\n  overflow: hidden;\n  position: absolute;\n  white-space: nowrap;\n  width: 1px;\n}\n\n.IconButton--disabled {\n  color: var(--puck-color-grey-07);\n}\n"
  },
  {
    "path": "packages/core/components/IconButton/IconButton.tsx",
    "content": "import { ReactNode, SyntheticEvent, useState } from \"react\";\nimport styles from \"./IconButton.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { Loader } from \"../Loader\";\n\nconst getClassName = getClassNameFactory(\"IconButton\", styles);\n\nexport const IconButton = ({\n  active = false,\n  children,\n  href,\n  onClick,\n  type,\n  disabled,\n  tabIndex,\n  newTab,\n  fullWidth,\n  title,\n  suppressHydrationWarning,\n}: {\n  active?: boolean;\n  children: ReactNode;\n  href?: string;\n  onClick?: (e: SyntheticEvent) => void | Promise<void>;\n  type?: \"button\" | \"submit\" | \"reset\";\n  disabled?: boolean;\n  tabIndex?: number;\n  newTab?: boolean;\n  fullWidth?: boolean;\n  title: string;\n  suppressHydrationWarning?: boolean;\n}) => {\n  const [loading, setLoading] = useState(false);\n\n  const ElementType = href ? \"a\" : \"button\";\n\n  const el = (\n    <ElementType\n      className={getClassName({\n        active,\n        disabled,\n        fullWidth,\n      })}\n      onClick={(e) => {\n        if (!onClick) return;\n\n        setLoading(true);\n        Promise.resolve(onClick(e)).then(() => {\n          setLoading(false);\n        });\n      }}\n      type={type}\n      disabled={disabled || loading}\n      tabIndex={tabIndex}\n      target={newTab ? \"_blank\" : undefined}\n      rel={newTab ? \"noreferrer\" : undefined}\n      href={href}\n      title={title}\n      suppressHydrationWarning={suppressHydrationWarning}\n    >\n      <span className={getClassName(\"title\")}>{title}</span>\n      {children}\n      {loading && (\n        <>\n          &nbsp;&nbsp;\n          <Loader size={14} />\n        </>\n      )}\n    </ElementType>\n  );\n\n  return el;\n};\n"
  },
  {
    "path": "packages/core/components/IconButton/index.ts",
    "content": "export * from \"./IconButton\";\n"
  },
  {
    "path": "packages/core/components/InlineTextField/index.tsx",
    "content": "\"use client\";\n\nimport { memo, useEffect, useRef, useState } from \"react\";\nimport { registerOverlayPortal } from \"../../lib/overlay-portal\";\nimport { useAppStoreApi } from \"../../store\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"../../lib\";\nimport { setDeep } from \"../../lib/data/set-deep\";\nimport { getSelectorForId } from \"../../lib/get-selector-for-id\";\n\nconst getClassName = getClassNameFactory(\"InlineTextField\", styles);\n\nconst InlineTextFieldInternal = ({\n  propPath,\n  componentId,\n  value,\n  isReadOnly,\n  opts = {},\n}: {\n  propPath: string;\n  value: string;\n  componentId: string;\n  isReadOnly: boolean;\n  opts?: { disableLineBreaks?: boolean };\n}) => {\n  const ref = useRef<HTMLHeadingElement>(null);\n  const appStoreApi = useAppStoreApi();\n  const disableLineBreaks = opts.disableLineBreaks ?? false;\n\n  useEffect(() => {\n    const appStore = appStoreApi.getState();\n    const data = appStore.state.indexes.nodes[componentId].data;\n    const componentConfig = appStore.getComponentConfig(data.type);\n\n    if (!componentConfig) {\n      throw new Error(\n        `InlineTextField Error: No config defined for ${data.type}`\n      );\n    }\n\n    if (ref.current) {\n      if (value !== ref.current.innerText) {\n        ref.current.replaceChildren(value);\n      }\n\n      const cleanupPortal = registerOverlayPortal(ref.current);\n\n      const handleInput = async (e: any) => {\n        const appStore = appStoreApi.getState();\n        const node = appStore.state.indexes.nodes[componentId];\n\n        const zoneCompound = `${node.parentId}:${node.zone}`;\n        const index =\n          appStore.state.indexes.zones[zoneCompound]?.contentIds.indexOf(\n            componentId\n          );\n\n        let value = e.target.innerText;\n\n        if (disableLineBreaks) {\n          value = value.replaceAll(/\\n/gm, \"\");\n        }\n\n        const newProps = setDeep(node.data.props, propPath, value);\n\n        const resolvedData = await appStore.resolveComponentData(\n          { ...node.data, props: newProps },\n          \"replace\"\n        );\n\n        appStore.dispatch({\n          type: \"replace\",\n          data: resolvedData.node,\n          destinationIndex: index,\n          destinationZone: zoneCompound,\n        });\n      };\n\n      ref.current.addEventListener(\"input\", handleInput);\n\n      return () => {\n        ref.current?.removeEventListener(\"input\", handleInput);\n\n        cleanupPortal?.();\n      };\n    }\n  }, [appStoreApi, ref.current, value, disableLineBreaks]);\n\n  // We disable contentEditable when not hovering or already focused,\n  // otherwise Safari focuses the element during drag. Related:\n  // https://bugs.webkit.org/show_bug.cgi?id=112854\n  const [isHovering, setIsHovering] = useState(false);\n  const [isFocused, setIsFocused] = useState(false);\n\n  return (\n    <span\n      className={getClassName()}\n      ref={ref}\n      contentEditable={isHovering || isFocused ? \"plaintext-only\" : \"false\"}\n      onClick={(e) => {\n        e.preventDefault();\n        e.stopPropagation();\n      }}\n      onClickCapture={(e) => {\n        e.preventDefault();\n        e.stopPropagation();\n\n        const itemSelector = getSelectorForId(\n          appStoreApi.getState().state,\n          componentId\n        );\n\n        appStoreApi.getState().setUi({ itemSelector });\n      }}\n      onKeyDown={(e) => {\n        e.stopPropagation();\n\n        if ((disableLineBreaks && e.key === \"Enter\") || isReadOnly) {\n          e.preventDefault();\n        }\n      }}\n      onKeyUp={(e) => {\n        e.stopPropagation();\n        e.preventDefault();\n      }}\n      onMouseOverCapture={() => setIsHovering(true)}\n      onMouseOutCapture={() => setIsHovering(false)}\n      onFocus={() => setIsFocused(true)}\n      onBlur={() => setIsFocused(false)}\n    />\n  );\n};\n\nexport const InlineTextField = memo(InlineTextFieldInternal);\n"
  },
  {
    "path": "packages/core/components/InlineTextField/styles.module.css",
    "content": ".InlineTextField {\n  cursor: text;\n  display: inline-block;\n  white-space: pre-wrap;\n  text-decoration: inherit;\n}\n\n/* Safari fixes for https://bugs.webkit.org/show_bug.cgi?id=112854 */\n[data-dnd-dragging] .InlineTextField {\n  cursor: none;\n  caret-color: transparent;\n}\n\n[data-dnd-dragging] .InlineTextField::selection {\n  display: none;\n}\n"
  },
  {
    "path": "packages/core/components/LayerTree/index.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { ComponentConfig } from \"../../types\";\nimport { ItemSelector } from \"../../lib/data/get-item\";\nimport { scrollIntoView } from \"../../lib/scroll-into-view\";\nimport { ChevronDown, LayoutGrid, Layers, Type } from \"lucide-react\";\nimport { rootAreaId, rootDroppableId } from \"../../lib/root-droppable-id\";\nimport { useCallback, useContext } from \"react\";\nimport { ZoneStoreContext } from \"../DropZone/context\";\nimport { getFrame } from \"../../lib/get-frame\";\nimport { onScrollEnd } from \"../../lib/on-scroll-end\";\nimport { useAppStore } from \"../../store\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useContextStore } from \"../../lib/use-context-store\";\n\nconst getClassName = getClassNameFactory(\"LayerTree\", styles);\nconst getClassNameLayer = getClassNameFactory(\"Layer\", styles);\n\nconst Layer = ({\n  index,\n  itemId,\n  zoneCompound,\n}: {\n  index: number;\n  itemId: string;\n  zoneCompound: string;\n}) => {\n  const config = useAppStore((s) => s.config);\n  const itemSelector = useAppStore((s) => s.state.ui.itemSelector);\n  const dispatch = useAppStore((s) => s.dispatch);\n\n  const setItemSelector = useCallback(\n    (itemSelector: ItemSelector | null) => {\n      dispatch({ type: \"setUi\", ui: { itemSelector } });\n    },\n    [dispatch]\n  );\n\n  const selecedItemId = useAppStore((s) => s.selectedItem?.props.id);\n\n  const isSelected =\n    selecedItemId === itemId ||\n    (itemSelector && itemSelector.zone === rootDroppableId && !zoneCompound);\n\n  // eslint-disable-next-line react-hooks/rules-of-hooks\n  const nodeData = useAppStore((s) => s.state.indexes.nodes[itemId]);\n\n  const zonesForItem = useAppStore(\n    useShallow((s) =>\n      Object.keys(s.state.indexes.zones).filter(\n        (z) => z.split(\":\")[0] === itemId\n      )\n    )\n  );\n\n  const containsZone = zonesForItem.length > 0;\n\n  const zoneStore = useContext(ZoneStoreContext);\n  const isHovering = useContextStore(\n    ZoneStoreContext,\n    (s) => s.hoveringComponent === itemId\n  );\n\n  const childIsSelected = useAppStore((s) => {\n    const selectedData = s.state.indexes.nodes[s.selectedItem?.props.id];\n\n    return (\n      selectedData?.path.some((candidate) => {\n        const [candidateId] = candidate.split(\":\");\n\n        return candidateId === itemId;\n      }) ?? false\n    );\n  });\n\n  const componentConfig: ComponentConfig | undefined =\n    config.components[nodeData.data.type];\n  const label = componentConfig?.[\"label\"] ?? nodeData.data.type.toString();\n\n  return (\n    <li\n      className={getClassNameLayer({\n        isSelected,\n        isHovering,\n        containsZone,\n        childIsSelected,\n      })}\n    >\n      <div className={getClassNameLayer(\"inner\")}>\n        <button\n          type=\"button\"\n          className={getClassNameLayer(\"clickable\")}\n          onClick={() => {\n            if (isSelected) {\n              setItemSelector(null);\n              return;\n            }\n\n            const frame = getFrame();\n\n            const el = frame?.querySelector(\n              `[data-puck-component=\"${itemId}\"]`\n            );\n\n            if (!el) {\n              setItemSelector({\n                index,\n                zone: zoneCompound,\n              });\n              return;\n            }\n\n            scrollIntoView(el as HTMLElement);\n\n            onScrollEnd(frame, () => {\n              setItemSelector({\n                index,\n                zone: zoneCompound,\n              });\n            });\n          }}\n          onMouseEnter={(e) => {\n            e.stopPropagation();\n            zoneStore.setState({ hoveringComponent: itemId });\n          }}\n          onMouseLeave={(e) => {\n            e.stopPropagation();\n            zoneStore.setState({ hoveringComponent: null });\n          }}\n        >\n          {containsZone && (\n            <div\n              className={getClassNameLayer(\"chevron\")}\n              title={isSelected ? \"Collapse\" : \"Expand\"}\n            >\n              <ChevronDown size=\"12\" />\n            </div>\n          )}\n          <div className={getClassNameLayer(\"title\")}>\n            <div className={getClassNameLayer(\"icon\")}>\n              {nodeData.data.type === \"Text\" ||\n              nodeData.data.type === \"Heading\" ? (\n                <Type size=\"16\" />\n              ) : (\n                <LayoutGrid size=\"16\" />\n              )}\n            </div>\n            <div className={getClassNameLayer(\"name\")}>{label}</div>\n          </div>\n        </button>\n      </div>\n      {containsZone &&\n        zonesForItem.map((subzone) => (\n          <div key={subzone} className={getClassNameLayer(\"zones\")}>\n            <LayerTree zoneCompound={subzone} />\n          </div>\n        ))}\n    </li>\n  );\n};\n\nexport const LayerTree = ({\n  label: _label,\n  zoneCompound,\n}: {\n  label?: string;\n  zoneCompound: string;\n}) => {\n  // Use slot label if provided\n  const label = useAppStore((s) => {\n    if (_label) return _label;\n\n    if (zoneCompound === rootDroppableId) return;\n\n    const [componentId, slotId] = zoneCompound.split(\":\");\n\n    const componentType = s.state.indexes.nodes[componentId]?.data.type;\n\n    const configForComponent =\n      componentType && componentType !== rootAreaId\n        ? s.config.components[componentType]\n        : s.config.root;\n\n    return configForComponent?.fields?.[slotId]?.label ?? slotId;\n  });\n\n  const contentIds = useAppStore(\n    useShallow((s) =>\n      zoneCompound ? s.state.indexes.zones[zoneCompound]?.contentIds ?? [] : []\n    )\n  );\n\n  return (\n    <>\n      {label && (\n        <div className={getClassName(\"zoneTitle\")}>\n          <div className={getClassName(\"zoneIcon\")}>\n            <Layers size=\"16\" />\n          </div>\n          {label}\n        </div>\n      )}\n      <ul className={getClassName()}>\n        {contentIds.length === 0 && (\n          <div className={getClassName(\"helper\")}>No items</div>\n        )}\n        {contentIds.map((itemId, i) => {\n          return (\n            <Layer\n              index={i}\n              itemId={itemId}\n              zoneCompound={zoneCompound}\n              key={itemId}\n            />\n          );\n        })}\n      </ul>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/LayerTree/styles.module.css",
    "content": ".LayerTree {\n  color: var(--puck-color-grey-03);\n  font-family: var(--puck-font-family);\n  font-size: var(--puck-font-size-xxs);\n  margin: 0;\n  position: relative;\n  list-style: none;\n  padding: 0;\n}\n\n.LayerTree-zoneTitle {\n  color: var(--puck-color-grey-05);\n  font-size: var(--puck-font-size-xxxs);\n  text-transform: uppercase;\n}\n\n.LayerTree-helper {\n  text-align: center;\n  color: var(--puck-color-grey-07);\n  margin: 8px 4px;\n}\n\n.Layer {\n  position: relative;\n  border: 1px solid transparent;\n  border-radius: 4px;\n}\n\n.Layer-inner {\n  border: 1px solid transparent;\n  border-radius: 4px;\n  transition: color 50ms ease-in;\n}\n\n.Layer--containsZone > .Layer-inner {\n  padding-inline-start: 0;\n}\n\n.Layer-clickable {\n  align-items: center;\n  background: none;\n  border: 0;\n  border-radius: 4px;\n  color: inherit;\n  cursor: pointer;\n  display: flex;\n  font: inherit;\n  padding-inline-start: 12px;\n  padding-inline-end: 4px;\n  width: 100%;\n}\n\n.Layer-clickable:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n  position: relative;\n  z-index: 1;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .Layer:not(.Layer--isSelected) > .Layer-inner:hover {\n    border-color: var(--puck-color-azure-10);\n    background: var(--puck-color-azure-11);\n    color: var(--puck-color-azure-04);\n    transition: none;\n  }\n}\n\n.Layer--isSelected {\n  border-color: var(--puck-color-azure-08);\n}\n\n.Layer--isSelected > .Layer-inner {\n  background: var(--puck-color-azure-10);\n}\n\n.Layer--isSelected > .Layer-inner > .Layer-clickable > .Layer-chevron,\n.Layer--childIsSelected > .Layer-inner > .Layer-clickable > .Layer-chevron {\n  transform: scaleY(-1);\n}\n\n.Layer-zones {\n  display: none;\n  margin-inline-start: 12px;\n}\n\n.Layer--isSelected > .Layer-zones,\n.Layer--childIsSelected > .Layer-zones {\n  display: block;\n}\n\n.Layer-zones > .LayerTree {\n  margin-inline-start: 12px;\n}\n\n.Layer-title,\n.LayerTree-zoneTitle {\n  display: flex;\n  gap: 8px;\n  align-items: center;\n  margin: 8px 4px;\n  overflow-x: hidden;\n}\n\n.Layer-name {\n  overflow-x: hidden;\n  text-overflow: ellipsis;\n  white-space: nowrap;\n}\n\n.Layer-icon {\n  color: var(--puck-color-rose-07);\n  margin-top: 4px;\n}\n\n.Layer-zoneIcon {\n  color: var(--puck-color-grey-08);\n  margin-top: 4px;\n}\n"
  },
  {
    "path": "packages/core/components/Loader/index.tsx",
    "content": "import type { JSX } from \"react\";\nimport { getClassNameFactory } from \"../../lib\";\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"Loader\", styles);\n\nexport const Loader = ({\n  color,\n  size = 16,\n  ...props\n}: {\n  color?: string;\n  size?: number;\n} & JSX.IntrinsicAttributes) => {\n  return (\n    <span\n      className={getClassName()}\n      style={{\n        width: size,\n        height: size,\n        color,\n      }}\n      aria-label=\"loading\"\n      {...props}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Loader/styles.module.css",
    "content": "@keyframes loader-animation {\n  0% {\n    transform: rotate(0deg) scale(1);\n  }\n  50% {\n    transform: rotate(180deg) scale(0.8);\n  }\n  100% {\n    transform: rotate(360deg) scale(1);\n  }\n}\n\n.Loader {\n  background: transparent;\n  border-radius: 100%;\n  border: 2px solid currentColor;\n  border-bottom-color: transparent;\n  display: inline-block;\n  animation: loader-animation 1s 0s infinite linear;\n  animation-fill-mode: both;\n}\n"
  },
  {
    "path": "packages/core/components/MemoizeComponent/index.tsx",
    "content": "import { deepEqual } from \"fast-equals\";\nimport { ComponentType, memo } from \"react\";\nimport { shallowEqual } from \"../../lib/shallow-equal\";\n\nconst RenderComponent = ({\n  Component,\n  componentProps: renderProps,\n}: {\n  Component: ComponentType<any>;\n  componentProps: any;\n}) => {\n  return <Component {...renderProps} />;\n};\n\n/**　Renders the Component and only re-renders when its props change using shallow comparison. Uses deep comparison for the \"puck\" prop. */\nexport const MemoizeComponent = memo(RenderComponent, (prev, next) => {\n  let puckEquals = true;\n  if (\"puck\" in prev.componentProps && \"puck\" in next.componentProps) {\n    puckEquals = deepEqual(prev.componentProps.puck, next.componentProps.puck);\n  }\n\n  return (\n    prev.Component === next.Component &&\n    shallowEqual(prev.componentProps, next.componentProps, [\"puck\"]) &&\n    puckEquals\n  );\n});\n"
  },
  {
    "path": "packages/core/components/MenuBar/index.tsx",
    "content": "import { Dispatch, ReactElement, SetStateAction } from \"react\";\nimport { Undo2Icon, Redo2Icon } from \"lucide-react\";\n\nimport { IconButton } from \"../IconButton/IconButton\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { PuckAction } from \"../../reducer\";\nimport type { Data } from \"../../types\";\n\nimport styles from \"./styles.module.css\";\nimport { useAppStore } from \"../../store\";\n\nconst getClassName = getClassNameFactory(\"MenuBar\", styles);\n\nexport function MenuBar<UserData extends Data>({\n  menuOpen = false,\n  renderHeaderActions,\n  setMenuOpen,\n}: {\n  dispatch: (action: PuckAction) => void;\n  onPublish?: (data: UserData) => void;\n  menuOpen: boolean;\n  renderHeaderActions?: () => ReactElement;\n  setMenuOpen: Dispatch<SetStateAction<boolean>>;\n}) {\n  const back = useAppStore((s) => s.history.back);\n  const forward = useAppStore((s) => s.history.forward);\n  const hasFuture = useAppStore((s) => s.history.hasFuture());\n  const hasPast = useAppStore((s) => s.history.hasPast());\n\n  return (\n    <div\n      className={getClassName({ menuOpen })}\n      onClick={(event) => {\n        const element = event.target as HTMLElement;\n\n        if (window.matchMedia(\"(min-width: 638px)\").matches) {\n          return;\n        }\n        if (\n          element.tagName === \"A\" &&\n          element.getAttribute(\"href\")?.startsWith(\"#\")\n        ) {\n          setMenuOpen(false);\n        }\n      }}\n    >\n      <div className={getClassName(\"inner\")}>\n        <div className={getClassName(\"history\")}>\n          <IconButton\n            type=\"button\"\n            title=\"undo\"\n            disabled={!hasPast}\n            onClick={back}\n          >\n            <Undo2Icon size={21} />\n          </IconButton>\n          <IconButton\n            type=\"button\"\n            title=\"redo\"\n            disabled={!hasFuture}\n            onClick={forward}\n          >\n            <Redo2Icon size={21} />\n          </IconButton>\n        </div>\n        <>{renderHeaderActions && renderHeaderActions()}</>\n      </div>\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/MenuBar/styles.module.css",
    "content": ".MenuBar {\n  background-color: var(--puck-color-white);\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  display: none;\n  left: 0;\n  margin-top: 1px;\n  padding: 8px 16px;\n  position: absolute;\n  right: 0;\n  top: 100%;\n  z-index: 2;\n}\n\n.MenuBar--menuOpen {\n  display: block;\n}\n\n@media (min-width: 638px) {\n  .MenuBar {\n    border: none;\n    display: block;\n    margin-top: 0;\n    overflow-y: visible;\n    padding: 0;\n    position: static;\n  }\n}\n\n.MenuBar-inner {\n  align-items: center;\n  display: flex;\n  flex-wrap: wrap;\n  gap: 8px 16px;\n  justify-content: flex-end;\n}\n\n@media (min-width: 638px) {\n  .MenuBar-inner {\n    display: flex;\n    flex-direction: row;\n    flex-wrap: nowrap;\n  }\n}\n\n.MenuBar-history {\n  display: flex;\n}\n"
  },
  {
    "path": "packages/core/components/Modal/index.tsx",
    "content": "import { ReactNode, useEffect, useState } from \"react\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\nimport { createPortal } from \"react-dom\";\n\nconst getClassName = getClassNameFactory(\"Modal\", styles);\n\nexport const Modal = ({\n  children,\n  onClose,\n  isOpen,\n}: {\n  children: ReactNode;\n  onClose: () => void;\n  isOpen: boolean;\n}) => {\n  const [rootEl, setRootEl] = useState<any>(null);\n\n  useEffect(() => {\n    setRootEl(document.getElementById(\"puck-portal-root\"));\n  }, []);\n\n  if (!rootEl) {\n    return <div />;\n  }\n\n  return createPortal(\n    <div className={getClassName({ isOpen })} onClick={onClose}>\n      <div\n        className={getClassName(\"inner\")}\n        onClick={(e) => e.stopPropagation()}\n      >\n        {children}\n      </div>\n    </div>,\n    rootEl\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Modal/styles.module.css",
    "content": ".Modal {\n  background: color-mix(in srgb, var(--puck-color-black) 75%, transparent);\n  display: none;\n  justify-content: center;\n  align-items: center;\n  position: fixed;\n  top: 0;\n  left: 0;\n  bottom: 0;\n  right: 0;\n  z-index: 1;\n  padding: 32px;\n}\n\n.Modal--isOpen {\n  display: flex;\n}\n\n.Modal-inner {\n  width: 100%;\n  max-width: 1024px;\n  border-radius: 8px;\n  overflow: hidden;\n  background: var(--puck-color-white);\n  display: flex;\n  flex-direction: column;\n  max-height: 90dvh;\n}\n"
  },
  {
    "path": "packages/core/components/OutlineList/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { ReactNode, SyntheticEvent } from \"react\";\n\nconst getClassName = getClassNameFactory(\"OutlineList\", styles);\nconst getClassNameItem = getClassNameFactory(\"OutlineListItem\", styles);\n\nexport const OutlineList = ({ children }: { children: ReactNode }) => {\n  return <ul className={getClassName()}>{children}</ul>;\n};\n\n// eslint-disable-next-line react/display-name\nOutlineList.Clickable = ({ children }: { children: ReactNode }) => (\n  <div className={getClassNameItem({ clickable: true })}>{children}</div>\n);\n\n// eslint-disable-next-line react/display-name\nOutlineList.Item = ({\n  children,\n  onClick,\n}: {\n  children: ReactNode;\n  onClick?: (e: SyntheticEvent) => void;\n}) => {\n  return (\n    <li\n      className={getClassNameItem({ clickable: !!onClick })}\n      onClick={onClick}\n    >\n      {children}\n    </li>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/OutlineList/styles.module.css",
    "content": ".OutlineList {\n  color: var(--puck-color-grey-03);\n  font-family: var(--puck-font-family);\n  margin: 0;\n  padding-inline-start: 16px;\n  position: relative;\n  list-style: none;\n}\n\n.OutlineList::before {\n  background: var(--puck-color-grey-08);\n  position: absolute;\n  left: -1px;\n  top: 0px;\n  width: 1px;\n  height: calc(100% - 9px);\n  content: \"\";\n}\n\n.OutlineList:dir(rtl)::before {\n  left: unset;\n  right: -1px;\n}\n\n.OutlineListItem {\n  position: relative;\n  margin-bottom: 4px;\n}\n\n.OutlineListItem::before {\n  background: var(--puck-color-grey-08);\n  position: absolute;\n  left: -17px;\n  top: 9px;\n  width: 13px;\n  height: 1px;\n  content: \"\";\n}\n\n.OutlineListItem:dir(rtl)::before {\n  left: unset;\n  right: -17px;\n}\n\n.OutlineListItem--clickable {\n  cursor: pointer;\n  transition: color 50ms ease-in;\n}\n\n.OutlineListItem--clickable:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .OutlineListItem--clickable:hover {\n    color: var(--puck-color-azure-04);\n    transition: none;\n  }\n}\n\n.OutlineListItem--clickable:active {\n  color: var(--puck-color-azure-03);\n  transition: none;\n}\n\n.OutlineListItem > .OutlineList {\n  margin: 8px 0;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/__tests__/__snapshots__/index.tsx.snap",
    "content": "// Jest Snapshot v1, https://goo.gl/fbAQLP\n\nexports[`Puck should generate the correct state on mount 1`] = `\n{\n  \"componentState\": {},\n  \"config\": {\n    \"components\": {\n      \"componentA\": {\n        \"render\": [Function],\n      },\n      \"componentB\": {\n        \"render\": [Function],\n      },\n    },\n    \"root\": {\n      \"render\": [Function],\n    },\n  },\n  \"dispatch\": [Function],\n  \"fieldTransforms\": {},\n  \"fields\": {\n    \"fields\": {\n      \"title\": {\n        \"type\": \"text\",\n      },\n    },\n    \"id\": undefined,\n    \"lastResolvedData\": {},\n    \"loading\": false,\n  },\n  \"getComponentConfig\": [Function],\n  \"getCurrentData\": [Function],\n  \"history\": {\n    \"back\": [Function],\n    \"currentHistory\": [Function],\n    \"forward\": [Function],\n    \"hasFuture\": [Function],\n    \"hasPast\": [Function],\n    \"histories\": [\n      {\n        \"state\": {\n          \"data\": {\n            \"content\": [],\n            \"root\": {\n              \"props\": {},\n            },\n            \"zones\": {},\n          },\n          \"indexes\": {\n            \"nodes\": {\n              \"root\": {\n                \"data\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                  },\n                  \"type\": \"root\",\n                },\n                \"flatData\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                  },\n                  \"type\": \"root\",\n                },\n                \"parentId\": null,\n                \"path\": [],\n                \"zone\": \"\",\n              },\n            },\n            \"zones\": {\n              \"root:default-zone\": {\n                \"contentIds\": [],\n                \"type\": \"root\",\n              },\n            },\n          },\n          \"ui\": {\n            \"arrayState\": {},\n            \"componentList\": {},\n            \"field\": {\n              \"focus\": null,\n            },\n            \"isDragging\": false,\n            \"itemSelector\": null,\n            \"leftSideBarVisible\": true,\n            \"plugin\": {\n              \"current\": null,\n            },\n            \"previewMode\": \"edit\",\n            \"rightSideBarVisible\": true,\n            \"viewports\": {\n              \"controlsVisible\": true,\n              \"current\": {\n                \"height\": \"auto\",\n                \"width\": 360,\n              },\n              \"options\": [],\n            },\n          },\n        },\n      },\n    ],\n    \"index\": 0,\n    \"initialAppState\": {\n      \"data\": {\n        \"content\": [],\n        \"root\": {\n          \"props\": {},\n        },\n        \"zones\": {},\n      },\n      \"indexes\": {\n        \"nodes\": {\n          \"root\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"root\",\n              },\n              \"type\": \"root\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"root\",\n              },\n              \"type\": \"root\",\n            },\n            \"parentId\": null,\n            \"path\": [],\n            \"zone\": \"\",\n          },\n        },\n        \"zones\": {\n          \"root:default-zone\": {\n            \"contentIds\": [],\n            \"type\": \"root\",\n          },\n        },\n      },\n      \"ui\": {\n        \"arrayState\": {},\n        \"componentList\": {},\n        \"field\": {\n          \"focus\": null,\n        },\n        \"isDragging\": false,\n        \"itemSelector\": null,\n        \"leftSideBarVisible\": true,\n        \"plugin\": {\n          \"current\": null,\n        },\n        \"previewMode\": \"edit\",\n        \"rightSideBarVisible\": true,\n        \"viewports\": {\n          \"controlsVisible\": true,\n          \"current\": {\n            \"height\": \"auto\",\n            \"width\": 360,\n          },\n          \"options\": [],\n        },\n      },\n    },\n    \"nextHistory\": [Function],\n    \"prevHistory\": [Function],\n    \"record\": [Function],\n    \"setHistories\": [Function],\n    \"setHistoryIndex\": [Function],\n  },\n  \"iframe\": {\n    \"enabled\": false,\n    \"waitForStyles\": true,\n  },\n  \"instanceId\": \"_r_5_\",\n  \"metadata\": undefined,\n  \"nodes\": {\n    \"nodes\": {},\n    \"registerNode\": [Function],\n    \"unregisterNode\": [Function],\n  },\n  \"onAction\": undefined,\n  \"overrides\": {},\n  \"pendingLoadTimeouts\": {},\n  \"permissions\": {\n    \"cache\": {},\n    \"getPermissions\": [Function],\n    \"globalPermissions\": {\n      \"delete\": true,\n      \"drag\": true,\n      \"duplicate\": true,\n      \"edit\": true,\n      \"insert\": true,\n    },\n    \"refreshPermissions\": [Function],\n    \"resolvePermissions\": [Function],\n    \"resolvedPermissions\": {},\n  },\n  \"plugins\": [],\n  \"resolveAndCommitData\": [Function],\n  \"resolveComponentData\": [Function],\n  \"selectedItem\": null,\n  \"setComponentLoading\": [Function],\n  \"setComponentState\": [Function],\n  \"setStatus\": [Function],\n  \"setUi\": [Function],\n  \"setZoomConfig\": [Function],\n  \"state\": {\n    \"data\": {\n      \"content\": [],\n      \"root\": {\n        \"props\": {},\n      },\n      \"zones\": {},\n    },\n    \"indexes\": {\n      \"nodes\": {\n        \"root\": {\n          \"data\": {\n            \"props\": {\n              \"id\": \"root\",\n            },\n            \"type\": \"root\",\n          },\n          \"flatData\": {\n            \"props\": {\n              \"id\": \"root\",\n            },\n            \"type\": \"root\",\n          },\n          \"parentId\": null,\n          \"path\": [],\n          \"zone\": \"\",\n        },\n      },\n      \"zones\": {\n        \"root:default-zone\": {\n          \"contentIds\": [],\n          \"type\": \"root\",\n        },\n      },\n    },\n    \"ui\": {\n      \"arrayState\": {},\n      \"componentList\": {},\n      \"field\": {\n        \"focus\": null,\n      },\n      \"isDragging\": false,\n      \"itemSelector\": null,\n      \"leftSideBarVisible\": false,\n      \"plugin\": {\n        \"current\": \"blocks\",\n      },\n      \"previewMode\": \"edit\",\n      \"rightSideBarVisible\": false,\n      \"viewports\": {\n        \"controlsVisible\": true,\n        \"current\": {\n          \"height\": \"auto\",\n          \"width\": 360,\n        },\n        \"options\": [],\n      },\n    },\n  },\n  \"status\": \"READY\",\n  \"unsetComponentLoading\": [Function],\n  \"viewports\": [\n    {\n      \"height\": \"auto\",\n      \"icon\": \"Smartphone\",\n      \"label\": \"Small\",\n      \"width\": 360,\n    },\n    {\n      \"height\": \"auto\",\n      \"icon\": \"Tablet\",\n      \"label\": \"Medium\",\n      \"width\": 768,\n    },\n    {\n      \"height\": \"auto\",\n      \"icon\": \"Monitor\",\n      \"label\": \"Large\",\n      \"width\": 1280,\n    },\n    {\n      \"height\": \"auto\",\n      \"icon\": \"FullWidth\",\n      \"label\": \"Full-width\",\n      \"width\": \"100%\",\n    },\n  ],\n  \"zoomConfig\": {\n    \"autoZoom\": NaN,\n    \"rootHeight\": 0,\n    \"zoom\": NaN,\n  },\n}\n`;\n"
  },
  {
    "path": "packages/core/components/Puck/__tests__/index.tsx",
    "content": "import { act, render, screen } from \"@testing-library/react\";\nimport { Config } from \"../../../types\";\nimport \"@testing-library/jest-dom\";\n\njest.mock(\"../styles.module.css\");\njest.mock(\"@dnd-kit/react\");\n\nObject.defineProperty(window, \"matchMedia\", {\n  writable: true,\n  value: (query: string) => ({\n    matches: false, // default → desktop\n    media: query,\n    onchange: null,\n    addEventListener: jest.fn(),\n    removeEventListener: jest.fn(),\n    addListener: jest.fn(), // ⬅️ legacy APIs some libs still call\n    removeListener: jest.fn(),\n    dispatchEvent: jest.fn(),\n  }),\n});\n\njest.mock(\"@dnd-kit/react\", () => {\n  const original = jest.requireActual(\"@dnd-kit/react\");\n  return {\n    ...original,\n    // Provider becomes a no-op wrapper\n    DragDropProvider: ({ children }: { children: React.ReactNode }) => (\n      <>{children}</>\n    ),\n\n    // Hooks return dummy objects so destructuring works\n    useDroppable: () => ({\n      ref: () => undefined,\n      setNodeRef: () => undefined,\n      isOver: false,\n    }),\n    useDraggable: () => ({\n      attributes: {},\n      listeners: {},\n      setNodeRef: () => undefined,\n      isDragging: false,\n    }),\n  };\n});\n\nclass ResizeObserver {\n  observe() {}\n  unobserve() {}\n  disconnect() {}\n}\n(global as any).ResizeObserver = ResizeObserver;\n\ntype PuckInternal = {\n  appStore: AppStoreApi;\n};\n\nconst getInternal = () => {\n  return (window as any).__PUCK_INTERNAL_DO_NOT_USE as PuckInternal;\n};\n\nimport { Puck } from \"../index\";\nimport { AppStoreApi } from \"../../../store\";\n\ndescribe(\"Puck\", () => {\n  const componentARender = jest.fn(() => null);\n  const componentBRender = jest.fn(() => null);\n  const rootRender = jest.fn(() => null);\n\n  const config: Config = {\n    root: {\n      render: ({ children }) => {\n        rootRender();\n        return <div>Root{children}</div>;\n      },\n    },\n    components: {\n      componentA: {\n        render: () => {\n          componentARender();\n          return <div>Component A</div>;\n        },\n      },\n      componentB: {\n        render: () => {\n          componentBRender();\n          return <div>Component A</div>;\n        },\n      },\n    },\n  };\n\n  afterEach(() => {\n    rootRender.mockClear();\n    componentARender.mockClear();\n    componentBRender.mockClear();\n  });\n\n  // flush any queued state updates\n  const flush = () => act(async () => {});\n\n  it(\"root renders\", async () => {\n    render(<Puck config={config} data={{}} iframe={{ enabled: false }} />);\n\n    await flush();\n\n    expect(rootRender).toHaveBeenCalled();\n    expect(screen.getByText(\"Root\")).toBeInTheDocument();\n  });\n\n  it(\"should generate the correct state on mount\", async () => {\n    render(<Puck config={config} data={{}} iframe={{ enabled: false }} />);\n\n    await flush();\n\n    const { appStore } = getInternal();\n\n    expect(appStore.getState()).toMatchSnapshot();\n  });\n\n  it(\"should index slots on mount\", async () => {\n    render(\n      <Puck\n        config={{\n          root: {\n            fields: {\n              content: { type: \"slot\" },\n            },\n          },\n          components: {},\n        }}\n        data={{\n          root: {\n            props: {\n              content: [],\n            },\n          },\n        }}\n        iframe={{ enabled: false }}\n      />\n    );\n\n    await flush();\n\n    const { appStore } = getInternal();\n\n    expect(appStore.getState().state.indexes).toMatchInlineSnapshot(`\n      {\n        \"nodes\": {\n          \"root\": {\n            \"data\": {\n              \"props\": {\n                \"content\": [],\n                \"id\": \"root\",\n              },\n              \"type\": \"root\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"content\": null,\n                \"id\": \"root\",\n              },\n              \"type\": \"root\",\n            },\n            \"parentId\": null,\n            \"path\": [],\n            \"zone\": \"\",\n          },\n        },\n        \"zones\": {\n          \"root:content\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"root:default-zone\": {\n            \"contentIds\": [],\n            \"type\": \"root\",\n          },\n        },\n      }\n    `);\n  });\n});\n"
  },
  {
    "path": "packages/core/components/Puck/components/Canvas/index.tsx",
    "content": "import { getBox } from \"css-box-model\";\nimport {\n  ReactNode,\n  useCallback,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport { useAppStore, useAppStoreApi } from \"../../../../store\";\nimport { ViewportControls } from \"../../../ViewportControls\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory, useResetAutoZoom } from \"../../../../lib\";\nimport { Preview } from \"../Preview\";\nimport { UiState } from \"../../../../types\";\nimport { Loader } from \"../../../Loader\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useCanvasFrame } from \"../../../../lib/frame-context\";\nimport { usePropsContext } from \"../..\";\nimport { defaultViewports } from \"../../../ViewportControls/default-viewports\";\n\nconst getClassName = getClassNameFactory(\"PuckCanvas\", styles);\n\nconst ZOOM_ON_CHANGE = true;\nconst TRANSITION_DURATION = 150;\n\nexport const Canvas = () => {\n  const { frameRef } = useCanvasFrame();\n  const resetAutoZoom = useResetAutoZoom(frameRef);\n\n  const {\n    _experimentalFullScreenCanvas,\n    viewports: viewportOptions = defaultViewports,\n    ui: uiProp,\n  } = usePropsContext();\n\n  const {\n    dispatch,\n    overrides,\n    setUi,\n    zoomConfig,\n    setZoomConfig,\n    status,\n    iframe,\n  } = useAppStore(\n    useShallow((s) => ({\n      dispatch: s.dispatch,\n      overrides: s.overrides,\n      setUi: s.setUi,\n      zoomConfig: s.zoomConfig,\n      setZoomConfig: s.setZoomConfig,\n      status: s.status,\n      iframe: s.iframe,\n    }))\n  );\n  const {\n    leftSideBarVisible,\n    rightSideBarVisible,\n    leftSideBarWidth,\n    rightSideBarWidth,\n    viewports,\n  } = useAppStore(\n    useShallow((s) => ({\n      leftSideBarVisible: s.state.ui.leftSideBarVisible,\n      rightSideBarVisible: s.state.ui.rightSideBarVisible,\n      leftSideBarWidth: s.state.ui.leftSideBarWidth,\n      rightSideBarWidth: s.state.ui.rightSideBarWidth,\n      viewports: s.state.ui.viewports,\n    }))\n  );\n\n  const [showTransition, setShowTransition] = useState(false);\n  const isResizingRef = useRef(false);\n\n  const defaultRender = useMemo<\n    React.FunctionComponent<{ children?: ReactNode }>\n  >(() => {\n    const PuckDefault = ({ children }: { children?: ReactNode }) => (\n      <>{children}</>\n    );\n\n    return PuckDefault;\n  }, []);\n\n  const CustomPreview = useMemo(\n    () => overrides.preview || defaultRender,\n    [overrides]\n  );\n\n  const getFrameDimensions = useCallback(() => {\n    if (frameRef.current) {\n      const frame = frameRef.current;\n\n      const box = getBox(frame);\n\n      return { width: box.contentBox.width, height: box.contentBox.height };\n    }\n\n    return { width: 0, height: 0 };\n  }, [frameRef]);\n\n  // Auto zoom\n  useEffect(() => {\n    resetAutoZoom();\n  }, [\n    frameRef,\n    leftSideBarVisible,\n    rightSideBarVisible,\n    leftSideBarWidth,\n    rightSideBarWidth,\n    viewports,\n  ]);\n\n  // Constrain height\n  useEffect(() => {\n    const { height: frameHeight } = getFrameDimensions();\n\n    if (viewports.current.height === \"auto\") {\n      setZoomConfig({\n        ...zoomConfig,\n        rootHeight: frameHeight / zoomConfig.zoom,\n      });\n    }\n  }, [zoomConfig.zoom, getFrameDimensions, setZoomConfig]);\n\n  // Zoom whenever state changes, even if external driver\n  useEffect(() => {\n    if (ZOOM_ON_CHANGE) {\n      resetAutoZoom();\n    }\n  }, [viewports.current.width, viewports]);\n\n  // Resize based on frame size\n  useEffect(() => {\n    if (!frameRef.current) return;\n\n    const resizeObserver = new ResizeObserver(() => {\n      if (!isResizingRef.current) {\n        resetAutoZoom();\n      }\n    });\n\n    resizeObserver.observe(frameRef.current);\n\n    return () => {\n      resizeObserver.disconnect();\n    };\n  }, [frameRef.current]);\n\n  const [showLoader, setShowLoader] = useState(false);\n\n  useEffect(() => {\n    setTimeout(() => {\n      setShowLoader(true);\n    }, 500);\n  }, []);\n\n  const appStoreApi = useAppStoreApi();\n\n  // Select closest viewport on load\n  useEffect(() => {\n    if (typeof window === \"undefined\") return;\n\n    // Don't override if user has set a viewport\n    if (uiProp?.viewports?.current) return;\n\n    const viewportWidth = window.innerWidth;\n    const frameWidth = frameRef.current?.getBoundingClientRect().width;\n\n    if (!viewportWidth) return;\n    if (!frameWidth) return;\n    if (viewportOptions.length === 0) return;\n\n    const fullWidthViewport = Object.values(viewportOptions).find(\n      (v) => v.width === \"100%\"\n    );\n\n    const containsFullWidthViewport = !!fullWidthViewport;\n\n    const viewportDifferences = Object.entries(viewportOptions)\n      .filter(([_, value]) => value.width !== \"100%\")\n      .map(([key, value]) => ({\n        key,\n        diff: Math.abs(\n          viewportWidth -\n            (typeof value.width === \"string\" ? viewportWidth : value.width)\n        ),\n        value,\n      }))\n      .sort((a, b) => (a.diff > b.diff ? 1 : -1));\n\n    let closestViewport = viewportDifferences[0].value;\n\n    // Select full width viewport if it exists, and the closest viewport is smaller than the window\n    if (\n      (closestViewport.width as number) < frameWidth &&\n      containsFullWidthViewport\n    ) {\n      closestViewport = fullWidthViewport;\n    }\n\n    if (iframe.enabled) {\n      const s = appStoreApi.getState();\n\n      const appState = {\n        state: {\n          ...s.state,\n          ui: {\n            ...s.state.ui,\n            viewports: {\n              ...s.state.ui.viewports,\n\n              current: {\n                ...s.state.ui.viewports.current,\n                height: closestViewport?.height || \"auto\",\n                width: closestViewport?.width,\n              },\n            },\n          },\n        },\n      };\n\n      let history = s.history;\n\n      if (s.history.histories.length === 1) {\n        history = { ...history, histories: [appState] };\n      }\n\n      appStoreApi.setState({ ...appState, history });\n    }\n  }, [\n    viewportOptions,\n    frameRef.current,\n    iframe,\n    appStoreApi,\n    uiProp?.viewports?.current,\n  ]);\n\n  return (\n    <div\n      className={getClassName({\n        ready: status === \"READY\" || !iframe.enabled || !iframe.waitForStyles,\n        showLoader,\n        fullScreen: _experimentalFullScreenCanvas,\n      })}\n      onClick={(e) => {\n        const el = e.target as Element;\n\n        if (\n          !el.hasAttribute(\"data-puck-component\") &&\n          !el.hasAttribute(\"data-puck-dropzone\")\n        ) {\n          dispatch({\n            type: \"setUi\",\n            ui: { itemSelector: null },\n            recordHistory: true,\n          });\n        }\n      }}\n    >\n      {viewports.controlsVisible && iframe.enabled && (\n        <div className={getClassName(\"controls\")}>\n          <ViewportControls\n            fullScreen={_experimentalFullScreenCanvas}\n            autoZoom={zoomConfig.autoZoom}\n            zoom={zoomConfig.zoom}\n            onViewportChange={(viewport) => {\n              setShowTransition(true);\n              isResizingRef.current = true;\n\n              const uiViewport = {\n                ...viewport,\n                height: viewport.height || \"auto\",\n                zoom: zoomConfig.zoom,\n              };\n\n              const newUi: Partial<UiState> = {\n                viewports: { ...viewports, current: uiViewport },\n              };\n\n              setUi(newUi);\n\n              if (ZOOM_ON_CHANGE) {\n                resetAutoZoom({\n                  viewports: { ...viewports, current: uiViewport },\n                });\n              }\n            }}\n            onZoom={(zoom) => {\n              setShowTransition(true);\n              isResizingRef.current = true;\n\n              setZoomConfig({ ...zoomConfig, zoom });\n            }}\n          />\n        </div>\n      )}\n      <div className={getClassName(\"inner\")} ref={frameRef}>\n        <div\n          className={getClassName(\"root\")}\n          style={{\n            width: iframe.enabled ? viewports.current.width : \"100%\",\n            height: zoomConfig.rootHeight,\n            transform: iframe.enabled ? `scale(${zoomConfig.zoom})` : undefined,\n            transition: showTransition\n              ? `width ${TRANSITION_DURATION}ms ease-out, height ${TRANSITION_DURATION}ms ease-out, transform ${TRANSITION_DURATION}ms ease-out`\n              : \"\",\n            overflow: iframe.enabled ? undefined : \"auto\",\n          }}\n          suppressHydrationWarning // Suppress hydration warning as frame is not visible until after load\n          id=\"puck-canvas-root\"\n          onTransitionEnd={() => {\n            setShowTransition(false);\n            isResizingRef.current = false;\n          }}\n        >\n          <CustomPreview>\n            <Preview />\n          </CustomPreview>\n        </div>\n        <div className={getClassName(\"loader\")}>\n          <Loader size={24} />\n        </div>\n      </div>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Canvas/styles.module.css",
    "content": ".PuckCanvas {\n  background: var(--puck-color-grey-11);\n  display: flex;\n  grid-area: editor;\n  flex-direction: column;\n  padding: var(--puck-space-px);\n  position: relative;\n  overflow: auto;\n}\n\n@media (min-width: 1198px) {\n  .PuckCanvas {\n    padding: calc(var(--puck-space-px) * 1.5);\n    padding-top: calc(var(--puck-space-px) * 0.5);\n  }\n\n  .PuckCanvas:not(.PuckCanvas:has(.PuckCanvas-controls)) {\n    padding-top: calc(var(--puck-space-px) * 1.5);\n  }\n}\n\n.PuckCanvas--fullScreen {\n  padding: 0;\n  overflow: hidden;\n}\n\n@media (min-width: 1198px) {\n  .PuckCanvas--fullScreen {\n    padding: 0;\n  }\n}\n\n.PuckCanvas-inner {\n  display: flex;\n  height: 100%;\n  justify-content: center;\n  min-width: 288px;\n  position: relative;\n  width: 100%;\n}\n\n.PuckCanvas-root {\n  background: white;\n  outline: 1px solid var(--puck-color-grey-09);\n  box-sizing: content-box;\n  min-width: 321px;\n  position: absolute;\n  pointer-events: none;\n  transform-origin: top;\n  top: 0;\n  bottom: 0;\n  opacity: 0;\n}\n\n@media (min-width: 1198px) {\n  .PuckCanvas-root {\n    min-width: unset;\n  }\n}\n\n@media (prefers-reduced-motion: reduce) {\n  .PuckCanvas-root {\n    transition: none !important;\n  }\n}\n\n.PuckCanvas--ready .PuckCanvas-root {\n  pointer-events: unset;\n  opacity: 1;\n}\n\n.PuckCanvas-loader {\n  align-items: center;\n  color: var(--puck-color-grey-06);\n  display: flex;\n  height: 100%;\n  justify-content: center;\n  transition: opacity 250ms ease-out;\n  opacity: 0;\n}\n\n.PuckCanvas--showLoader .PuckCanvas-loader {\n  opacity: 1;\n}\n\n.PuckCanvas--showLoader.PuckCanvas--ready .PuckCanvas-loader {\n  opacity: 0;\n  height: 0;\n  transition: none;\n}\n\n.PuckCanvas-controls {\n  padding-bottom: calc(var(--puck-space-px) * 0.5);\n}\n\n.PuckCanvas--fullScreen .PuckCanvas-controls {\n  padding-bottom: 0;\n  z-index: 1;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/Components/index.tsx",
    "content": "import { useComponentList } from \"../../../../lib/use-component-list\";\nimport { useAppStore } from \"../../../../store\";\nimport { ComponentList } from \"../../../ComponentList\";\nimport { useMemo } from \"react\";\n\nexport const Components = () => {\n  const overrides = useAppStore((s) => s.overrides);\n\n  const componentList = useComponentList();\n\n  const Wrapper = useMemo(() => {\n    // DEPRECATED\n    if (overrides.components) {\n      console.warn(\n        \"The `components` override has been deprecated and renamed to `drawer`\"\n      );\n    }\n    return overrides.components || overrides.drawer || \"div\";\n  }, [overrides]);\n\n  return (\n    <Wrapper>\n      {componentList ? componentList : <ComponentList id=\"all\" />}\n    </Wrapper>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Fields/index.tsx",
    "content": "import { Loader } from \"../../../Loader\";\nimport { rootDroppableId } from \"../../../../lib/root-droppable-id\";\nimport { ItemSelector } from \"../../../../lib/data/get-item\";\nimport { getSelectorForId } from \"../../../../lib/get-selector-for-id\";\nimport { UiState } from \"../../../../types\";\nimport { AutoFieldPrivate } from \"../../../AutoField\";\nimport { fieldContextStore } from \"../../../AutoField/store\";\nimport { AppStore, useAppStore, useAppStoreApi } from \"../../../../store\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"../../../../lib\";\nimport {\n  memo,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n} from \"react\";\nimport { useRegisterFieldsSlice } from \"../../../../store/slices/fields\";\nimport { useShallow } from \"zustand/react/shallow\";\nimport { StoreApi } from \"zustand\";\n\nconst getClassName = getClassNameFactory(\"PuckFields\", styles);\n\nconst DefaultFields = ({\n  children,\n}: {\n  children: ReactNode;\n  isLoading: boolean;\n  itemSelector?: ItemSelector | null;\n}) => {\n  return <>{children}</>;\n};\n\nconst createOnChange =\n  (fieldName: string, appStore: StoreApi<AppStore>) =>\n  async (value: any, updatedUi?: Partial<UiState>) => {\n    const { dispatch, state, selectedItem, resolveComponentData } =\n      appStore.getState();\n\n    const { data, ui } = state;\n    const { itemSelector } = ui;\n\n    // DEPRECATED: root without props object\n    const rootProps = data.root.props || data.root;\n    const currentProps = selectedItem ? selectedItem.props : rootProps;\n\n    const newProps = { ...currentProps, [fieldName]: value };\n\n    if (selectedItem && itemSelector) {\n      const resolved = await resolveComponentData(\n        { ...selectedItem, props: newProps },\n        \"replace\"\n      );\n\n      const latestSelector = getSelectorForId(\n        appStore.getState().state,\n        selectedItem.props.id\n      );\n      if (!latestSelector) return;\n\n      dispatch({\n        type: \"replace\",\n        destinationIndex: latestSelector.index,\n        destinationZone: latestSelector.zone || rootDroppableId,\n        data: resolved.node,\n        ui: updatedUi,\n      });\n\n      return;\n    }\n\n    if (data.root.props) {\n      dispatch({\n        type: \"replaceRoot\",\n        root: (\n          await resolveComponentData(\n            { ...data.root, props: newProps },\n            \"replace\"\n          )\n        ).node,\n        ui: { ...ui, ...updatedUi },\n        recordHistory: true,\n      });\n\n      return;\n    }\n\n    // DEPRECATED: root without props object\n    dispatch({\n      type: \"setData\",\n      data: { root: newProps },\n    });\n  };\n\nconst FieldsChildInner = ({ fieldName }: { fieldName: string }) => {\n  const field = useAppStore((s) => s.fields.fields[fieldName]);\n  const isReadOnly = useAppStore(\n    (s) =>\n      ((s.selectedItem\n        ? s.selectedItem.readOnly\n        : s.state.data.root.readOnly) || {})[fieldName]\n  );\n\n  const id = useAppStore((s) => {\n    if (!field) return null;\n\n    return s.selectedItem\n      ? `${s.selectedItem.props.id}_${field.type}_${fieldName}`\n      : `root_${field.type}_${fieldName}`;\n  });\n\n  const permissions = useAppStore(\n    useShallow((s) => {\n      const { selectedItem, permissions } = s;\n\n      return selectedItem\n        ? permissions.getPermissions({ item: selectedItem })\n        : permissions.getPermissions({ root: true });\n    })\n  );\n\n  const appStore = useAppStoreApi();\n\n  const onChange = useCallback(createOnChange(fieldName, appStore), [\n    fieldName,\n  ]);\n\n  const { visible = true } = field ?? {};\n\n  const fieldStore = useContext(fieldContextStore.ctx);\n\n  useEffect(() => {\n    return appStore.subscribe(\n      (s) => {\n        const data = s.getCurrentData();\n\n        return data.props?.[fieldName];\n      },\n      (value) => {\n        fieldStore.setState({ [fieldName]: value });\n      }\n    );\n  }, [appStore, fieldStore]);\n\n  if (!field || !id || !visible) return null;\n\n  if (field.type === \"slot\") return null;\n\n  return (\n    <div key={id} className={getClassName(\"field\")}>\n      <AutoFieldPrivate\n        field={field}\n        name={fieldName}\n        id={id}\n        readOnly={!permissions.edit || isReadOnly}\n        onChange={onChange}\n      />\n    </div>\n  );\n};\n\nconst FieldsChild = ({ fieldName }: { fieldName: string }) => {\n  const appStore = useAppStoreApi();\n\n  const initialValue = useMemo(() => {\n    const value = appStore.getState().getCurrentData().props?.[fieldName];\n\n    return { [fieldName]: value };\n  }, []);\n\n  return (\n    <fieldContextStore.Provider value={initialValue}>\n      <FieldsChildInner fieldName={fieldName} />\n    </fieldContextStore.Provider>\n  );\n};\n\nconst FieldsChildMemo = memo(FieldsChild);\n\nconst FieldsInternal = ({ wrapFields = true }: { wrapFields?: boolean }) => {\n  const overrides = useAppStore((s) => s.overrides);\n  const componentResolving = useAppStore((s) => {\n    const loadingCount = s.selectedItem\n      ? s.componentState[s.selectedItem.props.id]?.loadingCount\n      : s.componentState[\"root\"]?.loadingCount;\n\n    return (loadingCount ?? 0) > 0;\n  });\n  const itemSelector = useAppStore(useShallow((s) => s.state.ui.itemSelector));\n  const id = useAppStore((s) => s.selectedItem?.props.id);\n  const appStore = useAppStoreApi();\n  useRegisterFieldsSlice(appStore, id);\n\n  const fieldsLoading = useAppStore((s) => s.fields.loading);\n  const fieldNames = useAppStore(\n    useShallow((s) => {\n      if (s.fields.id === id) {\n        return Object.keys(s.fields.fields);\n      }\n\n      return [];\n    })\n  );\n\n  const isLoading = fieldsLoading || componentResolving;\n\n  const Wrapper = useMemo(() => overrides.fields || DefaultFields, [overrides]);\n\n  return (\n    <form\n      className={getClassName({ wrapFields })}\n      onSubmit={(e) => {\n        e.preventDefault();\n      }}\n    >\n      <Wrapper isLoading={isLoading} itemSelector={itemSelector}>\n        {fieldNames.map((fieldName) => (\n          <FieldsChildMemo key={fieldName} fieldName={fieldName} />\n        ))}\n      </Wrapper>\n      {isLoading && (\n        <div className={getClassName(\"loadingOverlay\")}>\n          <div className={getClassName(\"loadingOverlayInner\")}>\n            <Loader size={16} />\n          </div>\n        </div>\n      )}\n    </form>\n  );\n};\n\nexport const Fields = memo(FieldsInternal);\n"
  },
  {
    "path": "packages/core/components/Puck/components/Fields/styles.module.css",
    "content": ".PuckFields {\n  position: relative;\n  font-family: var(--puck-font-family);\n}\n\n.PuckFields--isLoading {\n  min-height: 48px; /* Ensure there is sufficient room for loader if no fields */\n}\n\n.PuckFields-loadingOverlay {\n  background: var(--puck-color-white);\n  display: flex;\n  justify-content: flex-end;\n  align-items: flex-start;\n  height: 100%;\n  width: 100%;\n  top: 0px;\n  position: absolute;\n  z-index: 1;\n  pointer-events: all;\n  box-sizing: border-box;\n  opacity: 0.8;\n}\n\n.PuckFields-loadingOverlayInner {\n  display: flex;\n  padding: 16px;\n  position: sticky;\n  top: 0;\n}\n\n.PuckFields-field * {\n  box-sizing: border-box;\n}\n\n.PuckFields--wrapFields .PuckFields-field {\n  color: var(--puck-color-grey-04);\n  padding: 16px;\n  padding-bottom: 12px;\n  display: block;\n}\n\n.PuckFields--wrapFields .PuckFields-field + .PuckFields-field {\n  border-top: 1px solid var(--puck-color-grey-09);\n  margin-top: 8px;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/Header/index.tsx",
    "content": "import { memo, useCallback, useMemo, useState } from \"react\";\nimport { useAppStore, useAppStoreApi } from \"../../../../store\";\nimport {\n  ChevronDown,\n  ChevronUp,\n  Globe,\n  PanelLeft,\n  PanelRight,\n} from \"lucide-react\";\nimport { Heading } from \"../../../Heading\";\nimport { IconButton } from \"../../../IconButton/IconButton\";\nimport { MenuBar } from \"../../../MenuBar\";\nimport { Button } from \"../../../Button\";\nimport { Config, Overrides, UserGenerics } from \"../../../../types\";\nimport { DefaultOverride } from \"../../../DefaultOverride\";\nimport { usePropsContext } from \"../..\";\nimport { getClassNameFactory } from \"../../../../lib\";\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"PuckHeader\", styles);\n\nconst HeaderInner = <\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>({\n  hidePlugins,\n}: {\n  hidePlugins: boolean;\n}) => {\n  const {\n    onPublish,\n    renderHeader,\n    renderHeaderActions,\n    headerTitle,\n    headerPath,\n    iframe: _iframe,\n  } = usePropsContext();\n\n  const dispatch = useAppStore((s) => s.dispatch);\n  const appStore = useAppStoreApi();\n\n  // DEPRECATED\n  const defaultHeaderRender = useMemo((): Overrides[\"header\"] => {\n    if (renderHeader) {\n      console.warn(\n        \"`renderHeader` is deprecated. Please use `overrides.header` and the `usePuck` hook instead\"\n      );\n\n      const RenderHeader = ({ actions, ...props }: any) => {\n        const Comp = renderHeader!;\n\n        const appState = useAppStore((s) => s.state);\n\n        return (\n          <Comp {...props} dispatch={dispatch} state={appState}>\n            {actions}\n          </Comp>\n        );\n      };\n\n      return RenderHeader;\n    }\n\n    return DefaultOverride;\n  }, [renderHeader]);\n\n  // DEPRECATED\n  const defaultHeaderActionsRender = useMemo((): Overrides[\"headerActions\"] => {\n    if (renderHeaderActions) {\n      console.warn(\n        \"`renderHeaderActions` is deprecated. Please use `overrides.headerActions` and the `usePuck` hook instead.\"\n      );\n\n      const RenderHeader = (props: any) => {\n        const Comp = renderHeaderActions!;\n\n        const appState = useAppStore((s) => s.state);\n\n        return <Comp {...props} dispatch={dispatch} state={appState}></Comp>;\n      };\n\n      return RenderHeader;\n    }\n\n    return DefaultOverride;\n  }, [renderHeaderActions]);\n\n  const CustomHeader = useAppStore(\n    (s) => s.overrides.header || defaultHeaderRender\n  );\n\n  const CustomHeaderActions = useAppStore(\n    (s) => s.overrides.headerActions || defaultHeaderActionsRender\n  );\n\n  const [menuOpen, setMenuOpen] = useState(false);\n\n  const rootTitle = useAppStore((s) => {\n    const rootData = s.state.indexes.nodes[\"root\"]?.data as G[\"UserRootProps\"];\n\n    return rootData.props.title ?? \"\";\n  });\n\n  const leftSideBarVisible = useAppStore((s) => s.state.ui.leftSideBarVisible);\n  const rightSideBarVisible = useAppStore(\n    (s) => s.state.ui.rightSideBarVisible\n  );\n\n  const toggleSidebars = useCallback(\n    (sidebar: \"left\" | \"right\") => {\n      const widerViewport = window.matchMedia(\"(min-width: 638px)\").matches;\n      const sideBarVisible =\n        sidebar === \"left\" ? leftSideBarVisible : rightSideBarVisible;\n      const oppositeSideBar =\n        sidebar === \"left\" ? \"rightSideBarVisible\" : \"leftSideBarVisible\";\n\n      dispatch({\n        type: \"setUi\",\n        ui: {\n          [`${sidebar}SideBarVisible`]: !sideBarVisible,\n          ...(!widerViewport ? { [oppositeSideBar]: false } : {}),\n        },\n      });\n    },\n    [dispatch, leftSideBarVisible, rightSideBarVisible]\n  );\n\n  return (\n    <CustomHeader\n      actions={\n        <>\n          <CustomHeaderActions>\n            <Button\n              onClick={() => {\n                const data = appStore.getState().state.data;\n                onPublish && onPublish(data as G[\"UserData\"]);\n              }}\n              icon={<Globe size=\"14px\" />}\n            >\n              Publish\n            </Button>\n          </CustomHeaderActions>\n        </>\n      }\n    >\n      <header\n        className={getClassName({\n          leftSideBarVisible,\n          rightSideBarVisible,\n          hidePlugins,\n        })}\n      >\n        <div className={getClassName(\"inner\")}>\n          <div className={getClassName(\"toggle\")}>\n            <div className={getClassName(\"leftSideBarToggle\")}>\n              <IconButton\n                type=\"button\"\n                onClick={() => {\n                  toggleSidebars(\"left\");\n                }}\n                title=\"Toggle left sidebar\"\n              >\n                <PanelLeft focusable=\"false\" />\n              </IconButton>\n            </div>\n            <div className={getClassName(\"rightSideBarToggle\")}>\n              <IconButton\n                type=\"button\"\n                onClick={() => {\n                  toggleSidebars(\"right\");\n                }}\n                title=\"Toggle right sidebar\"\n              >\n                <PanelRight focusable=\"false\" />\n              </IconButton>\n            </div>\n          </div>\n          <div className={getClassName(\"title\")}>\n            <Heading rank=\"2\" size=\"xs\">\n              {headerTitle || rootTitle || \"Page\"}\n              {headerPath && (\n                <>\n                  {\" \"}\n                  <code className={getClassName(\"path\")}>{headerPath}</code>\n                </>\n              )}\n            </Heading>\n          </div>\n          <div className={getClassName(\"tools\")}>\n            <div className={getClassName(\"menuButton\")}>\n              <IconButton\n                type=\"button\"\n                onClick={() => {\n                  return setMenuOpen(!menuOpen);\n                }}\n                title=\"Toggle menu bar\"\n              >\n                {menuOpen ? (\n                  <ChevronUp focusable=\"false\" />\n                ) : (\n                  <ChevronDown focusable=\"false\" />\n                )}\n              </IconButton>\n            </div>\n            <MenuBar<G[\"UserData\"]>\n              dispatch={dispatch}\n              onPublish={onPublish}\n              menuOpen={menuOpen}\n              renderHeaderActions={() => (\n                <CustomHeaderActions>\n                  <Button\n                    onClick={() => {\n                      const data = appStore.getState().state\n                        .data as G[\"UserData\"];\n                      onPublish && onPublish(data);\n                    }}\n                    icon={<Globe size=\"14px\" />}\n                  >\n                    Publish\n                  </Button>\n                </CustomHeaderActions>\n              )}\n              setMenuOpen={setMenuOpen}\n            />\n          </div>\n        </div>\n      </header>\n    </CustomHeader>\n  );\n};\n\nexport const Header = memo(HeaderInner);\n"
  },
  {
    "path": "packages/core/components/Puck/components/Header/styles.module.css",
    "content": ".PuckHeader {\n  background: var(--puck-color-white);\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  color: var(--puck-color-black);\n  grid-area: header;\n  position: relative;\n  max-width: 100vw;\n}\n\n@media (min-width: 638px) {\n  .PuckHeader {\n    padding-left: 67px;\n  }\n\n  .PuckHeader--hidePlugins {\n    padding-left: 0;\n  }\n}\n\n.PuckHeader-inner {\n  align-items: end;\n  display: grid;\n  gap: var(--puck-space-px);\n  grid-template-areas: \"left middle right\";\n  grid-template-columns: 1fr auto 1fr;\n  grid-template-rows: auto;\n  padding: var(--puck-space-px);\n}\n\n@media (min-width: 638px) {\n  .PuckHeader-inner {\n    border-left: 1px solid var(--puck-color-grey-09);\n  }\n\n  .PuckHeader--hidePlugins .PuckHeader-inner {\n    border-left: none;\n  }\n}\n\n.PuckHeader-toggle {\n  color: var(--puck-color-grey-05);\n  display: flex;\n  margin-inline-start: -4px;\n  padding-top: 2px;\n}\n\n.PuckHeader--rightSideBarVisible .PuckHeader-rightSideBarToggle,\n.PuckHeader--leftSideBarVisible .PuckHeader-leftSideBarToggle {\n  color: var(--puck-color-black);\n}\n\n.PuckHeader-rightSideBarToggle,\n.PuckHeader-leftSideBarToggle {\n  display: none;\n}\n\n@media (min-width: 638px) {\n  .PuckHeader-rightSideBarToggle,\n  .PuckHeader-leftSideBarToggle {\n    display: block;\n  }\n}\n\n.PuckHeader-title {\n  align-self: center;\n}\n\n.PuckHeader-path {\n  font-family: var(--puck-font-family-monospaced);\n  font-size: var(--puck-font-size-xxs);\n  font-weight: normal;\n  word-break: break-all;\n}\n\n.PuckHeader-tools {\n  display: flex;\n  gap: 16px;\n  justify-content: flex-end;\n}\n\n.PuckHeader-menuButton {\n  color: var(--puck-color-grey-05);\n  margin-inline-start: -4px;\n}\n\n.PuckHeader--menuOpen .PuckHeader-menuButton {\n  color: var(--puck-color-black);\n}\n\n@media (min-width: 638px) {\n  .PuckHeader-menuButton {\n    display: none;\n  }\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/Layout/index.tsx",
    "content": "import { ReactElement, ReactNode, useEffect, useMemo, useState } from \"react\";\nimport { getClassNameFactory } from \"../../../../lib\";\nimport { IframeConfig, UiState } from \"../../../../types\";\nimport { usePropsContext } from \"../..\";\nimport styles from \"./styles.module.css\";\nimport { useInjectGlobalCss } from \"../../../../lib/use-inject-css\";\nimport { useAppStore, useAppStoreApi } from \"../../../../store\";\nimport { DefaultOverride } from \"../../../DefaultOverride\";\nimport { monitorHotkeys, useMonitorHotkeys } from \"../../../../lib/use-hotkey\";\nimport { getFrame } from \"../../../../lib/get-frame\";\nimport { usePreviewModeHotkeys } from \"../../../../lib/use-preview-mode-hotkeys\";\nimport { DragDropContext } from \"../../../DragDropContext\";\nimport { Header } from \"../Header\";\nimport { SidebarSection } from \"../../../SidebarSection\";\nimport { Canvas } from \"../Canvas\";\nimport { Fields } from \"../Fields\";\nimport { useSidebarResize } from \"../../../../lib/use-sidebar-resize\";\nimport { FrameProvider } from \"../../../../lib/frame-context\";\nimport { Sidebar } from \"../Sidebar\";\nimport { useDeleteHotkeys } from \"../../../../lib/use-delete-hotkeys\";\nimport { MenuItem, Nav } from \"../Nav\";\nimport { IconButton } from \"../../../IconButton\";\nimport { Maximize2, Minimize2, ToyBrick } from \"lucide-react\";\nimport { PluginInternal } from \"../../../../types/Internal\";\nimport { blocksPlugin } from \"../../../../plugins/blocks\";\nimport { outlinePlugin } from \"../../../../plugins/outline\";\nimport { fieldsPlugin } from \"../../../../plugins/fields\";\n\nconst getClassName = getClassNameFactory(\"Puck\", styles);\nconst getLayoutClassName = getClassNameFactory(\"PuckLayout\", styles);\nconst getPluginTabClassName = getClassNameFactory(\"PuckPluginTab\", styles);\n\nconst FieldSideBar = () => {\n  const title = useAppStore((s) =>\n    s.selectedItem\n      ? s.config.components[s.selectedItem.type]?.[\"label\"] ??\n        s.selectedItem.type.toString()\n      : s.config.root?.label || \"Page\"\n  );\n\n  return (\n    <SidebarSection noBorderTop showBreadcrumbs title={title}>\n      <Fields />\n    </SidebarSection>\n  );\n};\n\nconst PluginTab = ({\n  children,\n  visible,\n  mobileOnly,\n}: {\n  children: ReactNode;\n  visible: boolean;\n  mobileOnly?: boolean;\n}) => {\n  return (\n    <div className={getPluginTabClassName({ visible, mobileOnly })}>\n      <div className={getPluginTabClassName(\"body\")}>{children}</div>\n    </div>\n  );\n};\n\nexport const Layout = ({ children }: { children?: ReactNode }) => {\n  const {\n    iframe: _iframe,\n    dnd,\n    initialHistory: _initialHistory,\n    plugins,\n    height,\n  } = usePropsContext();\n\n  const iframe: IframeConfig = useMemo(\n    () => ({\n      enabled: true,\n      waitForStyles: true,\n      ..._iframe,\n    }),\n    [_iframe]\n  );\n\n  useInjectGlobalCss(iframe.enabled);\n\n  const dispatch = useAppStore((s) => s.dispatch);\n  const leftSideBarVisible = useAppStore((s) => s.state.ui.leftSideBarVisible);\n  const rightSideBarVisible = useAppStore(\n    (s) => s.state.ui.rightSideBarVisible\n  );\n\n  const instanceId = useAppStore((s) => s.instanceId);\n\n  const {\n    width: leftWidth,\n    setWidth: setLeftWidth,\n    sidebarRef: leftSidebarRef,\n    handleResizeEnd: handleLeftSidebarResizeEnd,\n  } = useSidebarResize(\"left\", dispatch);\n\n  const {\n    width: rightWidth,\n    setWidth: setRightWidth,\n    sidebarRef: rightSidebarRef,\n    handleResizeEnd: handleRightSidebarResizeEnd,\n  } = useSidebarResize(\"right\", dispatch);\n\n  useEffect(() => {\n    if (!window.matchMedia(\"(min-width: 638px)\").matches) {\n      dispatch({\n        type: \"setUi\",\n        ui: {\n          leftSideBarVisible: false,\n          rightSideBarVisible: false,\n        },\n      });\n    }\n\n    const handleResize = () => {\n      if (!window.matchMedia(\"(min-width: 638px)\").matches) {\n        dispatch({\n          type: \"setUi\",\n          ui: (ui: UiState) => ({\n            ...ui,\n            ...(ui.rightSideBarVisible ? { leftSideBarVisible: false } : {}),\n          }),\n        });\n      }\n    };\n\n    window.addEventListener(\"resize\", handleResize);\n\n    return () => {\n      window.removeEventListener(\"resize\", handleResize);\n    };\n  }, []);\n\n  const overrides = useAppStore((s) => s.overrides);\n\n  const CustomPuck = useMemo(\n    () => overrides.puck || DefaultOverride,\n    [overrides]\n  );\n\n  const [mounted, setMounted] = useState(false);\n\n  useEffect(() => {\n    setMounted(true);\n  }, []);\n\n  const ready = useAppStore((s) => s.status === \"READY\");\n\n  useMonitorHotkeys();\n\n  useEffect(() => {\n    if (ready && iframe.enabled) {\n      const frameDoc = getFrame();\n\n      if (frameDoc) {\n        return monitorHotkeys(frameDoc);\n      }\n    }\n  }, [ready, iframe.enabled]);\n\n  usePreviewModeHotkeys();\n  useDeleteHotkeys();\n\n  const layoutOptions: Record<string, any> = {};\n\n  if (leftWidth) {\n    layoutOptions[\"--puck-user-left-side-bar-width\"] = `${leftWidth}px`;\n  }\n\n  if (rightWidth) {\n    layoutOptions[\"--puck-user-right-side-bar-width\"] = `${rightWidth}px`;\n  }\n\n  const setUi = useAppStore((s) => s.setUi);\n  const currentPlugin = useAppStore((s) => s.state.ui.plugin?.current);\n  const appStoreApi = useAppStoreApi();\n\n  const [mobilePanelHeightMode, setMobilePanelHeightMode] = useState<\n    \"toggle\" | \"min-content\"\n  >(\"toggle\");\n\n  const hasLegacySideBarPlugin = useMemo(\n    () => !!plugins?.find((p) => p.name === \"legacy-side-bar\"),\n    [plugins]\n  );\n\n  const pluginItems = useMemo(() => {\n    const details: Record<string, MenuItem & { render: () => ReactElement }> =\n      {};\n\n    const defaultPlugins: PluginInternal[] = [blocksPlugin(), outlinePlugin()];\n\n    const isLegacy = (plugin: PluginInternal) =>\n      plugin.name === \"legacy-side-bar\" ? -1 : 0;\n\n    // Always place legacy-side-bar first\n    // Stable tie-break ensures consistent order for non-legacy plugins\n    const combinedPlugins: PluginInternal[] = [\n      ...defaultPlugins,\n      ...(plugins ?? []),\n    ].sort((a, b) => isLegacy(a) - isLegacy(b));\n\n    if (!plugins?.some((p) => p.name === \"fields\")) {\n      combinedPlugins.push(fieldsPlugin());\n    }\n\n    combinedPlugins?.forEach((plugin) => {\n      if (plugin.name && plugin.render) {\n        if (details[plugin.name]) {\n          // Delete existing plugins with this name to enable user sorting\n          delete details[plugin.name];\n        }\n\n        details[plugin.name] = {\n          label: plugin.label ?? plugin.name,\n          icon: plugin.icon ?? <ToyBrick />,\n          onClick: () => {\n            setMobilePanelHeightMode(plugin.mobilePanelHeight ?? \"toggle\");\n\n            if (plugin.name === currentPlugin) {\n              if (leftSideBarVisible) {\n                setUi({ leftSideBarVisible: false });\n              } else {\n                setUi({ leftSideBarVisible: true });\n              }\n            } else {\n              if (plugin.name) {\n                setUi({\n                  plugin: { current: plugin.name },\n                  leftSideBarVisible: true,\n                });\n              }\n            }\n          },\n          isActive: leftSideBarVisible && currentPlugin === plugin.name,\n          render: plugin.render,\n          mobileOnly: hasLegacySideBarPlugin || plugin.mobileOnly,\n          desktopOnly: plugin.name === \"legacy-side-bar\" || plugin.desktopOnly,\n        };\n      }\n    });\n\n    return details;\n  }, [plugins, currentPlugin, appStoreApi, leftSideBarVisible]);\n\n  useEffect(() => {\n    if (!currentPlugin) {\n      const names = Object.keys(pluginItems);\n\n      setUi({ plugin: { current: names[0] } });\n    }\n  }, [pluginItems, currentPlugin]);\n\n  const hasDesktopFieldsPlugin =\n    pluginItems[\"fields\"] && pluginItems[\"fields\"].mobileOnly === false;\n\n  const mobilePanelExpanded = useAppStore(\n    (s) => s.state.ui.mobilePanelExpanded ?? false\n  );\n\n  return (\n    <div\n      className={`Puck ${getClassName({\n        hidePlugins: hasLegacySideBarPlugin,\n      })}`}\n      id={instanceId}\n      style={{ height }}\n    >\n      <DragDropContext disableAutoScroll={dnd?.disableAutoScroll}>\n        <CustomPuck>\n          {children || (\n            <FrameProvider>\n              <div\n                className={getLayoutClassName({\n                  leftSideBarVisible,\n                  mounted,\n                  rightSideBarVisible:\n                    !hasDesktopFieldsPlugin && rightSideBarVisible,\n                  isExpanded: mobilePanelExpanded,\n                  mobilePanelHeightToggle: mobilePanelHeightMode === \"toggle\",\n                  mobilePanelHeightMinContent:\n                    mobilePanelHeightMode === \"min-content\",\n                })}\n                style={{ height }}\n              >\n                <div\n                  className={getLayoutClassName(\"inner\")}\n                  style={layoutOptions}\n                >\n                  <div className={getLayoutClassName(\"header\")}>\n                    <Header hidePlugins={hasLegacySideBarPlugin} />\n                  </div>\n                  <div className={getLayoutClassName(\"nav\")}>\n                    <Nav\n                      items={pluginItems}\n                      mobileActions={\n                        leftSideBarVisible &&\n                        mobilePanelHeightMode === \"toggle\" && (\n                          <IconButton\n                            type=\"button\"\n                            title=\"maximize\"\n                            onClick={() => {\n                              setUi({\n                                mobilePanelExpanded: !mobilePanelExpanded,\n                              });\n                            }}\n                          >\n                            {mobilePanelExpanded ? (\n                              <Minimize2 size={21} />\n                            ) : (\n                              <Maximize2 size={21} />\n                            )}\n                          </IconButton>\n                        )\n                      }\n                    />\n                  </div>\n                  <Sidebar\n                    position=\"left\"\n                    sidebarRef={leftSidebarRef}\n                    isVisible={leftSideBarVisible}\n                    onResize={setLeftWidth}\n                    onResizeEnd={handleLeftSidebarResizeEnd}\n                  >\n                    {Object.entries(pluginItems).map(\n                      ([id, { mobileOnly, render: Render, label }]) => (\n                        <PluginTab\n                          key={id}\n                          visible={currentPlugin === id}\n                          mobileOnly={mobileOnly}\n                        >\n                          <Render />\n                        </PluginTab>\n                      )\n                    )}\n                  </Sidebar>\n                  <Canvas />\n                  {!hasDesktopFieldsPlugin && (\n                    <Sidebar\n                      position=\"right\"\n                      sidebarRef={rightSidebarRef}\n                      isVisible={rightSideBarVisible}\n                      onResize={setRightWidth}\n                      onResizeEnd={handleRightSidebarResizeEnd}\n                    >\n                      <FieldSideBar />\n                    </Sidebar>\n                  )}\n                </div>\n              </div>\n            </FrameProvider>\n          )}\n        </CustomPuck>\n      </DragDropContext>\n      <div id=\"puck-portal-root\" className={getClassName(\"portal\")} />\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Layout/styles.module.css",
    "content": "/*\n * Puck's responsive layout uses minimum viewport widths slightly _below_ common\n * framework/device breakpoints, and ensures that the width of the resulting\n * Puck page preview (zoomed at 75%) is slightly _above_ common framework/device\n * breakpoints. This can help alleviate some of the pain when editing responsive\n * pages in a preview area that is narrower than the reported viewport width.\n *\n * Viewport | Puck page @ zoom 0.75\n * --------------------------------\n * -        | 322px\n * 766px    | 322px\n * 990px    | 604px\n * 1022px   | 646px\n * 1198px   | 801px\n * 1398px   | 1025px\n * 1598px   | 1212px\n */\n\n.Puck {\n  --puck-space-px: 16px;\n  font-family: var(--puck-font-family);\n  overflow-x: hidden;\n}\n\n@media (min-width: 766px) {\n  .Puck {\n    overflow-x: auto;\n  }\n}\n\n.Puck-portal {\n  position: relative;\n  z-index: 2;\n}\n\n.PuckLayout {\n  height: 100dvh;\n}\n\n.PuckLayout-inner {\n  --puck-frame-width: auto;\n  --puck-side-nav-width: min-content;\n  --puck-side-bar-width: 0px;\n  --puck-left-side-bar-width: var(\n    --puck-user-left-side-bar-width,\n    var(--puck-side-bar-width)\n  );\n  --puck-right-side-bar-width: var(\n    --puck-user-right-side-bar-width,\n    var(--puck-side-bar-width)\n  );\n  background-color: var(--puck-color-grey-12);\n  display: grid;\n  grid-template-areas: \"header\" \"editor\" \"left\" \"right\" \"sidenav\";\n  grid-template-columns: var(--puck-frame-width);\n  grid-template-rows: min-content auto 0 0 var(--puck-side-nav-width);\n  height: 100%;\n  position: relative;\n  transition: grid-template-rows 150ms ease-in;\n  z-index: 0;\n  overflow: hidden;\n}\n\n@media (min-width: 638px) {\n  .PuckLayout-inner {\n    --puck-side-nav-width: 68px;\n    grid-template-areas: \"header header header header\" \"sidenav left editor right\";\n    grid-template-columns: var(--puck-side-nav-width) 0 var(--puck-frame-width) 0;\n    grid-template-rows: min-content auto;\n  }\n\n  .Puck--hidePlugins .PuckLayout-inner {\n    --puck-side-nav-width: 0;\n  }\n}\n\n.PuckLayout--mounted .PuckLayout-inner {\n  --puck-side-bar-width: 186px;\n}\n\n.PuckLayout--mobilePanelHeightToggle.PuckLayout--leftSideBarVisible\n  .PuckLayout-inner {\n  /* Setting header to 0 causes jump as it can't be animated. Can't use fixed number as we don't know user's header dimensions. Consider using ref + JS. */\n  grid-template-rows: 0 auto 30% 0 var(--puck-side-nav-width);\n}\n\n.PuckLayout--mobilePanelHeightToggle.PuckLayout--leftSideBarVisible.PuckLayout--isExpanded\n  .PuckLayout-inner {\n  /* Setting header to 0 causes jump as it can't be animated. Can't use fixed number as we don't know user's header dimensions. Consider using ref + JS. */\n  grid-template-rows: 0 auto 55% 0 var(--puck-side-nav-width);\n}\n\n@media (min-width: 638px) {\n  .PuckLayout--mobilePanelHeightToggle.PuckLayout--leftSideBarVisible\n    .PuckLayout-inner {\n    grid-template-columns:\n      var(--puck-side-nav-width) var(--puck-left-side-bar-width) var(\n        --puck-frame-width\n      )\n      0;\n    grid-template-rows: min-content auto;\n  }\n}\n\n.PuckLayout--mobilePanelHeightMinContent.PuckLayout--leftSideBarVisible\n  .PuckLayout-inner,\n.PuckLayout--mobilePanelHeightMinContent.PuckLayout--leftSideBarVisible.PuckLayout--isExpanded\n  .PuckLayout-inner {\n  /* Setting header to 0 causes jump as it can't be animated. Can't use fixed number as we don't know user's header dimensions. Consider using ref + JS. */\n  grid-template-rows: 0 auto min-content 0 var(--puck-side-nav-width);\n}\n\n@media (min-width: 638px) {\n  .PuckLayout--mobilePanelHeightToggle.PuckLayout--leftSideBarVisible\n    .PuckLayout-inner,\n  .PuckLayout--mobilePanelHeightToggle.PuckLayout--leftSideBarVisible.PuckLayout--isExpanded\n    .PuckLayout-inner,\n  .PuckLayout--mobilePanelHeightMinContent.PuckLayout--leftSideBarVisible\n    .PuckLayout-inner,\n  .PuckLayout--mobilePanelHeightMinContent.PuckLayout--leftSideBarVisible.PuckLayout--isExpanded\n    .PuckLayout-inner {\n    grid-template-columns:\n      var(--puck-side-nav-width) var(--puck-left-side-bar-width) var(\n        --puck-frame-width\n      )\n      0;\n    grid-template-rows: min-content auto;\n  }\n}\n\n@media (min-width: 638px) {\n  .PuckLayout--rightSideBarVisible .PuckLayout-inner {\n    grid-template-columns:\n      var(--puck-side-nav-width) 0 var(--puck-frame-width)\n      var(--puck-right-side-bar-width);\n  }\n}\n\n@media (min-width: 638px) {\n  .PuckLayout--leftSideBarVisible.PuckLayout--rightSideBarVisible\n    .PuckLayout-inner {\n    grid-template-columns:\n      var(--puck-side-nav-width) var(--puck-left-side-bar-width) var(\n        --puck-frame-width\n      )\n      var(--puck-right-side-bar-width);\n  }\n}\n\n@media (min-width: 458px) {\n  .PuckLayout-mounted .PuckLayout-inner {\n    --puck-frame-width: minmax(266px, auto);\n  }\n}\n\n@media (min-width: 638px) {\n  .PuckLayout .PuckLayout-inner {\n    --puck-side-bar-width: minmax(186px, 250px);\n  }\n}\n\n@media (min-width: 766px) {\n  .PuckLayout .PuckLayout-inner {\n    --puck-frame-width: auto;\n  }\n}\n\n@media (min-width: 990px) {\n  .PuckLayout .PuckLayout-inner {\n    --puck-side-bar-width: 256px;\n  }\n}\n\n@media (min-width: 1198px) {\n  .PuckLayout .PuckLayout-inner {\n    --puck-side-bar-width: 274px;\n  }\n}\n\n@media (min-width: 1398px) {\n  .PuckLayout .PuckLayout-inner {\n    --puck-side-bar-width: 290px;\n  }\n}\n\n@media (min-width: 1598px) {\n  .PuckLayout .PuckLayout-inner {\n    --puck-side-bar-width: 320px;\n  }\n}\n\n.PuckLayout-nav {\n  border-top: 1px solid var(--puck-color-grey-09);\n  background-color: var(--puck-color-grey-12);\n  grid-area: sidenav;\n  overflow: hidden;\n  width: 100%;\n}\n\n@media (min-width: 638px) {\n  .PuckLayout-nav {\n    border-top: 0;\n    border-right: 1px solid var(--puck-color-grey-09);\n    box-sizing: border-box;\n  }\n}\n\n.PuckLayout-header {\n  grid-area: header;\n}\n\n.PuckLayout--leftSideBarVisible .PuckLayout-header {\n  overflow: hidden;\n}\n\n@media (min-width: 638px) {\n  .PuckLayout--leftSideBarVisible .PuckLayout-header {\n    overflow: auto;\n  }\n}\n\n.PuckPluginTab {\n  display: none;\n  flex-grow: 1;\n  max-height: 100%;\n}\n\n.PuckPluginTab--visible {\n  display: flex;\n  flex-direction: column;\n}\n\n.PuckPluginTab-body {\n  flex-grow: 1;\n  max-height: 100%;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/Nav/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport { ReactNode } from \"react\";\nimport { getClassNameFactory } from \"../../../../lib\";\n\nconst getClassName = getClassNameFactory(\"Nav\", styles);\nconst getClassNameItem = getClassNameFactory(\"NavItem\", styles);\n\nexport type MenuItem = {\n  label: string;\n  onClick?: () => void;\n  icon?: ReactNode;\n  isActive?: boolean;\n  mobileOnly?: boolean;\n  desktopOnly?: boolean;\n};\n\nexport const MenuItem = ({\n  label,\n  icon,\n  onClick,\n  isActive,\n  mobileOnly,\n  desktopOnly,\n}: MenuItem) => {\n  return (\n    <li\n      className={getClassNameItem({\n        active: isActive,\n        mobileOnly,\n        desktopOnly,\n      })}\n    >\n      {onClick && (\n        <div className={getClassNameItem(\"link\")} onClick={onClick}>\n          {icon && <span className={getClassNameItem(\"linkIcon\")}>{icon}</span>}\n          <span className={getClassNameItem(\"linkLabel\")}>{label}</span>\n        </div>\n      )}\n    </li>\n  );\n};\n\nexport const Nav = ({\n  items,\n  mobileActions,\n}: {\n  items: Record<string, MenuItem>;\n  mobileActions?: ReactNode;\n}) => {\n  return (\n    <nav className={getClassName()}>\n      <ul className={getClassName(\"list\")}>\n        {Object.entries(items).map(([key, item]) => (\n          <MenuItem key={key} {...item} />\n        ))}\n      </ul>\n      {mobileActions && (\n        <div className={getClassName(\"mobileActions\")}>{mobileActions}</div>\n      )}\n    </nav>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Nav/styles.module.css",
    "content": ".Nav {\n  display: flex;\n}\n\n.Nav-list {\n  display: flex;\n  list-style: none;\n  margin: 0;\n  padding: 0;\n  overflow-x: auto;\n  gap: 8px;\n}\n\n@media (min-width: 638px) {\n  .Nav-list {\n    padding-top: 32px;\n    flex-direction: column;\n    gap: 16px;\n    width: 100%;\n  }\n}\n\n.Nav-mobileActions {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  margin-inline-start: auto;\n  padding: 4px 16px;\n  border-inline-start: 1px solid var(--puck-color-grey-09);\n}\n\n@media (min-width: 638px) {\n  .Nav-mobileActions {\n    display: none;\n  }\n}\n\n.NavItem-link {\n  text-align: center;\n  align-items: center;\n  color: var(--puck-color-grey-03);\n  display: flex;\n  gap: 8px;\n  text-decoration: none;\n  cursor: pointer;\n  border-radius: 4px;\n  padding: 8px 4px;\n  width: 64px;\n  box-sizing: border-box;\n}\n\n@media (min-width: 638px) {\n  .NavItem-link {\n    width: auto;\n  }\n}\n\n.NavItem:first-of-type {\n  padding-left: 16px;\n}\n\n.NavItem:last-of-type {\n  padding-right: 16px;\n}\n\n@media (min-width: 638px) {\n  .NavItem:first-of-type,\n  .NavItem:last-of-type {\n    padding: 0;\n  }\n}\n\n.NavItem-link {\n  border-top: 4px solid transparent;\n  border-bottom: 4px solid transparent;\n  border-radius: 0;\n  flex-direction: column;\n  font-size: var(--puck-font-size-xxxs);\n}\n\n@media (min-width: 638px) {\n  .NavItem-link {\n    border: 0;\n    border-left: 4px solid transparent;\n    border-right: 4px solid transparent;\n  }\n}\n\n.NavItem-linkIcon {\n  height: 24px;\n  width: 24px;\n}\n\n.NavItem--active > .NavItem-link {\n  background-color: var(--puck-color-azure-10);\n  color: var(--puck-color-azure-04);\n  font-weight: 600;\n}\n\n.NavItem--active > .NavItem-link {\n  background-color: transparent;\n  border-top-color: var(--puck-color-azure-04);\n  border-top-right-radius: 0;\n  border-bottom-right-radius: 0;\n  font-weight: 600;\n}\n\n@media (min-width: 638px) {\n  .NavItem--active > .NavItem-link {\n    border-top-color: transparent;\n    border-right-color: var(--puck-color-azure-04);\n  }\n}\n\n.NavItem:not(.NavItem--active) > .NavItem-link:hover {\n  background-color: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-04);\n}\n\n@media (min-width: 638px) {\n  .NavItem--mobileOnly {\n    display: none;\n  }\n}\n\n.NavItem--desktopOnly {\n  display: none;\n}\n\n@media (min-width: 638px) {\n  .NavItem--desktopOnly {\n    display: block;\n  }\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/Outline/index.tsx",
    "content": "import { LayerTree } from \"../../../LayerTree\";\nimport { useAppStore } from \"../../../../store\";\nimport { useMemo } from \"react\";\nimport { findZonesForArea } from \"../../../../lib/data/find-zones-for-area\";\nimport { useShallow } from \"zustand/react/shallow\";\n\nexport const Outline = () => {\n  const outlineOverride = useAppStore((s) => s.overrides.outline);\n\n  const rootZones = useAppStore(\n    useShallow((s) => findZonesForArea(s.state, \"root\"))\n  );\n\n  const Wrapper = useMemo(() => outlineOverride || \"div\", [outlineOverride]);\n  return (\n    <Wrapper>\n      {rootZones.map((zoneCompound) => (\n        <LayerTree\n          key={zoneCompound}\n          label={rootZones.length === 1 ? \"\" : zoneCompound.split(\":\")[1]}\n          zoneCompound={zoneCompound}\n        />\n      ))}\n    </Wrapper>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Preview/index.tsx",
    "content": "import { DropZoneEditPure, DropZonePure } from \"../../../DropZone\";\nimport { rootDroppableId } from \"../../../../lib/root-droppable-id\";\nimport { RefObject, useCallback, useEffect, useRef, useMemo } from \"react\";\nimport { useAppStore } from \"../../../../store\";\nimport AutoFrame, { autoFrameContext } from \"../../../AutoFrame\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"../../../../lib\";\nimport { DefaultRootRenderProps } from \"../../../../types\";\nimport { Render } from \"../../../Render\";\nimport { BubbledPointerEvent } from \"../../../../lib/bubble-pointer-event\";\nimport { useSlots } from \"../../../../lib/use-slots\";\nimport { useRichtextProps } from \"../../../RichTextEditor/lib/use-richtext-props\";\n\nconst getClassName = getClassNameFactory(\"PuckPreview\", styles);\n\ntype PageProps = DefaultRootRenderProps;\n\nconst useBubbleIframeEvents = (ref: RefObject<HTMLIFrameElement | null>) => {\n  const status = useAppStore((s) => s.status);\n\n  useEffect(() => {\n    if (ref.current && status === \"READY\") {\n      const iframe = ref.current;\n\n      const handlePointerMove = (event: PointerEvent) => {\n        const evt = new BubbledPointerEvent(\"pointermove\", {\n          ...event,\n          bubbles: true,\n          cancelable: false,\n          clientX: event.clientX,\n          clientY: event.clientY,\n          originalTarget: event.target,\n        });\n\n        iframe.dispatchEvent(evt as any);\n      };\n\n      const register = () => {\n        unregister();\n\n        // Add event listeners\n        iframe.contentDocument?.addEventListener(\n          \"pointermove\",\n          handlePointerMove,\n          {\n            capture: true,\n          }\n        );\n      };\n\n      const unregister = () => {\n        // Clean up event listeners\n        iframe.contentDocument?.removeEventListener(\n          \"pointermove\",\n          handlePointerMove\n        );\n      };\n\n      register();\n\n      return () => {\n        unregister();\n      };\n    }\n  }, [status]);\n};\n\nexport const Preview = ({ id = \"puck-preview\" }: { id?: string }) => {\n  const dispatch = useAppStore((s) => s.dispatch);\n  const root = useAppStore((s) => s.state.data.root);\n  const config = useAppStore((s) => s.config);\n  const setStatus = useAppStore((s) => s.setStatus);\n  const iframe = useAppStore((s) => s.iframe);\n  const overrides = useAppStore((s) => s.overrides);\n  const metadata = useAppStore((s) => s.metadata);\n  const renderData = useAppStore((s) =>\n    s.state.ui.previewMode === \"edit\" ? null : s.state.data\n  );\n\n  const Page = useCallback<React.FC<PageProps>>(\n    (pageProps) => {\n      // eslint-disable-next-line react-hooks/rules-of-hooks\n      const propsWithSlots = useSlots(\n        config,\n        { type: \"root\", props: pageProps },\n        DropZoneEditPure\n      );\n\n      // eslint-disable-next-line react-hooks/rules-of-hooks\n      const richtextProps = useRichtextProps(\n        config.root?.fields ?? {},\n        pageProps\n      );\n\n      return config.root?.render ? (\n        config.root?.render({\n          id: \"puck-root\",\n          ...propsWithSlots,\n          ...richtextProps,\n        })\n      ) : (\n        <>{propsWithSlots.children}</>\n      );\n    },\n    [config]\n  );\n\n  const Frame = useMemo(() => overrides.iframe, [overrides]);\n\n  // DEPRECATED\n  const rootProps = root.props || root;\n\n  const ref = useRef<HTMLIFrameElement>(null);\n\n  useBubbleIframeEvents(ref);\n\n  const inner = !renderData ? (\n    <Page\n      {...rootProps}\n      puck={{\n        renderDropZone: DropZonePure,\n        isEditing: true,\n        dragRef: null,\n        metadata,\n      }}\n      editMode={true} // DEPRECATED\n    >\n      <DropZonePure zone={rootDroppableId} />\n    </Page>\n  ) : (\n    <Render data={renderData} config={config} metadata={metadata} />\n  );\n\n  useEffect(() => {\n    if (!iframe.enabled) {\n      setStatus(\"READY\");\n    }\n  }, [iframe.enabled]);\n\n  return (\n    <div\n      className={getClassName()}\n      id={id}\n      data-puck-preview\n      onClick={(e) => {\n        const el = e.target as Element;\n\n        if (\n          !el.hasAttribute(\"data-puck-component\") &&\n          !el.hasAttribute(\"data-puck-dropzone\")\n        ) {\n          dispatch({ type: \"setUi\", ui: { itemSelector: null } });\n        }\n      }}\n    >\n      {iframe.enabled ? (\n        <AutoFrame\n          id=\"preview-frame\"\n          className={getClassName(\"frame\")}\n          data-rfd-iframe\n          onReady={() => {\n            setStatus(\"READY\");\n          }}\n          onNotReady={() => {\n            setStatus(\"MOUNTED\");\n          }}\n          frameRef={ref}\n        >\n          <autoFrameContext.Consumer>\n            {({ document }) => {\n              if (Frame) {\n                return <Frame document={document}>{inner}</Frame>;\n              }\n\n              return inner;\n            }}\n          </autoFrameContext.Consumer>\n        </AutoFrame>\n      ) : (\n        <div\n          id=\"preview-frame\"\n          className={getClassName(\"frame\")}\n          ref={ref}\n          data-puck-entry\n        >\n          {inner}\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Preview/styles.module.css",
    "content": ".PuckPreview {\n  position: relative;\n  height: 100%;\n}\n\n.PuckPreview-frame {\n  border: none;\n  height: 100%;\n  width: 100%;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/ResizeHandle/index.tsx",
    "content": "import React, { useCallback, useEffect, useRef } from \"react\";\nimport getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\nimport \"./styles.css\";\nimport { useCanvasFrame } from \"../../../../lib/frame-context\";\nimport { useResetAutoZoom } from \"../../../../lib\";\n\nconst getClassName = getClassNameFactory(\"ResizeHandle\", styles);\n\ninterface ResizeHandleProps {\n  position: \"left\" | \"right\";\n  sidebarRef: { current: HTMLDivElement | null };\n  onResize: (width: number) => void;\n  onResizeEnd: (width: number) => void;\n}\n\nexport const ResizeHandle: React.FC<ResizeHandleProps> = ({\n  position,\n  sidebarRef,\n  onResize,\n  onResizeEnd,\n}) => {\n  const { frameRef } = useCanvasFrame();\n  const resetAutoZoom = useResetAutoZoom(frameRef);\n\n  const handleRef = useRef<HTMLDivElement>(null);\n  const isDragging = useRef(false);\n  const startX = useRef(0);\n  const startWidth = useRef(0);\n\n  const handleMouseMove = useCallback(\n    (e: MouseEvent) => {\n      if (!isDragging.current) return;\n\n      const delta = e.clientX - startX.current;\n      const newWidth =\n        position === \"left\"\n          ? startWidth.current + delta\n          : startWidth.current - delta;\n\n      const width = Math.max(192, newWidth);\n      onResize(width);\n      e.preventDefault();\n    },\n    [onResize, position]\n  );\n\n  const handleMouseUp = useCallback(() => {\n    if (!isDragging.current) return;\n\n    isDragging.current = false;\n    document.body.style.cursor = \"\";\n    document.body.style.userSelect = \"\";\n\n    const overlay = document.getElementById(\"resize-overlay\");\n    if (overlay) {\n      document.body.removeChild(overlay);\n    }\n\n    // Remove event listeners when dragging ends\n    document.removeEventListener(\"mousemove\", handleMouseMove);\n    document.removeEventListener(\"mouseup\", handleMouseUp);\n\n    const finalWidth = sidebarRef.current?.getBoundingClientRect().width || 0;\n    onResizeEnd(finalWidth);\n\n    resetAutoZoom();\n  }, [onResizeEnd]);\n\n  const handleMouseDown = useCallback(\n    (e: React.MouseEvent) => {\n      isDragging.current = true;\n      startX.current = e.clientX;\n\n      startWidth.current =\n        sidebarRef.current?.getBoundingClientRect().width || 0;\n\n      document.body.style.cursor = \"col-resize\";\n      document.body.style.userSelect = \"none\";\n\n      const overlay = document.createElement(\"div\");\n      overlay.id = \"resize-overlay\";\n      overlay.setAttribute(\"data-resize-overlay\", \"\");\n      document.body.appendChild(overlay);\n\n      // Add event listeners only when dragging starts\n      document.addEventListener(\"mousemove\", handleMouseMove);\n      document.addEventListener(\"mouseup\", handleMouseUp);\n\n      e.preventDefault();\n    },\n    [position, handleMouseMove, handleMouseUp]\n  );\n\n  return (\n    <div\n      ref={handleRef}\n      className={getClassName({ [position]: true })}\n      onMouseDown={handleMouseDown}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/ResizeHandle/styles.css",
    "content": "/* Resize overlay style */\n[data-resize-overlay] {\n  position: fixed;\n  top: 0;\n  left: 0;\n  right: 0;\n  bottom: 0;\n  z-index: 9999;\n  cursor: col-resize;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/ResizeHandle/styles.module.css",
    "content": "@media (min-width: 766px) {\n  .ResizeHandle {\n    position: absolute;\n    width: 5px;\n    height: 100%;\n    cursor: col-resize;\n    z-index: 10;\n    background: transparent;\n    top: 0;\n  }\n\n  .ResizeHandle:hover {\n    background: rgba(0, 0, 0, 0.1);\n  }\n\n  .ResizeHandle--left {\n    right: -3px;\n  }\n\n  .ResizeHandle--right {\n    left: -3px;\n  }\n}\n"
  },
  {
    "path": "packages/core/components/Puck/components/Sidebar/index.tsx",
    "content": "import React from \"react\";\nimport { ResizeHandle } from \"../ResizeHandle\";\nimport getClassNameFactory from \"../../../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"Sidebar\", styles);\n\ninterface SidebarProps {\n  position: \"left\" | \"right\";\n  sidebarRef: { current: HTMLDivElement | null };\n  isVisible: boolean;\n  onResize: (width: number) => void;\n  onResizeEnd: (width: number) => void;\n  children: React.ReactNode;\n}\n\nexport const Sidebar: React.FC<SidebarProps> = ({\n  position,\n  sidebarRef,\n  isVisible,\n  onResize,\n  onResizeEnd,\n  children,\n}) => {\n  return (\n    <>\n      <div\n        ref={sidebarRef}\n        className={getClassName({ [position]: true, isVisible })}\n      >\n        {children}\n      </div>\n      <div className={`${getClassName(\"resizeHandle\")}`}>\n        <ResizeHandle\n          position={position}\n          sidebarRef={sidebarRef}\n          onResize={onResize}\n          onResizeEnd={onResizeEnd}\n        />\n      </div>\n    </>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Puck/components/Sidebar/styles.module.css",
    "content": ".Sidebar {\n  border-block-start: 1px solid var(--puck-color-grey-09);\n  position: relative;\n  display: none;\n  flex-direction: column;\n  overflow-y: auto;\n}\n\n.Sidebar--isVisible {\n  display: flex;\n}\n\n.Sidebar--left {\n  background: var(--puck-color-grey-12);\n  grid-area: left;\n}\n\n@media (min-width: 766px) {\n  .Sidebar--left {\n    border-block-start: 0;\n    border-inline-end: 1px solid var(--puck-color-grey-09);\n  }\n}\n\n.Sidebar--right {\n  background: var(--puck-color-white);\n  grid-area: right;\n}\n\n@media (min-width: 766px) {\n  .Sidebar--right {\n    border-block-start: 0;\n    border-inline-start: 1px solid var(--puck-color-grey-09);\n  }\n}\n\n.Sidebar-resizeHandle {\n  position: absolute;\n  height: 100%;\n}\n\n.Sidebar--left + .Sidebar-resizeHandle {\n  grid-area: left;\n  justify-self: end;\n}\n\n.Sidebar--right + .Sidebar-resizeHandle {\n  grid-area: right;\n  justify-self: start;\n}\n"
  },
  {
    "path": "packages/core/components/Puck/index.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport {\n  Context,\n  createContext,\n  CSSProperties,\n  PropsWithChildren,\n  ReactElement,\n  ReactNode,\n  useCallback,\n  useContext,\n  useEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\n\nimport type {\n  UiState,\n  IframeConfig,\n  OnAction,\n  Overrides,\n  Permissions,\n  Plugin,\n  InitialHistory,\n  UserGenerics,\n  Config,\n  Data,\n  Metadata,\n  AsFieldProps,\n  DefaultComponentProps,\n} from \"../../types\";\n\nimport { PuckAction } from \"../../reducer\";\nimport { createAppStore, defaultAppState, appStoreContext } from \"../../store\";\nimport { Fields } from \"./components/Fields\";\nimport { Components } from \"./components/Components\";\nimport { Preview } from \"./components/Preview\";\nimport { Outline } from \"./components/Outline\";\nimport { defaultViewports } from \"../ViewportControls/default-viewports\";\nimport { Viewports } from \"../../types\";\nimport { useLoadedOverrides } from \"../../lib/use-loaded-overrides\";\nimport { useRegisterHistorySlice } from \"../../store/slices/history\";\nimport { useRegisterPermissionsSlice } from \"../../store/slices/permissions\";\nimport {\n  UsePuckStoreContext,\n  useRegisterUsePuckStore,\n} from \"../../lib/use-puck\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { deepEqual } from \"fast-equals\";\nimport { FieldTransforms } from \"../../types/API/FieldTransforms\";\nimport { populateIds } from \"../../lib/data/populate-ids\";\nimport { toComponent } from \"../../lib/data/to-component\";\nimport { Layout } from \"./components/Layout\";\nimport { useSafeId } from \"../../lib/use-safe-id\";\n\ntype PuckProps<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n> = {\n  children?: ReactNode;\n  config: UserConfig;\n  data: Partial<G[\"UserData\"] | Data>;\n  ui?: Partial<UiState>;\n  onChange?: (data: G[\"UserData\"]) => void;\n  onPublish?: (data: G[\"UserData\"]) => void;\n  onAction?: OnAction<G[\"UserData\"]>;\n  permissions?: Partial<Permissions>;\n  plugins?: Plugin<UserConfig>[];\n  overrides?: Partial<Overrides<UserConfig>>;\n  fieldTransforms?: FieldTransforms<UserConfig>;\n  renderHeader?: (props: {\n    children: ReactNode;\n    dispatch: (action: PuckAction) => void;\n    state: G[\"UserAppState\"];\n  }) => ReactElement;\n  renderHeaderActions?: (props: {\n    state: G[\"UserAppState\"];\n    dispatch: (action: PuckAction) => void;\n  }) => ReactElement;\n  headerTitle?: string;\n  headerPath?: string;\n  viewports?: Viewports;\n  iframe?: IframeConfig;\n  dnd?: {\n    disableAutoScroll?: boolean;\n  };\n  initialHistory?: InitialHistory;\n  metadata?: Metadata;\n  height?: CSSProperties[\"height\"];\n  _experimentalFullScreenCanvas?: boolean;\n};\n\nconst propsContext = createContext<Partial<PuckProps>>({});\n\nfunction PropsProvider<UserConfig extends Config = Config>(\n  props: PuckProps<UserConfig>\n) {\n  return (\n    <propsContext.Provider value={props as PuckProps}>\n      {props.children}\n    </propsContext.Provider>\n  );\n}\n\nexport const usePropsContext = () =>\n  useContext<PuckProps>(propsContext as Context<PuckProps>);\n\nfunction PuckProvider<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>({ children }: PropsWithChildren) {\n  const {\n    config,\n    data: initialData,\n    ui: initialUi,\n    onChange,\n    permissions = {},\n    plugins,\n    overrides,\n    viewports = defaultViewports,\n    iframe: _iframe,\n    initialHistory: _initialHistory,\n    metadata,\n    onAction,\n    fieldTransforms,\n  } = usePropsContext();\n\n  const iframe: IframeConfig = useMemo(\n    () => ({\n      enabled: true,\n      waitForStyles: true,\n      ..._iframe,\n    }),\n    [_iframe]\n  );\n\n  const [generatedAppState] = useState<G[\"UserAppState\"]>(() => {\n    const initial = { ...defaultAppState.ui, ...initialUi };\n\n    let clientUiState: Partial<G[\"UserAppState\"][\"ui\"]> = {};\n\n    // DEPRECATED\n    if (\n      Object.keys(initialData?.root || {}).length > 0 &&\n      !initialData?.root?.props\n    ) {\n      console.warn(\n        \"Warning: Defining props on `root` is deprecated. Please use `root.props`, or republish this page to migrate automatically.\"\n      );\n    }\n\n    // Deprecated\n    const rootProps = initialData?.root?.props || initialData?.root || {};\n\n    const defaultedRootProps = {\n      ...config.root?.defaultProps,\n      ...(rootProps as AsFieldProps<DefaultComponentProps> | AsFieldProps<any>),\n    };\n\n    const root = populateIds(\n      toComponent({ ...initialData?.root, props: defaultedRootProps }),\n      config\n    );\n\n    const newAppState = {\n      ...defaultAppState,\n      data: {\n        ...initialData,\n        root: { ...initialData?.root, props: root.props },\n        content: initialData.content || [],\n      },\n      ui: {\n        ...initial,\n        ...clientUiState,\n        // Store categories under componentList on state to allow render functions and plugins to modify\n        componentList: config.categories\n          ? Object.entries(config.categories).reduce(\n              (acc, [categoryName, category]) => {\n                return {\n                  ...acc,\n                  [categoryName]: {\n                    title: category.title,\n                    components: category.components,\n                    expanded: category.defaultExpanded,\n                    visible: category.visible,\n                  },\n                };\n              },\n              {}\n            )\n          : {},\n      },\n    } as G[\"UserAppState\"];\n\n    return walkAppState(newAppState, config);\n  });\n\n  const { appendData = true } = _initialHistory || {};\n\n  const [blendedHistories] = useState(\n    [\n      ...(_initialHistory?.histories || []),\n      ...(appendData ? [{ state: generatedAppState }] : []),\n    ].map((history) => {\n      // Inject default data to enable partial history injections\n      let newState = { ...generatedAppState, ...history.state };\n\n      // The history generally doesn't include the indexes, so calculate them for each state item\n      if (!(history.state as PrivateAppState).indexes) {\n        newState = walkAppState(newState, config);\n      }\n\n      return {\n        ...history,\n        state: newState,\n      };\n    })\n  );\n\n  const initialHistoryIndex = useMemo(() => {\n    if (\n      _initialHistory?.index !== undefined &&\n      _initialHistory?.index >= 0 &&\n      _initialHistory?.index < blendedHistories.length\n    ) {\n      return _initialHistory?.index;\n    }\n\n    return blendedHistories.length - 1;\n  }, []);\n  const initialAppState = blendedHistories[initialHistoryIndex].state;\n\n  // Load all plugins into the overrides\n  const loadedOverrides = useLoadedOverrides({\n    overrides: overrides,\n    plugins: plugins,\n  });\n\n  const loadedFieldTransforms = useMemo(() => {\n    const _plugins: Plugin[] = plugins || [];\n    const pluginFieldTransforms = _plugins.reduce<FieldTransforms>(\n      (acc, plugin) => ({ ...acc, ...plugin.fieldTransforms }),\n      {}\n    );\n\n    return {\n      ...pluginFieldTransforms,\n      ...fieldTransforms,\n    };\n  }, [fieldTransforms, plugins]);\n\n  const instanceId = useSafeId();\n\n  const generateAppStore = useCallback(\n    (state?: PrivateAppState) => {\n      return {\n        instanceId,\n        state,\n        config,\n        plugins: plugins || [],\n        overrides: loadedOverrides,\n        viewports,\n        iframe,\n        onAction,\n        metadata,\n        fieldTransforms: loadedFieldTransforms,\n      };\n    },\n    [\n      instanceId,\n      initialAppState,\n      config,\n      plugins,\n      loadedOverrides,\n      viewports,\n      iframe,\n      onAction,\n      metadata,\n      loadedFieldTransforms,\n    ]\n  );\n\n  const [appStore] = useState(() =>\n    createAppStore(generateAppStore(initialAppState))\n  );\n\n  useEffect(() => {\n    if (process.env.NODE_ENV !== \"production\") {\n      (window as any).__PUCK_INTERNAL_DO_NOT_USE = { appStore };\n    }\n  }, [appStore]);\n\n  useEffect(() => {\n    const state = appStore.getState().state;\n\n    appStore.setState({\n      ...generateAppStore(state),\n    });\n  }, [config, plugins, loadedOverrides, viewports, iframe, onAction, metadata]);\n\n  useRegisterHistorySlice(appStore, {\n    histories: blendedHistories,\n    index: initialHistoryIndex,\n    initialAppState,\n  });\n\n  const previousData = useRef<Data>(null);\n\n  useEffect(() => {\n    return appStore.subscribe(\n      (s) => s.state.data,\n      (data) => {\n        if (onChange) {\n          if (deepEqual(data, previousData.current)) return;\n\n          onChange(data as G[\"UserData\"]);\n\n          previousData.current = data;\n        }\n      }\n    );\n  }, [onChange]);\n\n  useRegisterPermissionsSlice(appStore, permissions);\n\n  const uPuckStore = useRegisterUsePuckStore(appStore);\n\n  useEffect(() => {\n    const { resolveAndCommitData } = appStore.getState();\n\n    resolveAndCommitData();\n  }, []);\n\n  return (\n    <appStoreContext.Provider value={appStore}>\n      <UsePuckStoreContext.Provider value={uPuckStore}>\n        {children}\n      </UsePuckStoreContext.Provider>\n    </appStoreContext.Provider>\n  );\n}\n\nexport function Puck<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>(props: PuckProps<UserConfig>) {\n  return (\n    <PropsProvider {...props}>\n      <PuckProvider {...props}>\n        <Layout>{props.children}</Layout>\n      </PuckProvider>\n    </PropsProvider>\n  );\n}\n\nPuck.Components = Components;\nPuck.Fields = Fields;\nPuck.Layout = Layout;\nPuck.Outline = Outline;\nPuck.Preview = Preview;\n"
  },
  {
    "path": "packages/core/components/Render/index.tsx",
    "content": "\"use client\";\n\nimport { rootZone } from \"../../lib/root-droppable-id\";\nimport { useSlots } from \"../../lib/use-slots\";\nimport { Config, Data, Metadata, UserGenerics } from \"../../types\";\nimport {\n  DropZonePure,\n  DropZoneProvider,\n  DropZoneRenderPure,\n} from \"../DropZone\";\nimport React, { useMemo } from \"react\";\nimport { SlotRender } from \"../SlotRender\";\nimport { DropZoneContext } from \"../DropZone/context\";\nimport { useRichtextProps } from \"../RichTextEditor/lib/use-richtext-props\";\n\nexport const renderContext = React.createContext<{\n  config: Config;\n  data: Data;\n  metadata: Metadata;\n}>({\n  config: { components: {} },\n  data: { root: {}, content: [] },\n  metadata: {},\n});\n\nexport function Render<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>({\n  config,\n  data,\n  metadata = {},\n}: {\n  config: UserConfig;\n  data: Partial<G[\"UserData\"] | Data>;\n  metadata?: Metadata;\n}) {\n  const defaultedData = {\n    ...data,\n    root: data.root || {},\n    content: data.content || [],\n  } as G[\"UserData\"];\n\n  // DEPRECATED\n  const rootProps =\n    \"props\" in defaultedData.root\n      ? defaultedData.root.props\n      : defaultedData.root;\n  const title = rootProps?.title || \"\";\n\n  const pageProps = {\n    ...rootProps,\n    puck: {\n      renderDropZone: DropZonePure,\n      isEditing: false,\n      dragRef: null,\n      metadata: metadata,\n    },\n    title,\n    editMode: false,\n    id: \"puck-root\",\n  };\n\n  const propsWithSlots = useSlots(\n    config,\n    { type: \"root\", props: pageProps },\n    (props) => <SlotRender {...props} config={config} metadata={metadata} />\n  );\n\n  const richtextProps = useRichtextProps(config.root?.fields, pageProps);\n\n  const nextContextValue = useMemo<DropZoneContext>(\n    () => ({\n      mode: \"render\",\n      depth: 0,\n    }),\n    []\n  );\n\n  if (config.root?.render) {\n    return (\n      <renderContext.Provider value={{ config, data: defaultedData, metadata }}>\n        <DropZoneProvider value={nextContextValue}>\n          <config.root.render {...propsWithSlots} {...richtextProps}>\n            <DropZoneRenderPure zone={rootZone} />\n          </config.root.render>\n        </DropZoneProvider>\n      </renderContext.Provider>\n    );\n  }\n\n  return (\n    <renderContext.Provider value={{ config, data: defaultedData, metadata }}>\n      <DropZoneProvider value={nextContextValue}>\n        <DropZoneRenderPure zone={rootZone} />\n      </DropZoneProvider>\n    </renderContext.Provider>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/components/Editor.tsx",
    "content": "import { memo, useMemo } from \"react\";\nimport { useSyncedEditor } from \"../lib/use-synced-editor\";\nimport { PuckRichText } from \"../extension\";\nimport { EditorContent } from \"@tiptap/react\";\nimport { EditorProps } from \"../types\";\nimport { useAppStore, useAppStoreApi } from \"../../../store\";\nimport { LoadedRichTextMenu } from \"../../RichTextMenu\";\nimport { EditorInner } from \"./EditorInner\";\n\nexport const Editor = memo((props: EditorProps) => {\n  const {\n    onChange,\n    content,\n    readOnly = false,\n    field,\n    inline = false,\n    onFocus,\n    id,\n    name,\n  } = props;\n\n  const { tiptap = {}, options } = field;\n  const { extensions = [] } = tiptap;\n\n  const loadedExtensions = useMemo(\n    () => [PuckRichText.configure(options), ...extensions],\n    [field, extensions]\n  );\n\n  const appStoreApi = useAppStoreApi();\n\n  const focusName = `${name}${inline ? \"::inline\" : \"\"}`;\n\n  const editor = useSyncedEditor({\n    content,\n    onChange,\n    extensions: loadedExtensions,\n    editable: !readOnly,\n    name: focusName,\n    onFocusChange: (editor) => {\n      if (editor) {\n        const s = appStoreApi.getState();\n\n        appStoreApi.setState({\n          currentRichText: {\n            field,\n            editor,\n            id,\n            inline,\n          },\n          state: {\n            ...s.state,\n            ui: {\n              ...s.state.ui,\n              field: {\n                ...s.state.ui.field,\n                focus: focusName,\n              },\n            },\n          },\n        });\n\n        onFocus?.(editor);\n      }\n    },\n  });\n\n  const menuEditor = useAppStore((s) => {\n    if (\n      !inline &&\n      s.currentRichText?.id === id &&\n      s.currentRichText?.inlineComponentId\n    ) {\n      return s.currentRichText.editor;\n    }\n\n    return editor;\n  });\n\n  if (!editor) return null;\n\n  return (\n    <EditorInner\n      {...props}\n      editor={editor}\n      menu={\n        <LoadedRichTextMenu\n          field={field}\n          editor={menuEditor}\n          readOnly={readOnly}\n        />\n      }\n    >\n      <EditorContent editor={editor} className=\"rich-text\" />\n    </EditorInner>\n  );\n});\n\nEditor.displayName = \"Editor\";\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/components/EditorFallback.tsx",
    "content": "/** Fallback component. Should not contain any tiptap imports (except for types) */\n\nimport { memo } from \"react\";\nimport { EditorProps } from \"../types\";\nimport { LoadedRichTextMenuInner } from \"../../RichTextMenu/inner\";\nimport { EditorInner } from \"./EditorInner\";\n\nexport const EditorFallback = memo((props: EditorProps) => {\n  return (\n    <EditorInner\n      {...props}\n      editor={null}\n      menu={\n        <LoadedRichTextMenuInner\n          field={props.field}\n          editor={null}\n          editorState={null}\n          readOnly={props.readOnly ?? false}\n        />\n      }\n    >\n      <div\n        className=\"rich-text\"\n        dangerouslySetInnerHTML={{ __html: props.content }}\n        contentEditable\n      />\n    </EditorInner>\n  );\n});\n\nEditorFallback.displayName = \"EditorFallback\";\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/components/EditorInner.tsx",
    "content": "/** Inner component. Should not contain any tiptap imports (except for types) */\n\nimport {\n  memo,\n  ReactNode,\n  useCallback,\n  KeyboardEvent,\n  FocusEventHandler,\n} from \"react\";\nimport styles from \"../styles.module.css\";\nimport getClassNameFactory from \"../../../lib/get-class-name-factory\";\nimport { EditorProps } from \"../types\";\nimport type { Editor } from \"@tiptap/core\";\nimport { useAppStore, useAppStoreApi } from \"../../../store\";\n\nconst getClassName = getClassNameFactory(\"RichTextEditor\", styles);\n\nexport const EditorInner = memo(\n  ({\n    children,\n    menu,\n    readOnly = false,\n    field,\n    inline = false,\n    editor,\n    id,\n  }: EditorProps & {\n    children: ReactNode;\n    menu: ReactNode;\n    editor: Editor | null;\n  }) => {\n    const { initialHeight } = field;\n\n    const isActive = useAppStore(\n      (s) => s.currentRichText?.id === id && inline === s.currentRichText.inline\n    );\n\n    const appStoreApi = useAppStoreApi();\n\n    const handleHotkeyCapture = useCallback(\n      (event: KeyboardEvent<HTMLDivElement>) => {\n        if (\n          (event.metaKey || event.ctrlKey) &&\n          event.key.toLowerCase() === \"i\"\n        ) {\n          event.stopPropagation();\n\n          // Stop Safari from capturing key binding\n          event.preventDefault();\n          editor?.commands.toggleItalic?.();\n        }\n\n        // Prevent event propagation for backspace. When propagated, it will trigger block deletion\n        if (event.key.toLowerCase() === \"backspace\") {\n          event.stopPropagation();\n        }\n      },\n      [editor]\n    );\n\n    const handleBlur = useCallback<FocusEventHandler<HTMLDivElement>>(\n      (e) => {\n        const targetInMenu = !!e.relatedTarget?.closest?.(\n          \"[data-puck-rte-menu]\"\n        );\n\n        if (e.relatedTarget && !targetInMenu) {\n          appStoreApi.setState({\n            currentRichText: null,\n          });\n        } else {\n          e.stopPropagation();\n        }\n      },\n      [appStoreApi]\n    );\n\n    return (\n      <div\n        className={getClassName({\n          editor: !inline,\n          inline,\n          isActive,\n          disabled: readOnly,\n        })}\n        style={\n          inline ? {} : { height: initialHeight ?? 192, overflowY: \"auto\" }\n        }\n        onKeyDownCapture={handleHotkeyCapture}\n        onBlur={handleBlur}\n      >\n        {!inline && <div className={getClassName(\"menu\")}>{menu}</div>}\n        {children}\n      </div>\n    );\n  }\n);\n\nEditorInner.displayName = \"EditorInner\";\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/components/Render.tsx",
    "content": "import type { JSONContent } from \"@tiptap/react\";\nimport { generateHTML, generateJSON } from \"@tiptap/html\";\nimport { useMemo } from \"react\";\nimport getClassNameFactory from \"../../../lib/get-class-name-factory\";\nimport styles from \"../styles.module.css\";\nimport { PuckRichText } from \"../extension\";\nimport { RichtextField } from \"../../../types\";\n\nconst getClassName = getClassNameFactory(\"RichTextEditor\", styles);\n\nexport function RichTextRender({\n  content,\n  field,\n}: {\n  content: string | JSONContent;\n  field: RichtextField;\n}) {\n  const { tiptap = {}, options } = field;\n  const { extensions = [] } = tiptap;\n\n  const loadedExtensions = useMemo(\n    () => [PuckRichText.configure(options), ...extensions],\n    [field, extensions]\n  );\n\n  const normalized: JSONContent = useMemo(() => {\n    if (typeof content === \"object\" && content?.type === \"doc\") {\n      return content;\n    }\n\n    if (typeof content === \"string\") {\n      const isHtml = /<\\/?[a-z][\\s\\S]*>/i.test(content);\n\n      if (isHtml) {\n        return generateJSON(content, loadedExtensions);\n      }\n\n      return {\n        type: \"doc\",\n        content: [\n          { type: \"paragraph\", content: [{ type: \"text\", text: content }] },\n        ],\n      };\n    }\n\n    return { type: \"doc\", content: [] };\n  }, [content, loadedExtensions]);\n\n  const html = useMemo(() => {\n    return generateHTML(normalized, loadedExtensions);\n  }, [normalized, loadedExtensions]);\n\n  return (\n    <div className={getClassName()}>\n      <div className=\"rich-text\" dangerouslySetInnerHTML={{ __html: html }} />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/components/RenderFallback.tsx",
    "content": "/** Fallback component. Should not contain any tiptap imports (except for types) */\n\nimport getClassNameFactory from \"../../../lib/get-class-name-factory\";\nimport styles from \"../styles.module.css\";\n\nconst getClassName = getClassNameFactory(\"RichTextEditor\", styles);\n\nexport function RichTextRenderFallback({ content }: { content: string }) {\n  return (\n    <div className={getClassName()}>\n      <div\n        className=\"rich-text\"\n        dangerouslySetInnerHTML={{ __html: content }}\n      />\n    </div>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/extension.ts",
    "content": "import { Extension } from \"@tiptap/core\";\nimport type { BlockquoteOptions } from \"@tiptap/extension-blockquote\";\nimport { Blockquote } from \"@tiptap/extension-blockquote\";\nimport type { BoldOptions } from \"@tiptap/extension-bold\";\nimport { Bold } from \"@tiptap/extension-bold\";\nimport type { CodeOptions } from \"@tiptap/extension-code\";\nimport { Code } from \"@tiptap/extension-code\";\nimport type { CodeBlockOptions } from \"@tiptap/extension-code-block\";\nimport { CodeBlock } from \"@tiptap/extension-code-block\";\nimport { Document } from \"@tiptap/extension-document\";\nimport type { HardBreakOptions } from \"@tiptap/extension-hard-break\";\nimport { HardBreak } from \"@tiptap/extension-hard-break\";\nimport type { HeadingOptions } from \"@tiptap/extension-heading\";\nimport { Heading } from \"@tiptap/extension-heading\";\nimport type { HorizontalRuleOptions } from \"@tiptap/extension-horizontal-rule\";\nimport { HorizontalRule } from \"@tiptap/extension-horizontal-rule\";\nimport type { ItalicOptions } from \"@tiptap/extension-italic\";\nimport { Italic } from \"@tiptap/extension-italic\";\nimport type { LinkOptions } from \"@tiptap/extension-link\";\nimport { Link } from \"@tiptap/extension-link\";\nimport type {\n  BulletListOptions,\n  ListItemOptions,\n  ListKeymapOptions,\n  OrderedListOptions,\n} from \"@tiptap/extension-list\";\nimport {\n  BulletList,\n  ListItem,\n  ListKeymap,\n  OrderedList,\n} from \"@tiptap/extension-list\";\nimport type { ParagraphOptions } from \"@tiptap/extension-paragraph\";\nimport { Paragraph } from \"@tiptap/extension-paragraph\";\nimport type { StrikeOptions } from \"@tiptap/extension-strike\";\nimport { Strike } from \"@tiptap/extension-strike\";\nimport { Text } from \"@tiptap/extension-text\";\nimport TextAlign, { TextAlignOptions } from \"@tiptap/extension-text-align\";\nimport type { UnderlineOptions } from \"@tiptap/extension-underline\";\nimport { Underline } from \"@tiptap/extension-underline\";\n\nexport interface PuckRichTextOptions {\n  /**\n   * If set to false, the blockquote extension will not be registered\n   * @example blockquote: false\n   */\n  blockquote: Partial<BlockquoteOptions> | false;\n\n  /**\n   * If set to false, the bold extension will not be registered\n   * @example bold: false\n   */\n  bold: Partial<BoldOptions> | false;\n\n  /**\n   * If set to false, the bulletList extension will not be registered\n   * @example bulletList: false\n   */\n  bulletList: Partial<BulletListOptions> | false;\n\n  /**\n   * If set to false, the code extension will not be registered\n   * @example code: false\n   */\n  code: Partial<CodeOptions> | false;\n\n  /**\n   * If set to false, the codeBlock extension will not be registered\n   * @example codeBlock: false\n   */\n  codeBlock: Partial<CodeBlockOptions> | false;\n\n  /**\n   * If set to false, the document extension will not be registered\n   * @example document: false\n   */\n  document: false;\n\n  /**\n   * If set to false, the hardBreak extension will not be registered\n   * @example hardBreak: false\n   */\n  hardBreak: Partial<HardBreakOptions> | false;\n\n  /**\n   * If set to false, the heading extension will not be registered\n   * @example heading: false\n   */\n  heading: Partial<HeadingOptions> | false;\n\n  /**\n   * If set to false, the horizontalRule extension will not be registered\n   * @example horizontalRule: false\n   */\n  horizontalRule: Partial<HorizontalRuleOptions> | false;\n\n  /**\n   * If set to false, the italic extension will not be registered\n   * @example italic: false\n   */\n  italic: Partial<ItalicOptions> | false;\n\n  /**\n   * If set to false, the listItem extension will not be registered\n   * @example listItem: false\n   */\n  listItem: Partial<ListItemOptions> | false;\n\n  /**\n   * If set to false, the listItemKeymap extension will not be registered\n   * @example listKeymap: false\n   */\n  listKeymap: Partial<ListKeymapOptions> | false;\n\n  /**\n   * If set to false, the link extension will not be registered\n   * @example link: false\n   */\n  link: Partial<LinkOptions> | false;\n\n  /**\n   * If set to false, the orderedList extension will not be registered\n   * @example orderedList: false\n   */\n  orderedList: Partial<OrderedListOptions> | false;\n\n  /**\n   * If set to false, the paragraph extension will not be registered\n   * @example paragraph: false\n   */\n  paragraph: Partial<ParagraphOptions> | false;\n\n  /**\n   * If set to false, the strike extension will not be registered\n   * @example strike: false\n   */\n  strike: Partial<StrikeOptions> | false;\n\n  /**\n   * If set to false, the text extension will not be registered\n   * @example text: false\n   */\n  text: false;\n\n  /**\n   * If set to false, the textAlign extension will not be registered\n   * @example text: false\n   */\n  textAlign: Partial<TextAlignOptions> | false;\n\n  /**\n   * If set to false, the underline extension will not be registered\n   * @example underline: false\n   */\n  underline: Partial<UnderlineOptions> | false;\n}\n\nexport const defaultPuckRichTextOptions: Partial<PuckRichTextOptions> = {\n  textAlign: {\n    types: [\"heading\", \"paragraph\"],\n  },\n};\n\nexport const PuckRichText = Extension.create<PuckRichTextOptions>({\n  name: \"puckRichText\",\n  addExtensions() {\n    const extensions = [];\n\n    const options: Partial<PuckRichTextOptions> = {\n      ...this.options,\n      ...defaultPuckRichTextOptions,\n    };\n\n    if (options.bold !== false) {\n      extensions.push(Bold.configure(options.bold));\n    }\n\n    if (options.blockquote !== false) {\n      extensions.push(Blockquote.configure(options.blockquote));\n    }\n\n    if (options.code !== false) {\n      extensions.push(Code.configure(options.code));\n    }\n\n    if (options.codeBlock !== false) {\n      extensions.push(CodeBlock.configure(options.codeBlock));\n    }\n\n    if (options.document !== false) {\n      extensions.push(Document.configure(options.document));\n    }\n\n    if (options.hardBreak !== false) {\n      extensions.push(HardBreak.configure(options.hardBreak));\n    }\n\n    if (options.heading !== false) {\n      extensions.push(Heading.configure(options.heading));\n    }\n\n    if (options.horizontalRule !== false) {\n      extensions.push(HorizontalRule.configure(options.horizontalRule));\n    }\n\n    if (options.italic !== false) {\n      extensions.push(Italic.configure(options.italic));\n    }\n\n    // Bullet lists and ordered lists require listItem\n    if (options.listItem !== false) {\n      extensions.push(ListItem.configure(options.listItem));\n\n      if (options.bulletList !== false) {\n        extensions.push(BulletList.configure(options.bulletList));\n      }\n\n      if (options.orderedList !== false) {\n        extensions.push(OrderedList.configure(options.orderedList));\n      }\n    }\n\n    if (options.listKeymap !== false) {\n      extensions.push(ListKeymap.configure(options?.listKeymap));\n    }\n\n    if (options.link !== false) {\n      extensions.push(Link.configure(options?.link));\n    }\n\n    if (options.paragraph !== false) {\n      extensions.push(Paragraph.configure(options.paragraph));\n    }\n\n    if (options.strike !== false) {\n      extensions.push(Strike.configure(options.strike));\n    }\n\n    if (options.text !== false) {\n      extensions.push(Text.configure(options.text));\n    }\n\n    if (options.textAlign !== false) {\n      extensions.push(TextAlign.configure(options.textAlign));\n    }\n\n    if (options.underline !== false) {\n      extensions.push(Underline.configure(options?.underline));\n    }\n\n    return extensions;\n  },\n});\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/index.ts",
    "content": "export * from \"./Editor\";\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/lib/mapDeep.ts",
    "content": "import { ReactNode } from \"react\";\n\nexport const mapDeep = (\n  source: any,\n  path: string[],\n  render: (v: any) => ReactNode\n): any => {\n  if (!source) {\n    return null;\n  }\n\n  if (path.length === 0) {\n    return render(source);\n  }\n\n  const [key, ...rest] = path;\n\n  if (Array.isArray(source)) {\n    return source.map((item) => mapDeep(item, path, render));\n  }\n\n  return {\n    ...source,\n    [key]: mapDeep(source![key], rest, render),\n  };\n};\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/lib/use-richtext-props.tsx",
    "content": "import { lazy, Suspense, useMemo } from \"react\";\nimport {\n  BaseField,\n  Fields,\n  RichtextField,\n  WithPuckProps,\n} from \"../../../types\";\nimport { RichTextRenderFallback } from \"../components/RenderFallback\";\nimport { generateId } from \"../../../lib/generate-id\";\nimport { mapDeep } from \"./mapDeep\";\n\ntype RichtextPath = {\n  path: string[];\n  field: RichtextField;\n};\n\nexport function useRichtextProps(\n  fields:\n    | Fields<any, {}>\n    | Fields<any, { type: string } & BaseField>\n    | undefined,\n  props: WithPuckProps<{\n    [x: string]: any;\n  }>\n) {\n  const findAllRichtextKeys = (\n    fields:\n      | Fields<any, {}>\n      | Fields<any, { type: string } & BaseField>\n      | undefined,\n    path: string[] = []\n  ): RichtextPath[] => {\n    if (!fields) return [];\n\n    const result: RichtextPath[] = [];\n\n    for (const [key, field] of Object.entries(fields)) {\n      const currentPath = [...path, key];\n\n      if (field.type === \"richtext\") {\n        result.push({\n          path: currentPath,\n          field: field as RichtextField,\n        });\n      }\n\n      if (field.type === \"array\" && \"arrayFields\" in field) {\n        result.push(...findAllRichtextKeys(field.arrayFields, currentPath));\n      }\n\n      if (field.type === \"object\" && \"objectFields\" in field) {\n        result.push(...findAllRichtextKeys(field.objectFields, currentPath));\n      }\n    }\n\n    return result;\n  };\n\n  const richtextKeys = useMemo(() => findAllRichtextKeys(fields), [fields]);\n\n  const richtextProps = useMemo(() => {\n    if (!richtextKeys?.length) return {};\n\n    const RichTextRender = lazy(() =>\n      import(\"../components/Render\").then((m) => ({\n        default: m.RichTextRender,\n      }))\n    );\n\n    let result = { ...props };\n\n    for (const { path, field } of richtextKeys) {\n      result = mapDeep(result, path, (content) => (\n        <Suspense\n          key={generateId()}\n          fallback={<RichTextRenderFallback content={content} />}\n        >\n          <RichTextRender content={content} field={field} />\n        </Suspense>\n      ));\n    }\n\n    return result;\n  }, [richtextKeys, props, fields]);\n\n  return richtextProps;\n}\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/lib/use-synced-editor.ts",
    "content": "import { useEditor } from \"@tiptap/react\";\nimport type { Extensions, JSONContent, Editor } from \"@tiptap/react\";\nimport { useEffect, useRef } from \"react\";\nimport { useDebounce } from \"use-debounce\";\nimport { UiState } from \"../../../types\";\nimport { useAppStore, useAppStoreApi } from \"../../../store\";\n\nexport function useSyncedEditor({\n  content,\n  onChange,\n  extensions,\n  editable = true,\n  onFocusChange,\n  name,\n}: {\n  content: JSONContent | string;\n  onChange: (content: JSONContent | string, uiState?: Partial<UiState>) => void;\n  extensions: Extensions;\n  editable?: boolean;\n  onFocusChange?: (editor: Editor | null) => void;\n  name: string | undefined;\n}) {\n  const [debouncedState, setDebouncedState] = useDebounce<{\n    from: number;\n    to: number;\n    html: string;\n  } | null>(null, 50, {\n    leading: true,\n    maxWait: 200,\n  });\n\n  const syncingRef = useRef(false);\n  const lastSyncedRef = useRef(\"\");\n  const editTimer = useRef<NodeJS.Timeout>(null);\n  const isPending = !!editTimer.current;\n  const isFocused = useAppStore((s) => s.state.ui.field.focus === name);\n\n  const resetTimer = (clearOn: string) => {\n    if (editTimer.current) {\n      clearTimeout(editTimer.current);\n    }\n\n    editTimer.current = setTimeout(() => {\n      if (lastSyncedRef.current === clearOn) {\n        editTimer.current = null;\n      }\n    }, 200);\n  };\n\n  const appStoreApi = useAppStoreApi();\n\n  const editor = useEditor({\n    extensions,\n    content,\n    editable,\n    immediatelyRender: false,\n    parseOptions: { preserveWhitespace: \"full\" },\n    onUpdate: ({ editor }) => {\n      // This can trigger during undo/redo history loads\n      if (syncingRef.current || !isFocused) {\n        appStoreApi.getState().setUi({ field: { focus: name } });\n\n        return;\n      }\n\n      const html = editor.getHTML();\n\n      const { from, to } = editor.state.selection;\n\n      setDebouncedState({ from, to, html });\n      resetTimer(html);\n\n      lastSyncedRef.current = html;\n    },\n  });\n\n  useEffect(() => {\n    if (!editor) return;\n\n    const handleFocus = () => {\n      onFocusChange?.(editor);\n    };\n\n    editor.on(\"focus\", handleFocus);\n    return () => {\n      editor.off(\"focus\", handleFocus);\n    };\n  }, [editor, onFocusChange]);\n\n  // Push debounced changes up to parent\n  useEffect(() => {\n    if (debouncedState) {\n      const { ui } = appStoreApi.getState().state;\n\n      onChange(debouncedState.html, {\n        field: {\n          ...ui.field,\n          metadata: { from: debouncedState.from, to: debouncedState.to },\n        },\n      });\n    }\n  }, [editor, debouncedState, onChange, appStoreApi, name]);\n\n  useEffect(() => {\n    editor?.setEditable(editable);\n  }, [editor, editable]);\n\n  // Bring in external content changes without causing flicker on blur\n  useEffect(() => {\n    if (!editor) return;\n\n    // If the editor currently has pending changes, don't stomp what the user is typing\n    if (isPending) {\n      return;\n    }\n\n    // Compare current doc vs incoming doc; if same, skip\n    const current = editor.getHTML();\n\n    if (current === content) return;\n\n    syncingRef.current = true;\n\n    editor.commands.setContent(content, { emitUpdate: false });\n\n    const { ui } = appStoreApi.getState().state;\n\n    if (typeof ui.field.metadata?.from !== \"undefined\") {\n      editor.commands.setTextSelection({\n        from: ui.field.metadata.from,\n        to: ui.field.metadata.to,\n      });\n    }\n\n    syncingRef.current = false;\n  }, [content, editor, appStoreApi]);\n\n  return editor;\n}\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/selector.ts",
    "content": "import type { EditorStateSnapshot } from \"@tiptap/react\";\n\nexport const defaultEditorState = (\n  ctx: EditorStateSnapshot,\n  readOnly: boolean\n) => {\n  const editor = ctx.editor;\n  if (!editor) return {};\n\n  const canChain = () => editor.can().chain();\n\n  return {\n    isAlignLeft: editor.isActive({ textAlign: \"left\" }),\n    canAlignLeft: !readOnly && canChain().setTextAlign?.(\"left\").run(),\n\n    isAlignCenter: editor.isActive({ textAlign: \"center\" }),\n    canAlignCenter: !readOnly && canChain().setTextAlign?.(\"center\").run(),\n\n    isAlignRight: editor.isActive({ textAlign: \"right\" }),\n    canAlignRight: !readOnly && canChain().setTextAlign?.(\"right\").run(),\n\n    isAlignJustify: editor.isActive({ textAlign: \"justify\" }),\n    canAlignJustify: !readOnly && canChain().setTextAlign?.(\"justify\").run(),\n\n    isBold: editor.isActive(\"bold\"),\n    canBold: !readOnly && canChain().toggleBold?.().run(),\n\n    isItalic: editor.isActive(\"italic\"),\n    canItalic: !readOnly && canChain().toggleItalic?.().run(),\n\n    isUnderline: editor.isActive(\"underline\"),\n    canUnderline: !readOnly && canChain().toggleUnderline?.().run(),\n\n    isStrike: editor.isActive(\"strike\"),\n    canStrike: !readOnly && canChain().toggleStrike?.().run(),\n\n    isInlineCode: editor.isActive(\"code\"),\n    canInlineCode: !readOnly && canChain().toggleCode?.().run(),\n\n    isBulletList: editor.isActive(\"bulletList\"),\n    canBulletList: !readOnly && canChain().toggleBulletList?.().run(),\n\n    isOrderedList: editor.isActive(\"orderedList\"),\n    canOrderedList: !readOnly && canChain().toggleOrderedList?.().run(),\n\n    isCodeBlock: editor.isActive(\"codeBlock\"),\n    canCodeBlock: !readOnly && canChain().toggleCodeBlock?.().run(),\n\n    isBlockquote: editor.isActive(\"blockquote\"),\n    canBlockquote: !readOnly && canChain().toggleBlockquote?.().run(),\n\n    canHorizontalRule: !readOnly && canChain().setHorizontalRule?.().run(),\n  };\n};\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/styles.module.css",
    "content": ".RichTextEditor :global(.ProseMirror) {\n  white-space: pre-wrap;\n  word-wrap: break-word;\n  cursor: text;\n  outline: none;\n  position: relative;\n}\n\n.RichTextEditor :global(.rich-text *) {\n  white-space: pre-wrap;\n\n  /* Reinstate user select (removed in data-puck-component) to ensure Firefox selection works with contentEditable  */\n  user-select: auto;\n  -webkit-user-select: auto;\n}\n\n.RichTextEditor :global(.rich-text blockquote) {\n  margin: 1em 0;\n  padding: 0 1em;\n  border-left: 4px solid var(--puck-color-grey-09);\n}\n\n.RichTextEditor :global(.rich-text code) {\n  background-color: var(--puck-color-grey-11);\n  padding: 4px 8px;\n  border-radius: 4px;\n}\n\n.RichTextEditor :global(.rich-text p:empty::before) {\n  content: \"\\00a0\";\n}\n\n.RichTextEditor :global(.rich-text pre code) {\n  display: block;\n  padding: 8px 12px;\n}\n\n.RichTextEditor :global(.rich-text > *:first-child),\n.RichTextEditor :global(.ProseMirror > *:first-child),\n.RichTextEditor :global(.rich-text * p:first-of-type) {\n  margin-top: 0;\n}\n\n.RichTextEditor :global(.rich-text > *:last-child),\n.RichTextEditor :global(.ProseMirror > *:last-child),\n.RichTextEditor :global(.rich-text * p:last-of-type) {\n  margin-bottom: 0;\n}\n\n.RichTextEditor--editor {\n  background: var(--puck-color-white);\n  border-width: 1px;\n  border-style: solid;\n  border-color: var(--puck-color-grey-09);\n  border-radius: 4px;\n  box-sizing: border-box;\n  display: flex;\n  flex-direction: column;\n  font-family: inherit;\n  font-size: var(--puck-font-size-xxs);\n  resize: vertical;\n  text-align: initial;\n  transition: border-color 50ms ease-in;\n  width: 100%;\n  max-width: 100%;\n  min-height: 128px;\n}\n\n.RichTextEditor--editor :global(.rich-text) {\n  flex-grow: 1;\n}\n\n/* Place padding on .rich-text, unless it contains .ProseMirror - in which case, apply to .ProseMirror */\n.RichTextEditor--editor :global(.rich-text:not(:has(.ProseMirror))),\n.RichTextEditor--editor :global(.rich-text .ProseMirror) {\n  height: 100%;\n  padding: 12px 15px;\n}\n\n.RichTextEditor--editor :global(.rich-text ul),\n.RichTextEditor--editor :global(.rich-text ol) {\n  padding-left: 24px;\n}\n\n.RichTextEditor--editor :global(.rich-text li) {\n  line-height: 1.5;\n}\n\n.RichTextEditor--editor :global(.rich-text p) {\n  margin-block: 12px;\n}\n\n.RichTextEditor--editor :global(.rich-text ul) {\n  list-style: disc;\n}\n\n.RichTextEditor--editor :global(.rich-text ol) {\n  list-style: decimal;\n}\n\n.RichTextEditor--editor:focus-within {\n  border-color: var(--puck-color-grey-05);\n  outline: 2px solid var(--puck-color-azure-05);\n  transition: none;\n}\n\n.RichTextEditor--editor.RichTextEditor--disabled {\n  background: var(--puck-color-grey-11);\n}\n\n.RichTextEditor:not(:focus-within):not(.RichTextEditor--isActive)\n  :global(.ProseMirror)\n  ::selection {\n  background-color: transparent;\n}\n\n.RichTextEditor-menu {\n  border-bottom: 1px solid var(--puck-color-grey-10);\n  position: sticky;\n  top: 0;\n  z-index: 1;\n}\n\n.RichTextEditor--disabled .RichTextEditor-menu {\n  border-bottom: 1px solid var(--puck-color-grey-09);\n}\n"
  },
  {
    "path": "packages/core/components/RichTextEditor/types.ts",
    "content": "import type { Editor, EditorStateSnapshot, JSONContent } from \"@tiptap/react\";\nimport { useSyncedEditor } from \"./lib/use-synced-editor\";\nimport { defaultEditorState } from \"./selector\";\nimport { RichtextField, UiState } from \"../../types\";\n\nexport type RichTextSelector = (\n  ctx: EditorStateSnapshot,\n  readOnly: boolean\n) => Partial<Record<string, boolean>>;\n\nexport type DefaultEditorState = ReturnType<typeof defaultEditorState>;\n\nexport type EditorState<Selector extends RichTextSelector = RichTextSelector> =\n  DefaultEditorState &\n    ReturnType<Selector> &\n    Record<string, boolean | undefined>;\n\nexport type EditorProps = {\n  onChange: (content: string | JSONContent, uiState?: Partial<UiState>) => void;\n  content: string;\n  readOnly?: boolean;\n  inline?: boolean;\n  field: RichtextField;\n  onFocus?: (editor: Editor) => void;\n  id: string;\n  name?: string;\n};\n\nexport type RichTextEditor = NonNullable<ReturnType<typeof useSyncedEditor>>;\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/components/Control/index.tsx",
    "content": "import { ReactNode, SyntheticEvent } from \"react\";\nimport { IconButton } from \"../../../IconButton\";\nimport { Action } from \"../../../ActionBar\";\nimport { getClassNameFactory } from \"../../../../lib\";\nimport styles from \"./styles.module.css\";\nimport { useControlContext } from \"../../lib/use-control-context\";\n\nconst getClassName = getClassNameFactory(\"Control\", styles);\n\nexport function Control({\n  icon,\n  disabled,\n  active,\n  onClick,\n  title,\n}: {\n  icon: ReactNode;\n  disabled?: boolean;\n  active?: boolean;\n  onClick: (e: SyntheticEvent) => any;\n  title: string;\n}) {\n  const { inline } = useControlContext();\n\n  if (inline) {\n    return (\n      <span className={getClassName({ inline: true })}>\n        <Action\n          onClick={onClick}\n          disabled={disabled}\n          active={active}\n          label={title}\n        >\n          {icon}\n        </Action>\n      </span>\n    );\n  }\n\n  return (\n    <span className={getClassName()}>\n      <IconButton\n        onClick={onClick}\n        disabled={disabled}\n        active={active}\n        title={title}\n      >\n        {icon}\n      </IconButton>\n    </span>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/components/Control/styles.module.css",
    "content": ".Control :global(.lucide) {\n  height: 18px;\n  width: 18px;\n}\n\n.Control--inline :global(.lucide) {\n  height: 16px;\n  width: 16px;\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/components/SelectControl/index.tsx",
    "content": "import { useControlContext } from \"../../lib/use-control-context\";\nimport { JSXElementConstructor, useMemo } from \"react\";\nimport { Select } from \"../../../Select\";\n\nexport type Option<T = string> = { label: string; value: T; icon?: React.FC };\nexport type Options<T = string> = Option<T>[];\n\nexport function SelectControl<ValueType extends string = string>({\n  renderDefaultIcon,\n  onChange,\n  options,\n  value,\n  defaultValue,\n}: {\n  renderDefaultIcon: JSXElementConstructor<any>;\n  onChange: (val: ValueType) => void;\n  options: Option<ValueType>[];\n  value: ValueType;\n  defaultValue: ValueType;\n}) {\n  const { inline, readOnly } = useControlContext();\n\n  type OptionsByValue = Record<ValueType, Option>;\n\n  const optionsByValue = useMemo(\n    () =>\n      options.reduce<OptionsByValue>(\n        (acc, option) => ({ ...acc, [option.value]: option }),\n        {} as OptionsByValue\n      ),\n    [options]\n  );\n\n  const Node = (value && optionsByValue[value]?.icon) ?? renderDefaultIcon;\n\n  return (\n    <Select\n      options={options}\n      onChange={onChange}\n      value={value}\n      defaultValue={defaultValue}\n      mode={inline ? \"actionBar\" : \"standalone\"}\n      disabled={readOnly}\n    >\n      <Node />\n    </Select>\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignCenter.tsx",
    "content": "import { AlignCenter as AlignCenterIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function AlignCenter() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<AlignCenterIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().setTextAlign(\"center\").run();\n      }}\n      disabled={!editorState?.canAlignCenter}\n      active={editorState?.isAlignCenter}\n      title=\"Align center\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignJustify.tsx",
    "content": "import { AlignJustify as AlignJustifyIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function AlignJustify() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<AlignJustifyIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().setTextAlign(\"justify\").run();\n      }}\n      disabled={!editorState?.canAlignJustify}\n      active={editorState?.isAlignJustify}\n      title=\"Justify\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignLeft.tsx",
    "content": "import { AlignLeft as AlignLeftIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function AlignLeft() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<AlignLeftIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().setTextAlign(\"left\").run();\n      }}\n      disabled={!editorState?.canAlignLeft}\n      active={editorState?.isAlignLeft}\n      title=\"Align left\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignRight.tsx",
    "content": "import { AlignRight as AlignRightIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function AlignRight() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<AlignRightIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().setTextAlign(\"right\").run();\n      }}\n      disabled={!editorState?.canAlignRight}\n      active={editorState?.isAlignRight}\n      title=\"Align right\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignSelect/fallback.tsx",
    "content": "import { useControlContext } from \"../../lib/use-control-context\";\nimport { AlignLeft } from \"lucide-react\";\nimport { SelectControl } from \"../../components/SelectControl\";\nimport { useAlignOptions, AlignDirection } from \"./use-options\";\n\nexport function AlignSelectFallback() {\n  const ctx = useControlContext();\n  const alignOptions = useAlignOptions(ctx.options);\n\n  return (\n    <SelectControl<AlignDirection>\n      options={alignOptions}\n      onChange={() => {}}\n      value=\"left\"\n      defaultValue=\"left\"\n      renderDefaultIcon={AlignLeft}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignSelect/index.tsx",
    "content": "import { lazy, Suspense } from \"react\";\nimport { AlignSelectFallback } from \"./fallback\";\n\nconst AlignSelectLoaded = lazy(() =>\n  import(\"./loaded\").then((m) => ({\n    default: m.AlignSelectLoaded,\n  }))\n);\n\nexport const AlignSelect = () => (\n  <Suspense fallback={<AlignSelectFallback />}>\n    <AlignSelectLoaded />\n  </Suspense>\n);\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignSelect/loaded.tsx",
    "content": "import { useEditorState } from \"@tiptap/react\";\nimport { useControlContext } from \"../../lib/use-control-context\";\nimport { AlignLeft } from \"lucide-react\";\nimport { SelectControl } from \"../../components/SelectControl\";\nimport { useAlignOptions } from \"./use-options\";\n\ntype AlignDirection = \"left\" | \"center\" | \"right\" | \"justify\";\n\nexport function AlignSelectLoaded() {\n  const { options } = useControlContext();\n\n  const alignOptions = useAlignOptions(options);\n\n  const { editor } = useControlContext();\n  const currentValue: AlignDirection =\n    useEditorState({\n      editor,\n      selector: (ctx) => {\n        if (ctx.editor?.isActive({ textAlign: \"center\" })) {\n          return \"center\";\n        } else if (ctx.editor?.isActive({ textAlign: \"right\" })) {\n          return \"right\";\n        } else if (ctx.editor?.isActive({ textAlign: \"justify\" })) {\n          return \"justify\";\n        }\n\n        return options?.textAlign\n          ? (options.textAlign.defaultAlignment as AlignDirection) ?? \"left\"\n          : \"left\";\n      },\n    }) ?? \"left\";\n\n  const handleChange = (val: AlignDirection) => {\n    const chain = editor?.chain();\n\n    chain?.focus().setTextAlign(val).run();\n  };\n\n  return (\n    <SelectControl<AlignDirection>\n      options={alignOptions}\n      onChange={handleChange}\n      value={currentValue}\n      defaultValue=\"left\"\n      renderDefaultIcon={AlignLeft}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/AlignSelect/use-options.ts",
    "content": "import { useMemo } from \"react\";\nimport { AlignCenter, AlignJustify, AlignLeft, AlignRight } from \"lucide-react\";\nimport { RichtextField } from \"../../../../types\";\n\nconst optionNodes: Record<string, { label: string; icon?: React.FC }> = {\n  left: { label: \"Left\", icon: AlignLeft },\n  center: { label: \"Center\", icon: AlignCenter },\n  right: { label: \"Right\", icon: AlignRight },\n  justify: { label: \"Justify\", icon: AlignJustify },\n};\n\nexport type AlignDirection = \"left\" | \"center\" | \"right\" | \"justify\";\n\nexport const useAlignOptions = (fieldOptions: RichtextField[\"options\"]) => {\n  let blockOptions: AlignDirection[] = [];\n\n  if (fieldOptions?.textAlign !== false) {\n    if (!fieldOptions?.textAlign?.alignments) {\n      blockOptions = [\"left\", \"center\", \"right\", \"justify\"];\n    } else {\n      if (fieldOptions?.textAlign.alignments.includes(\"left\")) {\n        blockOptions.push(\"left\");\n      }\n\n      if (fieldOptions?.textAlign.alignments.includes(\"center\")) {\n        blockOptions.push(\"center\");\n      }\n\n      if (fieldOptions?.textAlign.alignments.includes(\"right\")) {\n        blockOptions.push(\"right\");\n      }\n\n      if (fieldOptions?.textAlign.alignments.includes(\"justify\")) {\n        blockOptions.push(\"justify\");\n      }\n    }\n  }\n\n  return useMemo(\n    () =>\n      blockOptions.map((item) => ({\n        value: item,\n        label: optionNodes[item].label,\n        icon: optionNodes[item].icon,\n      })),\n    [blockOptions]\n  );\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/Blockquote.tsx",
    "content": "import { Quote as QuoteIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function Blockquote() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<QuoteIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleBlockquote().run();\n      }}\n      disabled={!editorState?.canBlockquote}\n      active={editorState?.isBlockquote}\n      title=\"Blockquote\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/Bold.tsx",
    "content": "import { Bold as BoldIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function Bold() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<BoldIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleBold().run();\n      }}\n      disabled={!editorState?.canBold}\n      active={editorState?.isBold}\n      title=\"Bold\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/BulletList.tsx",
    "content": "import { List as ListIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function BulletList() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<ListIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleBulletList().run();\n      }}\n      disabled={!editorState?.canBulletList}\n      active={editorState?.isBulletList}\n      title=\"Bullet list\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/CodeBlock.tsx",
    "content": "import { SquareCode as SquareCodeIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function CodeBlock() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<SquareCodeIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleCodeBlock().run();\n      }}\n      disabled={!editorState?.canCodeBlock}\n      active={editorState?.isCodeBlock}\n      title=\"Code block\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/HeadingSelect/fallback.tsx",
    "content": "import { Heading } from \"lucide-react\";\nimport { SelectControl } from \"../../components/SelectControl\";\nimport { useControlContext } from \"../../lib/use-control-context\";\nimport { HeadingElement, useHeadingOptions } from \"./use-options\";\n\nexport function HeadingSelectFallback() {\n  const ctx = useControlContext();\n  const headingOptions = useHeadingOptions(ctx.options);\n\n  return (\n    <SelectControl<HeadingElement | \"p\">\n      options={headingOptions}\n      onChange={() => {}}\n      value=\"p\"\n      defaultValue=\"p\"\n      renderDefaultIcon={Heading}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/HeadingSelect/index.tsx",
    "content": "import { lazy, Suspense } from \"react\";\nimport { HeadingSelectFallback } from \"./fallback\";\n\nconst HeadingSelectLoaded = lazy(() =>\n  import(\"./loaded\").then((m) => ({\n    default: m.HeadingSelectLoaded,\n  }))\n);\n\nexport const HeadingSelect = () => (\n  <Suspense fallback={<HeadingSelectFallback />}>\n    <HeadingSelectLoaded />\n  </Suspense>\n);\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/HeadingSelect/loaded.tsx",
    "content": "import { useEditorState } from \"@tiptap/react\";\nimport { useControlContext } from \"../../lib/use-control-context\";\nimport { Heading } from \"lucide-react\";\nimport { SelectControl } from \"../../components/SelectControl\";\nimport { HeadingElement, useHeadingOptions } from \"./use-options\";\n\nexport function HeadingSelectLoaded() {\n  const { options } = useControlContext();\n  const headingOptions = useHeadingOptions(options);\n\n  const { editor } = useControlContext();\n  const currentValue = useEditorState({\n    editor,\n    selector: (ctx) => {\n      if (ctx.editor?.isActive(\"paragraph\")) return \"p\";\n      for (let level = 1; level <= 6; level++) {\n        if (ctx.editor?.isActive(\"heading\", { level })) {\n          return `h${level}` as HeadingElement;\n        }\n      }\n      return \"p\";\n    },\n  });\n\n  const handleChange = (val: HeadingElement | \"p\") => {\n    const chain = editor?.chain();\n\n    if (val === \"p\") {\n      chain?.focus().setParagraph().run();\n    } else {\n      const level = parseInt(val.replace(\"h\", \"\"), 10) as 1 | 2 | 3 | 4 | 5 | 6;\n      chain?.focus().toggleHeading({ level }).run();\n    }\n  };\n\n  return (\n    <SelectControl<HeadingElement | \"p\">\n      options={headingOptions}\n      onChange={handleChange}\n      value={currentValue ?? \"p\"}\n      defaultValue=\"p\"\n      renderDefaultIcon={Heading}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/HeadingSelect/use-options.ts",
    "content": "import { useMemo } from \"react\";\nimport {\n  Heading1,\n  Heading2,\n  Heading3,\n  Heading4,\n  Heading5,\n  Heading6,\n} from \"lucide-react\";\nimport { RichtextField } from \"../../../../types\";\n\nconst optionNodes: Record<string, { label: string; icon?: React.FC }> = {\n  h1: { label: \"Heading 1\", icon: Heading1 },\n  h2: { label: \"Heading 2\", icon: Heading2 },\n  h3: { label: \"Heading 3\", icon: Heading3 },\n  h4: { label: \"Heading 4\", icon: Heading4 },\n  h5: { label: \"Heading 5\", icon: Heading5 },\n  h6: { label: \"Heading 6\", icon: Heading6 },\n};\n\nexport type HeadingElement = \"h1\" | \"h2\" | \"h3\" | \"h4\" | \"h5\" | \"h6\";\n\nexport const useHeadingOptions = (fieldOptions: RichtextField[\"options\"]) => {\n  let blockOptions: HeadingElement[] = [];\n\n  if (fieldOptions?.heading !== false) {\n    if (!fieldOptions?.heading?.levels) {\n      blockOptions = [\"h1\", \"h2\", \"h3\", \"h4\", \"h5\", \"h6\"];\n    } else {\n      if (fieldOptions?.heading.levels.includes(1)) {\n        blockOptions.push(\"h1\");\n      }\n\n      if (fieldOptions?.heading.levels.includes(2)) {\n        blockOptions.push(\"h2\");\n      }\n\n      if (fieldOptions?.heading.levels.includes(3)) {\n        blockOptions.push(\"h3\");\n      }\n\n      if (fieldOptions?.heading.levels.includes(4)) {\n        blockOptions.push(\"h4\");\n      }\n\n      if (fieldOptions?.heading.levels.includes(5)) {\n        blockOptions.push(\"h5\");\n      }\n\n      if (fieldOptions?.heading.levels.includes(6)) {\n        blockOptions.push(\"h6\");\n      }\n    }\n  }\n\n  return useMemo(\n    () =>\n      blockOptions.map((item) => ({\n        value: item,\n        label: optionNodes[item].label,\n        icon: optionNodes[item].icon,\n      })),\n    [blockOptions]\n  );\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/HorizontalRule.tsx",
    "content": "import { Minus as MinusIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function HorizontalRule() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<MinusIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().setHorizontalRule().run();\n      }}\n      disabled={!editorState?.canHorizontalRule}\n      title=\"Horizontal rule\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/InlineCode.tsx",
    "content": "import { Code as CodeIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function InlineCode() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<CodeIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleCode().run();\n      }}\n      disabled={!editorState?.canInlineCode}\n      active={editorState?.isInlineCode}\n      title=\"Inline code\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/Italic.tsx",
    "content": "import { Italic as ItalicIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function Italic() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<ItalicIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleItalic().run();\n      }}\n      disabled={!editorState?.canItalic}\n      active={editorState?.isItalic}\n      title=\"Italic\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/ListSelect/fallback.tsx",
    "content": "import { SelectControl } from \"../../components/SelectControl\";\nimport { useControlContext } from \"../../lib/use-control-context\";\nimport { List } from \"lucide-react\";\nimport { ListElement, useListOptions } from \"./use-options\";\n\nexport function ListSelectFallback() {\n  const ctx = useControlContext();\n  const listOptions = useListOptions(ctx.options);\n\n  return (\n    <SelectControl<ListElement | \"p\">\n      options={listOptions}\n      onChange={() => {}}\n      value=\"p\"\n      defaultValue=\"p\"\n      renderDefaultIcon={List}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/ListSelect/index.tsx",
    "content": "import { lazy, Suspense } from \"react\";\nimport { ListSelectFallback } from \"./fallback\";\n\nconst ListSelectLoaded = lazy(() =>\n  import(\"./loaded\").then((m) => ({\n    default: m.ListSelectLoaded,\n  }))\n);\n\nexport const ListSelect = () => (\n  <Suspense fallback={<ListSelectFallback />}>\n    <ListSelectLoaded />\n  </Suspense>\n);\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/ListSelect/loaded.tsx",
    "content": "import { useEditorState } from \"@tiptap/react\";\nimport { useControlContext } from \"../../lib/use-control-context\";\nimport { SelectControl } from \"../../components/SelectControl\";\nimport { List } from \"lucide-react\";\nimport { ListElement, useListOptions } from \"./use-options\";\n\nexport function ListSelectLoaded() {\n  const { options } = useControlContext();\n  const listOptions = useListOptions(options);\n\n  const { editor } = useControlContext();\n  const currentValue = useEditorState({\n    editor,\n    selector: (ctx) => {\n      if (ctx.editor?.isActive(\"bulletList\")) return \"ul\";\n      if (ctx.editor?.isActive(\"orderedList\")) return \"ol\";\n\n      return \"p\";\n    },\n  });\n\n  const handleChange = (val: ListElement | \"p\") => {\n    const chain = editor?.chain();\n\n    if (val === \"p\") {\n      chain?.focus().setParagraph().run();\n    } else if (val === \"ol\") {\n      chain?.focus().toggleOrderedList().run();\n    } else if (val === \"ul\") {\n      chain?.focus().toggleBulletList().run();\n    }\n  };\n\n  return (\n    <SelectControl<ListElement | \"p\">\n      options={listOptions}\n      onChange={handleChange}\n      value={currentValue ?? \"p\"}\n      defaultValue=\"p\"\n      renderDefaultIcon={List}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/ListSelect/use-options.ts",
    "content": "import { useMemo } from \"react\";\nimport { List, ListOrdered } from \"lucide-react\";\nimport { RichtextField } from \"../../../../types\";\n\nconst optionNodes: Record<string, { label: string; icon?: React.FC }> = {\n  ul: { label: \"Bullet list\", icon: List },\n  ol: { label: \"Numbered list\", icon: ListOrdered },\n};\n\nexport type ListElement = \"ol\" | \"ul\";\n\nexport const useListOptions = (fieldOptions: RichtextField[\"options\"]) => {\n  let blockOptions: ListElement[] = [];\n\n  if (fieldOptions?.listItem !== false) {\n    blockOptions = [\"ul\", \"ol\"];\n  }\n\n  return useMemo(\n    () =>\n      blockOptions.map((item) => ({\n        value: item,\n        label: optionNodes[item].label,\n        icon: optionNodes[item].icon,\n      })),\n    [blockOptions]\n  );\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/OrderedList.tsx",
    "content": "import { ListOrdered as ListOrderedIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function OrderedList() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<ListOrderedIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleOrderedList().run();\n      }}\n      disabled={!editorState?.canOrderedList}\n      active={editorState?.isOrderedList}\n      title=\"Ordered list\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/Strikethrough.tsx",
    "content": "import { Strikethrough as StrikethroughIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function Strikethrough() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<StrikethroughIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleStrike().run();\n      }}\n      disabled={!editorState?.canStrike}\n      active={editorState?.isStrike}\n      title=\"Strikethrough\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/Underline.tsx",
    "content": "import { Underline as UnderlineIcon } from \"lucide-react\";\nimport { Control } from \"../components/Control\";\nimport { useControlContext } from \"../lib/use-control-context\";\n\nexport function Underline() {\n  const { editor, editorState } = useControlContext();\n\n  return (\n    <Control\n      icon={<UnderlineIcon />}\n      onClick={(e) => {\n        e.stopPropagation();\n        editor?.chain().focus().toggleUnderline().run();\n      }}\n      disabled={!editorState?.canUnderline}\n      active={editorState?.isUnderline}\n      title=\"Underline\"\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/controls/index.ts",
    "content": "export * from \"./AlignLeft\";\nexport * from \"./AlignCenter\";\nexport * from \"./AlignRight\";\nexport * from \"./AlignJustify\";\nexport * from \"./AlignSelect\";\nexport * from \"./Bold\";\nexport * from \"./Italic\";\nexport * from \"./Underline\";\nexport * from \"./Strikethrough\";\nexport * from \"./InlineCode\";\nexport * from \"./BulletList\";\nexport * from \"./OrderedList\";\nexport * from \"./CodeBlock\";\nexport * from \"./Blockquote\";\nexport * from \"./HorizontalRule\";\nexport * from \"./HeadingSelect\";\nexport * from \"./ListSelect\";\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/full.tsx",
    "content": "import { useEditorState } from \"@tiptap/react\";\nimport { useMemo } from \"react\";\nimport {\n  EditorState,\n  RichTextEditor,\n  RichTextSelector,\n} from \"../RichTextEditor/types\";\nimport { defaultEditorState } from \"../RichTextEditor/selector\";\nimport { RichtextField } from \"../../types\";\nimport { LoadedRichTextMenuInner } from \"./inner\";\n\nexport const LoadedRichTextMenuFull = ({\n  editor,\n  field,\n  readOnly,\n  inline,\n}: {\n  field: RichtextField;\n  editor: RichTextEditor | null;\n  readOnly: boolean;\n  inline?: boolean;\n}) => {\n  const { tiptap = {} } = field;\n  const { selector } = tiptap;\n\n  const resolvedSelector = useMemo(() => {\n    return (ctx: Parameters<RichTextSelector>[0]) => ({\n      ...defaultEditorState(ctx, readOnly),\n      ...(selector ? selector(ctx, readOnly) : {}),\n    });\n  }, [selector, readOnly]);\n\n  const editorState = useEditorState<EditorState>({\n    editor,\n    selector: resolvedSelector,\n  });\n\n  if (!editor || !editorState) {\n    return null;\n  }\n\n  return (\n    <LoadedRichTextMenuInner\n      editor={editor}\n      editorState={editorState}\n      field={field}\n      readOnly={readOnly}\n      inline={inline}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/index.tsx",
    "content": "import { lazy, Suspense } from \"react\";\nimport { EditorState, RichTextEditor } from \"../RichTextEditor/types\";\nimport { RichtextField } from \"../../types\";\nimport { LoadedRichTextMenuInner } from \"./inner\";\n\nconst LoadedRichTextMenuFull = lazy(() =>\n  import(\"./full\").then((m) => ({\n    default: m.LoadedRichTextMenuFull,\n  }))\n);\n\nexport type LoadedRichTextMenuProps = {\n  field: RichtextField;\n  editor: RichTextEditor | null;\n  editorState?: EditorState | null;\n  readOnly: boolean;\n  inline?: boolean;\n};\nexport const LoadedRichTextMenu = (props: LoadedRichTextMenuProps) => {\n  return (\n    <Suspense fallback={<LoadedRichTextMenuInner {...props} />}>\n      <LoadedRichTextMenuFull {...props} />\n    </Suspense>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/inner.tsx",
    "content": "import getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport styles from \"./styles.module.css\";\nimport { ReactNode, useMemo } from \"react\";\nimport { EditorState, RichTextEditor } from \"../RichTextEditor/types\";\nimport { RichtextField } from \"../../types\";\n\nimport {\n  AlignCenter,\n  AlignJustify,\n  AlignLeft,\n  AlignRight,\n  Blockquote,\n  Bold,\n  BulletList,\n  CodeBlock,\n  HeadingSelect,\n  HorizontalRule,\n  InlineCode,\n  Italic,\n  ListSelect,\n  OrderedList,\n  Strikethrough,\n  Underline,\n} from \"./controls\";\nimport { ControlContext, useControlContext } from \"./lib/use-control-context\";\nimport { Control } from \"./components/Control\";\nimport { AlignSelect } from \"./controls/AlignSelect\";\nimport { LoadedRichTextMenuProps } from \".\";\n\nconst getClassName = getClassNameFactory(\"RichTextMenu\", styles);\n\nconst DefaultMenu = ({ children }: { children: ReactNode }) => {\n  return <RichTextMenu>{children}</RichTextMenu>;\n};\n\nexport const RichTextMenu = ({ children }: { children: ReactNode }) => {\n  const { inline } = useControlContext();\n  return (\n    <div className={getClassName({ inline, form: !inline })} data-puck-rte-menu>\n      {children}\n    </div>\n  );\n};\n\nconst Group = ({ children }: { children: ReactNode }) => {\n  return <div className={getClassName(\"group\")}>{children}</div>;\n};\n\nRichTextMenu.Group = Group;\nRichTextMenu.Control = Control;\nRichTextMenu.AlignCenter = AlignCenter;\nRichTextMenu.AlignJustify = AlignJustify;\nRichTextMenu.AlignLeft = AlignLeft;\nRichTextMenu.AlignRight = AlignRight;\nRichTextMenu.AlignSelect = AlignSelect;\nRichTextMenu.Blockquote = Blockquote;\nRichTextMenu.Bold = Bold;\nRichTextMenu.BulletList = BulletList;\nRichTextMenu.CodeBlock = CodeBlock;\nRichTextMenu.HeadingSelect = HeadingSelect;\nRichTextMenu.HorizontalRule = HorizontalRule;\nRichTextMenu.InlineCode = InlineCode;\nRichTextMenu.Italic = Italic;\nRichTextMenu.ListSelect = ListSelect;\nRichTextMenu.OrderedList = OrderedList;\nRichTextMenu.Strikethrough = Strikethrough;\nRichTextMenu.Underline = Underline;\n\nexport const LoadedRichTextMenuInner = ({\n  editor = null,\n  editorState = null,\n  field,\n  readOnly,\n  inline,\n}: LoadedRichTextMenuProps) => {\n  const { renderMenu, renderInlineMenu } = field;\n\n  const InlineMenu = useMemo(\n    () => renderInlineMenu || DefaultMenu,\n    [renderInlineMenu]\n  );\n\n  const Menu = useMemo(() => renderMenu || DefaultMenu, [renderMenu]);\n\n  return (\n    <ControlContext.Provider\n      value={{ editor, editorState, inline, options: field.options, readOnly }}\n    >\n      {inline ? (\n        <InlineMenu\n          editor={editor}\n          editorState={editorState}\n          readOnly={readOnly}\n        >\n          <Group>\n            <Bold />\n            <Italic />\n            <Underline />\n          </Group>\n        </InlineMenu>\n      ) : (\n        <Menu editor={editor} editorState={editorState} readOnly={readOnly}>\n          <Group>\n            <HeadingSelect />\n            <ListSelect />\n          </Group>\n          <Group>\n            <Bold />\n            <Italic />\n            <Underline />\n          </Group>\n          <Group>\n            <AlignSelect />\n          </Group>\n        </Menu>\n      )}\n    </ControlContext.Provider>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/lib/use-control-context.ts",
    "content": "import { createContext, useContext } from \"react\";\nimport { EditorState } from \"../../RichTextEditor/types\";\nimport type { Editor } from \"@tiptap/react\";\nimport type { PuckRichTextOptions } from \"../../RichTextEditor/extension\";\n\ntype ControlContextType = {\n  editor: Editor | null;\n  editorState: EditorState | null;\n  inline: boolean;\n  readOnly: boolean;\n  options?: Partial<PuckRichTextOptions>;\n};\n\nexport const ControlContext = createContext<Partial<ControlContextType>>({});\n\nexport const useControlContext = () => {\n  return useContext(ControlContext) as ControlContextType;\n};\n"
  },
  {
    "path": "packages/core/components/RichTextMenu/styles.module.css",
    "content": ".RichTextMenu {\n  display: flex;\n  flex-direction: row;\n  flex-wrap: nowrap;\n}\n\n.RichTextMenu--form {\n  border-top-left-radius: 4px;\n  border-top-right-radius: 4px;\n  padding: 6px 6px;\n  background-color: var(--puck-color-grey-12);\n  position: relative;\n  scrollbar-width: none;\n  overflow-x: auto;\n}\n\n.RichTextMenu-group {\n  display: flex;\n  align-items: space-between;\n  flex-direction: row;\n  flex-wrap: nowrap;\n  padding-inline: 6px;\n  gap: 2px;\n  position: relative;\n}\n\n.RichTextMenu-group:first-of-type {\n  padding-left: 0;\n}\n\n.RichTextMenu-group:last-of-type {\n  padding-right: 0;\n}\n\n.RichTextMenu--inline .RichTextMenu-group {\n  color: var(--puck-color-grey-08);\n  gap: 0px;\n  flex-wrap: nowrap;\n}\n\n.RichTextMenu-group + .RichTextMenu-group {\n  border-left: 1px solid var(--puck-color-grey-10);\n}\n\n.RichTextMenu--inline .RichTextMenu-group + .RichTextMenu-group {\n  border-left: 0.5px solid var(--puck-color-grey-05); /* Match actionbar separator */\n}\n"
  },
  {
    "path": "packages/core/components/Select/index.tsx",
    "content": "import styles from \"./styles.module.css\";\nimport { ReactNode, useState } from \"react\";\nimport {\n  Popover,\n  PopoverContent,\n  PopoverPortal,\n  PopoverTrigger,\n} from \"@radix-ui/react-popover\";\nimport { ChevronDown } from \"lucide-react\";\nimport { getClassNameFactory } from \"../../lib\";\n\nconst getClassName = getClassNameFactory(\"Select\", styles);\nconst getItemClassName = getClassNameFactory(\"SelectItem\", styles);\n\nconst Item = ({\n  children,\n  isSelected,\n  onClick,\n}: {\n  children: ReactNode;\n  isSelected: boolean;\n  onClick: () => void;\n}) => {\n  return (\n    <button className={getItemClassName({ isSelected })} onClick={onClick}>\n      {children}\n    </button>\n  );\n};\n\nexport const Select = ({\n  children,\n  options,\n  onChange,\n  value,\n  defaultValue,\n  mode,\n  disabled = false,\n}: {\n  children: ReactNode;\n  options: { icon?: React.FC; label: string; value: string }[];\n  onChange: (val: any) => void;\n  value: any;\n  defaultValue?: any;\n  mode: \"actionBar\" | \"standalone\";\n  disabled?: boolean;\n}) => {\n  const [open, setOpen] = useState(false);\n\n  const hasOptions = options.length > 0;\n  const isDisabled = disabled || !hasOptions;\n\n  return (\n    <div\n      className={getClassName({\n        hasValue: value !== defaultValue,\n        hasOptions,\n        actionBar: mode === \"actionBar\",\n        standalone: mode === \"standalone\",\n        disabled: isDisabled,\n      })}\n    >\n      <Popover open={open} onOpenChange={setOpen}>\n        {hasOptions ? (\n          <PopoverTrigger asChild>\n            <button className={getClassName(\"button\")}>\n              <span className={getClassName(\"buttonIcon\")}>{children}</span>\n              <ChevronDown size={12} />\n            </button>\n          </PopoverTrigger>\n        ) : (\n          <div>\n            <div className={getClassName(\"button\")}>\n              <span className={getClassName(\"buttonIcon\")}>{children}</span>\n              <ChevronDown size={12} />\n            </div>\n          </div>\n        )}\n\n        {options.length > 0 && (\n          <PopoverPortal>\n            <PopoverContent align=\"start\">\n              <ul className={getClassName(\"items\")} data-puck-rte-menu>\n                {options.map((option) => {\n                  const Icon: any = option.icon;\n\n                  return (\n                    <li key={option.value}>\n                      <Item\n                        isSelected={value === option.value}\n                        onClick={() => {\n                          onChange(option.value);\n                          setOpen(false);\n                        }}\n                      >\n                        {Icon && (\n                          <div className={getItemClassName(\"icon\")}>\n                            <Icon size={16} />\n                          </div>\n                        )}\n                        {option.label}\n                      </Item>\n                    </li>\n                  );\n                })}\n              </ul>\n            </PopoverContent>\n          </PopoverPortal>\n        )}\n      </Popover>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/Select/styles.module.css",
    "content": ".Select {\n  position: relative;\n  z-index: 1;\n}\n\n.Select-button {\n  align-items: center;\n  background: transparent;\n  border: none;\n  border-radius: 4px;\n  display: flex;\n  justify-content: center;\n  gap: 0px;\n  height: 100%;\n  padding: 4px;\n  padding-right: 2px;\n}\n\n.Select--hasOptions .Select-button {\n  color: currentColor;\n}\n\n.Select--hasOptions:not(.Select--disabled) .Select-button {\n  cursor: pointer;\n}\n\n.Select-buttonIcon {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n}\n\n.Select--standalone .Select-buttonIcon :global(.lucide) {\n  height: 18px;\n  width: 18px;\n}\n\n.Select--actionBar .Select-buttonIcon :global(.lucide) {\n  height: 16px;\n  width: 16px;\n}\n\n.Select--hasOptions:not(.Select--disabled) .Select-button:hover,\n.Select--hasValue .Select-button {\n  background: var(--puck-color-grey-10);\n  color: var(--puck-color-azure-04);\n}\n\n.Select--disabled .Select-button {\n  color: var(--puck-color-grey-07);\n}\n\n.Select--actionBar {\n  &.Select--hasOptions .Select-button:hover,\n  &.Select--hasValue .Select-button {\n    background: none;\n    color: var(--puck-color-azure-07);\n  }\n}\n\n.Select-items {\n  background: white;\n  border: 1px solid var(--puck-color-grey-09);\n  border-radius: 8px;\n  margin: 10px 8px;\n  margin-left: 0;\n  padding: 4px;\n  z-index: 2;\n  list-style: none;\n}\n\n.SelectItem {\n  background: transparent;\n  border-radius: 4px;\n  border: none;\n  color: var(--puck-color-grey-04);\n  cursor: pointer;\n  display: flex;\n  gap: 8px;\n  align-items: center;\n  font-size: var(--puck-font-size-xxs);\n  margin: 0;\n  padding: 8px 12px;\n  width: 100%;\n}\n\n.SelectItem--isSelected {\n  background: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-04);\n  font-weight: 500;\n}\n\n.SelectItem--isSelected .SelectItem-icon {\n  color: var(--puck-color-azure-04);\n}\n\n.SelectItem:hover {\n  background: var(--puck-color-azure-11);\n  color: var(--puck-color-azure-04);\n}\n"
  },
  {
    "path": "packages/core/components/ServerRender/index.tsx",
    "content": "import { CSSProperties } from \"react\";\nimport {\n  rootAreaId,\n  rootDroppableId,\n  rootZone,\n} from \"../../lib/root-droppable-id\";\nimport { setupZone } from \"../../lib/data/setup-zone\";\nimport { Config, Data, Metadata, UserGenerics } from \"../../types\";\nimport { useSlots } from \"../../lib/use-slots\";\nimport { SlotRenderPure } from \"../SlotRender/server\";\nimport { useRichtextProps } from \"../RichTextEditor/lib/use-richtext-props\";\n\ntype DropZoneRenderProps = {\n  zone: string;\n  data: Data;\n  config: Config;\n  areaId?: string;\n  style?: CSSProperties;\n  metadata?: Metadata;\n};\n\nexport function DropZoneRender({\n  zone,\n  data,\n  areaId = \"root\",\n  config,\n  metadata = {},\n}: DropZoneRenderProps) {\n  let zoneCompound = rootDroppableId;\n  let content = data?.content || [];\n\n  if (!data || !config) {\n    return null;\n  }\n\n  if (areaId !== rootAreaId && zone !== rootZone) {\n    zoneCompound = `${areaId}:${zone}`;\n    content = setupZone(data, zoneCompound).zones[zoneCompound];\n  }\n\n  return (\n    <>\n      {content.map((item) => {\n        const Component = config.components[item.type];\n\n        const props = {\n          ...item.props,\n          puck: {\n            renderDropZone: ({ zone }: { zone: string }) => (\n              <DropZoneRender\n                zone={zone}\n                data={data}\n                areaId={item.props.id}\n                config={config}\n                metadata={metadata}\n              />\n            ),\n            metadata,\n            dragRef: null,\n            isEditing: false,\n          },\n        };\n\n        const renderItem = { ...item, props };\n\n        // eslint-disable-next-line react-hooks/rules-of-hooks\n        const propsWithSlots = useSlots(config, renderItem, (props) => (\n          <SlotRenderPure {...props} config={config} metadata={metadata} />\n        ));\n\n        if (Component) {\n          return (\n            <Component.render key={renderItem.props.id} {...propsWithSlots} />\n          );\n        }\n\n        return null;\n      })}\n    </>\n  );\n}\n\nexport function Render<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>({\n  config,\n  data,\n  metadata = {},\n}: {\n  config: UserConfig;\n  data: G[\"UserData\"];\n  metadata?: Metadata;\n}) {\n  // DEPRECATED\n  const rootProps = \"props\" in data.root ? data.root.props : data.root;\n\n  const title = rootProps.title || \"\";\n\n  const props = {\n    ...rootProps,\n    puck: {\n      renderDropZone: ({ zone }: { zone: string }) => (\n        <DropZoneRender\n          zone={zone}\n          data={data}\n          config={config}\n          metadata={metadata}\n        />\n      ),\n      isEditing: false,\n      dragRef: null,\n      metadata,\n    },\n    title,\n    editMode: false,\n    id: \"puck-root\",\n  };\n\n  const propsWithSlots = useSlots(config, { type: \"root\", props }, (props) => (\n    <SlotRenderPure {...props} config={config} metadata={metadata} />\n  ));\n\n  const richtextProps = useRichtextProps(config.root?.fields, props);\n\n  if (config.root?.render) {\n    return (\n      <config.root.render {...propsWithSlots} {...richtextProps}>\n        <DropZoneRender\n          config={config}\n          data={data}\n          zone={rootZone}\n          metadata={metadata}\n        />\n      </config.root.render>\n    );\n  }\n\n  return (\n    <DropZoneRender\n      config={config}\n      data={data}\n      zone={rootZone}\n      metadata={metadata}\n    />\n  );\n}\n"
  },
  {
    "path": "packages/core/components/SidebarSection/index.tsx",
    "content": "import { ReactNode } from \"react\";\nimport styles from \"./styles.module.css\";\nimport getClassNameFactory from \"../../lib/get-class-name-factory\";\nimport { Heading } from \"../Heading\";\nimport { Loader } from \"../Loader\";\nimport { Breadcrumbs } from \"../Breadcrumbs\";\n\nconst getClassName = getClassNameFactory(\"SidebarSection\", styles);\n\nexport const SidebarSection = ({\n  children,\n  title,\n  background,\n  showBreadcrumbs,\n  noBorderTop,\n  isLoading,\n}: {\n  children: ReactNode;\n  title: ReactNode;\n  background?: string;\n  showBreadcrumbs?: boolean;\n  noBorderTop?: boolean;\n  isLoading?: boolean | null;\n}) => {\n  return (\n    <div className={getClassName({ noBorderTop })} style={{ background }}>\n      <div className={getClassName(\"title\")}>\n        <div className={getClassName(\"breadcrumbs\")}>\n          {showBreadcrumbs && <Breadcrumbs />}\n          <div className={getClassName(\"heading\")}>\n            <Heading rank=\"2\" size=\"xs\">\n              {title}\n            </Heading>\n          </div>\n        </div>\n      </div>\n      <div className={getClassName(\"content\")}>{children}</div>\n      {isLoading && (\n        <div className={getClassName(\"loadingOverlay\")}>\n          <Loader size={32} />\n        </div>\n      )}\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/SidebarSection/styles.module.css",
    "content": ".SidebarSection {\n  display: flex;\n  position: relative;\n  flex-direction: column;\n  color: var(--puck-color-black);\n}\n\n.SidebarSection:last-of-type {\n  flex-grow: 1;\n}\n\n.SidebarSection-title {\n  background: var(--puck-color-white);\n  padding: 16px;\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  border-top: 1px solid var(--puck-color-grey-09);\n  overflow-x: auto;\n}\n\n.SidebarSection--noBorderTop > .SidebarSection-title {\n  border-top: 0px;\n}\n\n.SidebarSection-content:last-child {\n  padding-bottom: 4px;\n}\n\n.SidebarSection:last-of-type .SidebarSection-content {\n  border-bottom: none;\n  flex-grow: 1;\n}\n\n.SidebarSection-breadcrumbLabel {\n  background: none;\n  border: 0;\n  border-radius: 2px;\n  color: var(--puck-color-azure-04);\n  cursor: pointer;\n  font: inherit;\n  flex-shrink: 0;\n  padding: 0;\n  transition: color 50ms ease-in;\n}\n\n.SidebarSection-breadcrumbLabel:focus-visible {\n  outline: 2px solid var(--puck-color-azure-05);\n  outline-offset: 2px;\n}\n\n@media (hover: hover) and (pointer: fine) {\n  .SidebarSection-breadcrumbLabel:hover {\n    color: var(--puck-color-azure-03);\n    transition: none;\n  }\n}\n\n.SidebarSection-breadcrumbLabel:active {\n  color: var(--puck-color-azure-02);\n  transition: none;\n}\n\n.SidebarSection-breadcrumbs {\n  align-items: center;\n  display: flex;\n  gap: 4px;\n}\n\n.SidebarSection-breadcrumb {\n  align-items: center;\n  display: flex;\n  gap: 4px;\n}\n\n.SidebarSection-heading {\n  padding-inline-end: 16px;\n}\n\n.SidebarSection-loadingOverlay {\n  background: var(--puck-color-white);\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  height: 100%;\n  width: 100%;\n  top: 0;\n  position: absolute;\n  z-index: 1;\n  pointer-events: all;\n  box-sizing: border-box;\n  opacity: 0.8;\n}\n"
  },
  {
    "path": "packages/core/components/SlotRender/index.tsx",
    "content": "\"use client\";\n\nimport { useShallow } from \"zustand/react/shallow\";\nimport { useAppStore } from \"../../store\";\nimport { SlotRenderPure } from \"./server\";\nexport * from \"./server\";\n\nexport const ContextSlotRender = ({\n  componentId,\n  zone,\n}: {\n  componentId: string;\n  zone: string;\n}) => {\n  const config = useAppStore((s) => s.config);\n  const metadata = useAppStore((s) => s.metadata);\n  const slotContent = useAppStore(\n    useShallow((s) => {\n      const indexes = s.state.indexes;\n\n      const contentIds =\n        indexes.zones[`${componentId}:${zone}`]?.contentIds ?? [];\n\n      return contentIds.map((contentId) => indexes.nodes[contentId].flatData);\n    })\n  );\n\n  return (\n    <SlotRenderPure\n      content={slotContent}\n      zone={zone}\n      config={config}\n      metadata={metadata}\n    />\n  );\n};\n"
  },
  {
    "path": "packages/core/components/SlotRender/server.tsx",
    "content": "import { forwardRef } from \"react\";\nimport { DropZoneProps } from \"../DropZone/types\";\nimport {\n  ComponentData,\n  Config,\n  Content,\n  Metadata,\n  WithPuckProps,\n} from \"../../types\";\nimport { useSlots } from \"../../lib/use-slots\";\nimport { useRichtextProps } from \"../RichTextEditor/lib/use-richtext-props\";\n\ntype SlotRenderProps = DropZoneProps & {\n  content: Content;\n  config: Config;\n  metadata: Metadata;\n};\n\nexport const SlotRenderPure = (props: SlotRenderProps) => (\n  <SlotRender {...props} />\n);\n\nconst Item = ({\n  config,\n  item,\n  metadata,\n}: {\n  config: Config;\n  item: ComponentData;\n  metadata: Metadata;\n}) => {\n  const Component = config.components[item.type];\n\n  // eslint-disable-next-line react-hooks/rules-of-hooks\n  const props = useSlots(config, item, (slotProps) => (\n    <SlotRenderPure {...slotProps} config={config} metadata={metadata} />\n  )) as WithPuckProps<ComponentData[\"props\"]>;\n\n  const richtextProps = useRichtextProps(Component.fields, props);\n\n  return (\n    <Component.render\n      {...props}\n      {...richtextProps}\n      puck={{\n        ...props.puck,\n        metadata: metadata || {},\n      }}\n    />\n  );\n};\n\n/**\n * Render a slot.\n *\n * Replacement for DropZoneRender\n */\nexport const SlotRender = forwardRef<HTMLDivElement, SlotRenderProps>(\n  function SlotRenderInternal(\n    { className, style, content, config, metadata, as },\n    ref\n  ) {\n    const El = as ?? \"div\";\n\n    return (\n      <El className={className} style={style} ref={ref}>\n        {content.map((item) => {\n          if (!config.components[item.type]) {\n            return null;\n          }\n\n          return (\n            <Item\n              key={item.props.id}\n              config={config}\n              item={item}\n              metadata={metadata}\n            />\n          );\n        })}\n      </El>\n    );\n  }\n);\n"
  },
  {
    "path": "packages/core/components/Sortable/index.tsx",
    "content": "import { DragDropProvider } from \"@dnd-kit/react\";\nimport { PropsWithChildren, ReactNode } from \"react\";\nimport { useSensors } from \"../../lib/dnd/use-sensors\";\nimport { createDynamicCollisionDetector } from \"../../lib/dnd/collision/dynamic\";\nimport \"./styles.css\";\nimport { useSortable } from \"@dnd-kit/react/sortable\";\n\nexport const SortableProvider = ({\n  children,\n  onDragStart,\n  onDragEnd,\n  onMove,\n}: PropsWithChildren<{\n  onDragStart: (id: string) => void;\n  onDragEnd: () => void;\n  onMove: (moveData: { source: number; target: number }) => void;\n}>) => {\n  const sensors = useSensors({\n    mouse: { distance: { value: 5 } },\n  });\n\n  return (\n    <DragDropProvider\n      sensors={sensors}\n      onDragStart={(event) =>\n        onDragStart(event.operation.source?.id.toString() ?? \"\")\n      }\n      onDragOver={(event, manager) => {\n        event.preventDefault();\n\n        const { operation } = event;\n        const { source, target } = operation;\n\n        if (!source || !target) return;\n\n        let sourceIndex = source.data.index;\n        let targetIndex = target.data.index;\n\n        const collisionData = manager.collisionObserver.collisions[0]?.data;\n\n        if (sourceIndex !== targetIndex && source.id !== target.id) {\n          const collisionPosition =\n            collisionData?.direction === \"up\" ? \"before\" : \"after\";\n\n          if (targetIndex >= sourceIndex) {\n            targetIndex = targetIndex - 1;\n          }\n\n          if (collisionPosition === \"after\") {\n            targetIndex = targetIndex + 1;\n          }\n\n          onMove({\n            source: sourceIndex,\n            target: targetIndex,\n          });\n        }\n      }}\n      onDragEnd={() => {\n        setTimeout(() => {\n          // Delay until animation finished\n          onDragEnd();\n        }, 250);\n      }}\n    >\n      {children}\n    </DragDropProvider>\n  );\n};\n\nexport const Sortable = ({\n  id,\n  index,\n  disabled,\n  children,\n  type = \"item\",\n}: {\n  id: string;\n  index: number;\n  disabled?: boolean;\n  children: (props: {\n    isDragging: boolean;\n    isDropping: boolean;\n    ref: (element: Element | null) => void;\n    handleRef: (element: Element | null) => void;\n  }) => ReactNode;\n  type?: string;\n}) => {\n  const {\n    ref: sortableRef,\n    isDragging,\n    isDropping,\n    handleRef,\n  } = useSortable({\n    id,\n    type,\n    index,\n    disabled,\n    data: { index },\n    collisionDetector: createDynamicCollisionDetector(\"y\"),\n  });\n\n  return children({ isDragging, isDropping, ref: sortableRef, handleRef });\n};\n"
  },
  {
    "path": "packages/core/components/Sortable/styles.css",
    "content": "[data-dnd-placeholder] * {\n  opacity: 0 !important;\n}\n\n[data-dnd-placeholder] {\n  background: var(--puck-color-azure-09) !important;\n  border: none !important;\n  color: #00000000 !important;\n  opacity: 0.3 !important;\n  outline: none !important;\n  transition: none !important;\n}\n"
  },
  {
    "path": "packages/core/components/ViewportControls/default-viewports.ts",
    "content": "import { Viewports } from \"../../types\";\n\nexport const defaultViewports: Required<Viewports> = [\n  { width: 360, height: \"auto\", icon: \"Smartphone\", label: \"Small\" },\n  { width: 768, height: \"auto\", icon: \"Tablet\", label: \"Medium\" },\n  { width: 1280, height: \"auto\", icon: \"Monitor\", label: \"Large\" },\n  { width: \"100%\", height: \"auto\", icon: \"FullWidth\", label: \"Full-width\" },\n];\n"
  },
  {
    "path": "packages/core/components/ViewportControls/index.tsx",
    "content": "import {\n  Expand,\n  Monitor,\n  Smartphone,\n  Tablet,\n  X,\n  ZoomIn,\n  ZoomOut,\n} from \"lucide-react\";\nimport { IconButton } from \"../IconButton\";\nimport { useAppStore } from \"../../store\";\nimport { ReactNode, SyntheticEvent, useEffect, useMemo, useState } from \"react\";\nimport { getClassNameFactory } from \"../../lib\";\n\nimport styles from \"./styles.module.css\";\nimport { Viewport } from \"../../types\";\n\nconst icons = {\n  Smartphone: <Smartphone size={16} />,\n  Tablet: <Tablet size={16} />,\n  Monitor: <Monitor size={16} />,\n  FullWidth: <Expand size={16} />,\n};\n\nconst getClassName = getClassNameFactory(\"ViewportControls\", styles);\nconst getClassNameButton = getClassNameFactory(\"ViewportButton\", styles);\n\nconst ActionButton = ({\n  children,\n  title,\n  onClick,\n  isActive,\n  disabled,\n}: {\n  children: ReactNode;\n  title: string;\n  onClick: (e: SyntheticEvent) => void;\n  isActive?: boolean;\n  disabled?: boolean;\n}) => {\n  return (\n    <span className={getClassNameButton({ isActive })} suppressHydrationWarning>\n      <IconButton\n        type=\"button\"\n        title={title}\n        disabled={disabled || isActive}\n        onClick={onClick}\n        suppressHydrationWarning\n      >\n        <span className={getClassNameButton(\"inner\")}>{children}</span>\n      </IconButton>\n    </span>\n  );\n};\n\n// Based on Chrome dev tools\nconst defaultZoomOptions = [\n  { label: \"25%\", value: 0.25 },\n  { label: \"50%\", value: 0.5 },\n  { label: \"75%\", value: 0.75 },\n  { label: \"100%\", value: 1 },\n  { label: \"125%\", value: 1.25 },\n  { label: \"150%\", value: 1.5 },\n  { label: \"200%\", value: 2 },\n];\n\nexport const ViewportControls = ({\n  autoZoom,\n  zoom,\n  onViewportChange,\n  onZoom,\n  fullScreen,\n}: {\n  autoZoom: number;\n  zoom: number;\n  onViewportChange: (viewport: Viewport) => void;\n  onZoom: (zoom: number) => void;\n  fullScreen?: boolean;\n}) => {\n  const viewports = useAppStore((s) => s.viewports);\n  const uiViewports = useAppStore((s) => s.state.ui.viewports);\n\n  const defaultsContainAutoZoom = defaultZoomOptions.find(\n    (option) => option.value === autoZoom\n  );\n\n  const zoomOptions = useMemo(\n    () =>\n      [\n        ...defaultZoomOptions,\n        ...(defaultsContainAutoZoom\n          ? []\n          : [\n              {\n                value: autoZoom,\n                label: `${(autoZoom * 100).toFixed(0)}% (Auto)`,\n              },\n            ]),\n      ]\n        .filter((a) => a.value <= autoZoom)\n        .sort((a, b) => (a.value > b.value ? 1 : -1)),\n    [autoZoom]\n  );\n\n  const [activeViewport, setActiveViewport] = useState(\n    uiViewports.current.width\n  );\n\n  useEffect(() => {\n    setActiveViewport(uiViewports.current.width);\n  }, [uiViewports.current]);\n\n  const [isExpanded, setIsExpanded] = useState(false);\n\n  return (\n    <div\n      className={getClassName({ isExpanded, fullScreen })}\n      suppressHydrationWarning // Suppress hydration warning as frame is not visible until after load\n    >\n      <div className={getClassName(\"actions\")}>\n        <div className={getClassName(\"actionsInner\")}>\n          {viewports.map((viewport, i) => (\n            <ActionButton\n              key={i}\n              title={\n                viewport.label\n                  ? `Switch to ${viewport.label} viewport`\n                  : \"Switch viewport\"\n              }\n              onClick={() => {\n                setActiveViewport(viewport.width);\n                onViewportChange(viewport);\n              }}\n              isActive={activeViewport === viewport.width}\n            >\n              {typeof viewport.icon === \"string\"\n                ? icons[viewport.icon as keyof typeof icons] || viewport.icon\n                : viewport.icon || icons.Smartphone}\n            </ActionButton>\n          ))}\n          <div className={getClassName(\"divider\")} />\n          <ActionButton\n            title=\"Zoom viewport out\"\n            disabled={zoom <= zoomOptions[0]?.value}\n            onClick={(e) => {\n              e.stopPropagation();\n              onZoom(\n                zoomOptions[\n                  Math.max(\n                    zoomOptions.findIndex((option) => option.value === zoom) -\n                      1,\n                    0\n                  )\n                ].value\n              );\n            }}\n          >\n            <ZoomOut size={16} />\n          </ActionButton>\n          <ActionButton\n            title=\"Zoom viewport in\"\n            disabled={zoom >= zoomOptions[zoomOptions.length - 1]?.value}\n            onClick={(e) => {\n              e.stopPropagation();\n\n              onZoom(\n                zoomOptions[\n                  Math.min(\n                    zoomOptions.findIndex((option) => option.value === zoom) +\n                      1,\n                    zoomOptions.length - 1\n                  )\n                ].value\n              );\n            }}\n          >\n            <ZoomIn size={16} />\n          </ActionButton>\n\n          <div className={getClassName(\"zoom\")}>\n            <div className={getClassName(\"divider\")} />\n            <select\n              className={getClassName(\"zoomSelect\")}\n              value={zoom.toString()}\n              onClick={(e) => {\n                e.stopPropagation();\n              }}\n              onChange={(e) => {\n                onZoom(parseFloat(e.currentTarget.value));\n              }}\n            >\n              {zoomOptions.map((option) => (\n                <option\n                  key={option.label}\n                  value={option.value}\n                  label={option.label}\n                />\n              ))}\n            </select>\n          </div>\n        </div>\n      </div>\n\n      <button\n        className={getClassName(\"toggleButton\")}\n        title=\"Toggle viewport menu\"\n        onClick={() => setIsExpanded((s) => !s)}\n      >\n        {isExpanded ? <X size={16} /> : <Monitor size={16} />}\n      </button>\n    </div>\n  );\n};\n"
  },
  {
    "path": "packages/core/components/ViewportControls/styles.module.css",
    "content": ".ViewportControls {\n  position: relative;\n}\n\n.ViewportControls--fullScreen {\n  border-radius: 32px;\n  display: flex;\n  position: absolute;\n  bottom: 12px;\n  right: 12px;\n  overflow: hidden;\n}\n\n.ViewportControls-toggleButton {\n  display: none;\n}\n\n.ViewportControls--fullScreen .ViewportControls-toggleButton {\n  align-items: center;\n  background-color: var(--puck-color-grey-02);\n  border: 1px solid var(--puck-color-grey-04);\n  border-radius: 30px;\n  cursor: pointer;\n  color: var(--puck-color-grey-11);\n  display: flex;\n  justify-content: center;\n  width: 42px;\n  height: 42px;\n  z-index: 1;\n}\n\n.ViewportControls--fullScreen .ViewportControls-toggleButton:hover {\n  background-color: var(--puck-color-grey-02);\n  border: 1px solid var(--puck-color-azure-04);\n  color: var(--puck-color-azure-07);\n}\n\n.ViewportControls--isExpanded .ViewportControls-toggleButton {\n  background-color: var(--puck-color-grey-03);\n}\n\n.ViewportControls-actions {\n  display: flex;\n}\n\n.ViewportControls-actionsInner {\n  display: flex;\n  box-sizing: border-box;\n  justify-content: center;\n  margin-left: auto;\n  margin-right: auto;\n  z-index: 0;\n  overflow: hidden;\n}\n\n.ViewportControls--fullScreen .ViewportControls-actionsInner {\n  background: var(--puck-color-grey-11);\n  border: 1px solid var(--puck-color-grey-09);\n  border-radius: 30px;\n  margin-left: none;\n  margin-right: none;\n  padding-right: 42px;\n}\n\n.ViewportControls--fullScreen .ViewportControls-actionsInner {\n  transform: translateX(100%);\n  transition: transform 150ms ease-in-out;\n}\n\n.ViewportControls--fullScreen.ViewportControls--isExpanded\n  .ViewportControls-actionsInner {\n  transform: translateX(42px);\n}\n\n.ViewportControls-divider {\n  border-inline-end: 1px solid var(--puck-color-grey-09);\n  margin-bottom: 8px;\n  margin-top: 8px;\n}\n\n.ViewportControls-zoomSelect {\n  appearance: none; /* Safari */\n  background: url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='100' height='100' fill='%23c3c3c3'><polygon points='0,0 100,0 50,50'/></svg>\")\n    no-repeat;\n  background-size: 10px;\n  background-position: calc(100% - 12px) calc(50% + 3px);\n  background-repeat: no-repeat;\n  border: 0;\n  font-size: var(--puck-font-size-xxxs);\n  padding: 0;\n  padding-left: 8px;\n  width: 96px;\n}\n\n.ViewportControls--fullScreen .ViewportControls-zoom {\n  display: none;\n}\n\n@media (min-width: 638px) {\n  .ViewportControls-zoom,\n  .ViewportControls--fullScreen .ViewportControls-zoom {\n    display: flex;\n    justify-content: center;\n  }\n}\n\n.ViewportControls-zoomSelect:dir(rtl) {\n  background-position: 12px calc(50% + 3px);\n}\n\n.ViewportButton-inner {\n  align-items: center;\n  display: flex;\n  justify-content: center;\n  height: 32px;\n  width: 32px;\n}\n\n.ViewportButton--isActive .ViewportButton-inner {\n  color: var(--puck-color-azure-04);\n}\n"
  },
  {
    "path": "packages/core/globals.d.ts",
    "content": "declare module \"*.module.css\";\n"
  },
  {
    "path": "packages/core/index.ts",
    "content": "export * from \"./bundle/index\";\n"
  },
  {
    "path": "packages/core/jest.config.ts",
    "content": "import type { Config } from \"jest\";\n\nconst config: Config = {\n  preset: \"ts-jest/presets/js-with-ts-esm\", // TS + ESM\n  testEnvironment: \"jsdom\",\n\n  // Treat these files as ESM so `import`/`export` keep working\n  extensionsToTreatAsEsm: [\".ts\", \".tsx\"],\n\n  transform: {\n    // Let ts-jest compile TS/JS for Jest\n    \"^.+\\\\.[tj]sx?$\": [\"ts-jest\", { useESM: true }],\n  },\n\n  // Re-enable transform *inside* selected node_modules\n  transformIgnorePatterns: [\n    \"/node_modules/(?!(?:@preact/signals-core|@preact/signals-react|@dnd-kit)/)\",\n  ],\n\n  moduleNameMapper: {\n    // stub out style & asset imports\n    \"\\\\.(css|less|sass|scss)$\": \"identity-obj-proxy\",\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/core/lib/__tests__/insert-component.spec.tsx",
    "content": "import { cleanup } from \"@testing-library/react\";\nimport { ComponentData, Config, RootDataWithProps } from \"../../types\";\nimport { PuckAction } from \"../../reducer\";\nimport { insertComponent } from \"../insert-component\";\nimport { rootDroppableId } from \"../root-droppable-id\";\n\nimport { createAppStore } from \"../../store\";\n\nconst appStore = createAppStore();\n\nconst config: Config = {\n  components: {\n    MyComponent: {\n      fields: {\n        prop: { type: \"text\" },\n        object: { type: \"object\", objectFields: { slot: { type: \"slot\" } } },\n      },\n      defaultProps: {\n        prop: \"Unresolved\",\n        object: {\n          slot: [\n            {\n              type: \"MyComponent\",\n              props: {\n                prop: \"Unresolved\",\n                object: { slot: [] },\n              },\n            },\n          ],\n        },\n      },\n      resolveData: ({ props }) => {\n        return {\n          props: {\n            ...props,\n            prop: \"Hello, world\",\n          },\n          readOnly: {\n            prop: true,\n          },\n        };\n      },\n      render: () => <div />,\n    },\n  },\n};\n\ntype ComponentOrRootData = ComponentData | RootDataWithProps;\n\ndescribe(\"use-insert-component\", () => {\n  describe(\"insert-component\", () => {\n    let dispatchedEvents: PuckAction[] = [];\n    let resolvedDataEvents: ComponentOrRootData[] = [];\n    let resolvedTrigger: string = \"\";\n\n    beforeEach(() => {\n      appStore.setState(\n        {\n          ...appStore.getInitialState(),\n          config,\n          dispatch: (action) => {\n            dispatchedEvents.push(action);\n          },\n          resolveComponentData: async (data, trigger) => {\n            resolvedDataEvents.push(data);\n\n            resolvedTrigger = trigger;\n\n            return data as any;\n          },\n        },\n        true\n      );\n    });\n\n    afterEach(() => {\n      cleanup();\n      dispatchedEvents = [];\n      resolvedDataEvents = [];\n    });\n\n    it(\"should dispatch the insert action\", async () => {\n      insertComponent(\"MyComponent\", rootDroppableId, 0, appStore);\n\n      expect(dispatchedEvents[0]).toEqual<PuckAction>({\n        type: \"insert\",\n        componentType: \"MyComponent\",\n        destinationZone: rootDroppableId,\n        destinationIndex: 0,\n        id: expect.stringContaining(\"MyComponent-\"),\n        recordHistory: true,\n      });\n    });\n\n    it(\"should dispatch the setUi action, and select the item\", async () => {\n      insertComponent(\"MyComponent\", rootDroppableId, 0, appStore);\n\n      expect(dispatchedEvents[1]).toEqual<PuckAction>({\n        type: \"setUi\",\n        ui: {\n          itemSelector: {\n            zone: rootDroppableId,\n            index: 0,\n          },\n        },\n      });\n    });\n\n    it(\"should run any resolveData methods on the inserted item\", async () => {\n      insertComponent(\"MyComponent\", rootDroppableId, 0, appStore);\n\n      expect(resolvedDataEvents[0]).toEqual({\n        type: \"MyComponent\",\n        props: {\n          id: expect.stringContaining(\"MyComponent-\"),\n          prop: \"Unresolved\",\n          object: {\n            slot: [\n              {\n                type: \"MyComponent\",\n                props: {\n                  id: expect.stringContaining(\"MyComponent-\"),\n                  prop: \"Unresolved\",\n                  object: { slot: [] },\n                },\n              },\n            ],\n          },\n        },\n      });\n\n      expect(resolvedTrigger).toEqual(\"insert\");\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/load-overrides.spec.tsx",
    "content": "import { loadOverrides } from \"../load-overrides\";\n\ndescribe(\"load-overrides\", () => {\n  it(\"should curry the overrides for any given override\", () => {\n    const loaded = loadOverrides({\n      overrides: {\n        header: ({ children }) => `${children} | 1` as any,\n      },\n      plugins: [\n        {\n          overrides: {\n            header: ({ children }) => `${children} | 2` as any,\n          },\n        },\n        {\n          overrides: {\n            header: ({ children }) => `${children} | 3` as any,\n          },\n        },\n      ],\n    });\n\n    expect(loaded.header!({ actions: \"\", children: \"0\" })).toBe(\n      \"0 | 1 | 2 | 3\"\n    );\n  });\n\n  it(\"should curry the overrides for fieldTypes\", () => {\n    const loaded = loadOverrides({\n      overrides: {\n        fieldTypes: { text: ({ children }) => `${children} | 1` as any },\n      },\n      plugins: [\n        {\n          overrides: {\n            fieldTypes: { text: ({ children }) => `${children} | 2` as any },\n          },\n        },\n        {\n          overrides: {\n            fieldTypes: { text: ({ children }) => `${children} | 3` as any },\n          },\n        },\n      ],\n    });\n\n    expect(loaded.fieldTypes!.text!({ children: \"0\" } as any)).toBe(\n      \"0 | 1 | 2 | 3\"\n    );\n  });\n\n  it(\"should avoid mutating the provided overrides\", () => {\n    const overrides = {};\n    const loaded = loadOverrides({\n      overrides,\n      plugins: [\n        {\n          overrides: {\n            fieldTypes: { text: ({ children }) => `${children} | 1` as any },\n          },\n        },\n      ],\n    });\n\n    expect(overrides).toEqual({});\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/migrate.spec.tsx",
    "content": "import { Config, Data, Slot } from \"../../types\";\nimport { migrate } from \"../migrate\";\n\njest.spyOn(console, \"warn\").mockImplementation(() => {});\n\ndescribe(\"migrate method\", () => {\n  it(\"should migrate root to root.props\", () => {\n    expect(\n      migrate(\n        { content: [], root: { title: \"Hello, world\" } },\n        { components: {} }\n      )\n    ).toEqual({\n      content: [],\n      root: { props: { title: \"Hello, world\" } },\n    });\n  });\n\n  it(\"should migrate zones to slots\", () => {\n    const input: Data = {\n      content: [{ type: \"Flex\", props: { id: \"Flex-123\" } }],\n      root: {},\n      zones: {\n        \"Flex-123:flexA\": [{ type: \"Other\", props: { id: \"Other-123\" } }],\n        \"Flex-123:flexB\": [{ type: \"Heading\", props: { id: \"Heading-123\" } }],\n        \"Other-123:content\": [\n          { type: \"Heading\", props: { id: \"Heading-456\" } },\n        ],\n      },\n    };\n\n    const config: Config = {\n      components: {\n        Flex: {\n          fields: {\n            // Migrate to slots for Flex\n            flexA: { type: \"slot\" },\n            flexB: { type: \"slot\" },\n          },\n          render: () => <div />,\n        },\n        Other: {\n          fields: {\n            // Migrate to slots for Other\n            content: { type: \"slot\" },\n          },\n          render: () => <div />,\n        },\n        Heading: {\n          render: () => <div />,\n        },\n      },\n    };\n\n    const output: Data = {\n      content: [\n        {\n          type: \"Flex\",\n          props: {\n            id: \"Flex-123\",\n            flexA: [\n              {\n                type: \"Other\",\n                props: {\n                  id: \"Other-123\",\n                  content: [{ type: \"Heading\", props: { id: \"Heading-456\" } }],\n                },\n              },\n            ],\n            flexB: [{ type: \"Heading\", props: { id: \"Heading-123\" } }],\n          },\n        },\n      ],\n      root: { props: {} },\n    };\n\n    expect(migrate(input, config)).toEqual(output);\n  });\n\n  it(\"should migrate dynamic arrays of zones to slots when a user provides a migration function\", () => {\n    const input: Data = {\n      root: {\n        props: {\n          title: \"Legacy Zones Migration\",\n        },\n      },\n      content: [\n        {\n          type: \"Columns\",\n          props: {\n            columns: [{}, {}, {}, {}],\n            id: \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be\",\n          },\n        },\n      ],\n      zones: {\n        \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be:column-0\": [\n          {\n            type: \"Text\",\n            props: {\n              text: \"Drop zone 1\",\n              id: \"Text-c2b5c0a5-d76b-4120-8bb3-99934e119967\",\n            },\n          },\n        ],\n        \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be:column-1\": [\n          {\n            type: \"Text\",\n            props: {\n              text: \"Drop zone 2\",\n              id: \"Text-8bdcf6ef-ba8c-4d5e-8010-e505a773e8d8\",\n            },\n          },\n        ],\n        \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be:column-2\": [\n          {\n            type: \"Text\",\n            props: {\n              text: \"Drop zone 3\",\n              id: \"Text-2f8f393a-d4ed-4714-9552-89defa056ed9\",\n            },\n          },\n        ],\n        \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be:column-3\": [\n          {\n            type: \"Text\",\n            props: {\n              text: \"Drop zone 4\",\n              id: \"Text-af41f55a-8af0-4e0c-8972-d54935301474\",\n            },\n          },\n        ],\n      },\n    };\n\n    const config: Config<{ Columns: { columns: { column: Slot } } }> = {\n      components: {\n        Columns: {\n          fields: {\n            columns: {\n              type: \"array\",\n              arrayFields: {\n                column: {\n                  type: \"slot\",\n                },\n              },\n            },\n          },\n          render: () => <div />,\n        },\n      },\n    };\n\n    const output: Data = {\n      root: {\n        props: {\n          title: \"Legacy Zones Migration\",\n        },\n      },\n      content: [\n        {\n          type: \"Columns\",\n          props: {\n            columns: [\n              {\n                column: [\n                  {\n                    type: \"Text\",\n                    props: {\n                      text: \"Drop zone 1\",\n                      id: \"Text-c2b5c0a5-d76b-4120-8bb3-99934e119967\",\n                    },\n                  },\n                ],\n              },\n              {\n                column: [\n                  {\n                    type: \"Text\",\n                    props: {\n                      text: \"Drop zone 2\",\n                      id: \"Text-8bdcf6ef-ba8c-4d5e-8010-e505a773e8d8\",\n                    },\n                  },\n                ],\n              },\n              {\n                column: [\n                  {\n                    type: \"Text\",\n                    props: {\n                      text: \"Drop zone 3\",\n                      id: \"Text-2f8f393a-d4ed-4714-9552-89defa056ed9\",\n                    },\n                  },\n                ],\n              },\n              {\n                column: [\n                  {\n                    type: \"Text\",\n                    props: {\n                      text: \"Drop zone 4\",\n                      id: \"Text-af41f55a-8af0-4e0c-8972-d54935301474\",\n                    },\n                  },\n                ],\n              },\n            ],\n            id: \"Columns-eb9dfe22-4408-44e6-b8e5-fbaedbbdb3be\",\n          },\n        },\n      ],\n    };\n\n    expect(\n      migrate(input, config, {\n        migrateDynamicZonesForComponent: {\n          Columns: (props, zones) => {\n            return {\n              ...props,\n              columns: Object.values(zones).map((zone) => ({\n                column: zone,\n              })),\n            };\n          },\n        },\n      })\n    ).toEqual(output);\n  });\n\n  it(\"should throw if matching slots aren't defined\", () => {\n    const input: Data = {\n      content: [{ type: \"Grid\", props: { id: \"Grid-123\" } }],\n      root: {},\n      zones: {\n        \"Grid-123:grid\": [{ type: \"Heading\", props: { id: \"Heading-123\" } }],\n      },\n    };\n\n    const config: Config = {\n      components: {\n        Grid: {\n          render: () => <div />,\n        },\n        Heading: {\n          render: () => <div />,\n        },\n      },\n    };\n\n    expect(() => migrate(input, config)).toThrowErrorMatchingInlineSnapshot(\n      `\"Could not migrate DropZone \"Grid-123:grid\" to slot field. No slot exists with the name \"grid\".\"`\n    );\n  });\n\n  it(\"should support migrating root DropZones\", () => {\n    const input: Data = {\n      root: { props: { title: \"\" } },\n      content: [\n        {\n          type: \"HeadingBlock\",\n          props: {\n            title: \"Header\",\n            id: \"HeadingBlock-1694032984497\",\n          },\n        },\n      ],\n      zones: {\n        \"root:footer\": [\n          {\n            type: \"HeadingBlock\",\n            props: {\n              id: \"HeadingBlock-f7f88252-1926-4042-80b0-6c5ec72f2f75\",\n              title: \"Footer header\",\n            },\n          },\n        ],\n      },\n    };\n\n    const config: Config = {\n      components: {\n        HeadingBlock: {\n          fields: {\n            title: { type: \"text\" },\n          },\n          render: ({ title }) => <h1>{title}</h1>,\n        },\n      },\n      root: {\n        fields: {\n          footer: { type: \"slot\" },\n        },\n        render: ({ children, footer }) => {\n          return (\n            <>\n              {children}\n              {footer()}\n            </>\n          );\n        },\n      },\n    };\n\n    expect(migrate(input, config)).toMatchInlineSnapshot(`\n      {\n        \"content\": [\n          {\n            \"props\": {\n              \"id\": \"HeadingBlock-1694032984497\",\n              \"title\": \"Header\",\n            },\n            \"type\": \"HeadingBlock\",\n          },\n        ],\n        \"root\": {\n          \"props\": {\n            \"footer\": [\n              {\n                \"props\": {\n                  \"id\": \"HeadingBlock-f7f88252-1926-4042-80b0-6c5ec72f2f75\",\n                  \"title\": \"Footer header\",\n                },\n                \"type\": \"HeadingBlock\",\n              },\n            ],\n            \"title\": \"\",\n          },\n        },\n      }\n    `);\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/move-component.spec.tsx",
    "content": "import { act } from \"@testing-library/react\";\nimport { createAppStore, defaultAppState } from \"../../store\";\nimport { Config } from \"../../types\";\nimport { getItem, ItemSelector } from \"../data/get-item\";\nimport { walkAppState } from \"../data/walk-app-state\";\nimport { cache } from \"../resolve-component-data\";\nimport { moveComponent } from \"../move-component\";\n\nconst appStore = createAppStore();\n\nconst childResolveData = jest.fn(async (data, params) => {\n  if (params.trigger === \"move\") {\n    return {\n      ...data,\n      props: {\n        resolvedProp: \"Resolved moved\",\n      },\n    };\n  }\n\n  return {\n    ...data,\n    props: {\n      resolvedProp: \"Resolved\",\n    },\n  };\n});\n\nconst config: Config = {\n  components: {\n    Parent: {\n      fields: { items: { type: \"slot\" } },\n      render: () => <div />,\n    },\n    Child: {\n      fields: {},\n      resolveData: childResolveData,\n      render: () => <div />,\n    },\n    NonResolvedChild: {\n      fields: {},\n      render: () => <div />,\n    },\n  },\n};\n\nconst moveChildTo = (\n  targetItemSelector: ItemSelector,\n  sourceItemSelector: ItemSelector = { zone: \"Parent-1:items\", index: 0 },\n  id = \"Child-1\"\n) => {\n  moveComponent(id, sourceItemSelector, targetItemSelector, appStore);\n};\n\nfunction resetStores() {\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n      config,\n      state: walkAppState(\n        {\n          ...defaultAppState,\n          data: {\n            ...defaultAppState.data,\n            content: [\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-1\",\n                  items: [\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-1\",\n                      },\n                    },\n                    {\n                      type: \"NonResolvedChild\",\n                      props: {\n                        id: \"NonResolvedChild-1\",\n                      },\n                    },\n                    {\n                      type: \"NonResolvedChild\",\n                      props: {\n                        id: \"NonResolvedChild-2\",\n                      },\n                    },\n                  ],\n                },\n              },\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-2\",\n                  items: [],\n                },\n              },\n            ],\n          },\n        },\n        config\n      ),\n    },\n    true\n  );\n}\n\n// TODO: Change this when we solve race conditions over caches, it should be 1\nconst resolveAndCommitDataCalls = 2;\n\ndescribe(\"moveComponent\", () => {\n  beforeEach(async () => {\n    resetStores();\n    jest.clearAllMocks();\n    cache.lastChange = {};\n\n    // Initialize resolveData cache\n    await act(async () => {\n      // This executes resolveData twice because of race conditions in cache\n      appStore.getState().resolveAndCommitData();\n    });\n  });\n\n  it(\"moves the component\", async () => {\n    // Given: ---------------\n    const targetItemSelector = { zone: \"Parent-1:items\", index: 1 };\n\n    // When: ---------------\n    await act(async () => moveChildTo(targetItemSelector));\n\n    // Then: ---------------\n    const componentAtTarget = getItem(\n      targetItemSelector,\n      appStore.getState().state\n    );\n    expect(componentAtTarget?.props.id).toEqual(\"Child-1\");\n  });\n\n  it(\"resolves data when dropped in a different parent\", async () => {\n    // Given: ---------------\n    const targetItemSelector = { zone: \"Parent-2:items\", index: 0 };\n\n    // When: ---------------\n    await act(async () => moveChildTo(targetItemSelector));\n\n    // Then: ---------------\n    const expectedCalls = resolveAndCommitDataCalls + 1;\n    expect(childResolveData).toHaveBeenCalledTimes(expectedCalls);\n    expect(\n      childResolveData.mock.calls[expectedCalls - 1][1].parent\n    ).toStrictEqual({\n      type: \"Parent\",\n      props: {\n        id: \"Parent-2\",\n        items: [\n          {\n            type: \"Child\",\n            props: {\n              id: \"Child-1\",\n              resolvedProp: \"Resolved\",\n            },\n          },\n        ],\n      },\n    });\n    const mockedReturn = await childResolveData.mock.results[expectedCalls - 1]\n      .value;\n    expect(mockedReturn.props.resolvedProp).toBe(\"Resolved moved\");\n  });\n\n  it(\"doesn't resolve data when dropped in the same parent\", async () => {\n    // Given: ---------------\n    const targetItemSelector = { zone: \"Parent-1:items\", index: 1 };\n\n    // When: ---------------\n    await act(async () => moveChildTo(targetItemSelector));\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(resolveAndCommitDataCalls);\n    const mockedCalls = childResolveData.mock.calls;\n    expect(\n      mockedCalls.find((call) => call[1].trigger === \"move\")\n    ).toBeUndefined();\n  });\n\n  it(\"resolves data with the move trigger\", async () => {\n    // When: ---------------\n    await act(async () => moveChildTo({ zone: \"Parent-2:items\", index: 0 }));\n\n    // Then: ---------------\n    const expectedCalls = resolveAndCommitDataCalls + 1;\n    expect(childResolveData).toHaveBeenCalledTimes(expectedCalls);\n    expect(childResolveData.mock.calls[expectedCalls - 1][1].trigger).toBe(\n      \"move\"\n    );\n    const mockedReturn = await childResolveData.mock.results[expectedCalls - 1]\n      .value;\n    expect(mockedReturn.props.resolvedProp).toBe(\"Resolved moved\");\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/resolve-all-data.spec.tsx",
    "content": "import { ComponentData, Config, Data } from \"../../types\";\nimport { resolveAllData } from \"../resolve-all-data\";\n\nconst item4 = {\n  type: \"ComponentWithResolveProps\",\n  props: { id: \"MyComponent-4\", prop: \"Original\", slot: [] },\n};\nconst item3 = {\n  type: \"ComponentWithResolveProps\",\n  props: { id: \"MyComponent-3\", prop: \"Original\", slot: [item4] },\n};\nconst item2 = {\n  type: \"ComponentWithResolveProps\",\n  props: { id: \"MyComponent-2\", prop: \"Original\", slot: [] },\n};\nconst item1 = {\n  type: \"ComponentWithResolveProps\",\n  props: { id: \"MyComponent-1\", prop: \"Original\", slot: [item2, item3] },\n};\nconst item7 = {\n  type: \"ComponentWithResolveProps\",\n  props: { id: \"MyComponent-7\", prop: \"Original\", slot: [] },\n};\nconst item5 = {\n  type: \"ComponentWithoutResolveProps\",\n  props: { id: \"MyComponent-5\", prop: \"Original\", slot: [] },\n};\nconst item6 = {\n  type: \"ComponentWithoutResolveProps\",\n  props: { id: \"MyComponent-6\", prop: \"Original\", slot: [item5] },\n};\n\nconst data: Data = {\n  root: { props: { title: \"\" } },\n  content: [item1],\n  zones: {\n    \"MyComponent-1:zone\": [item6],\n    \"MyComponent-5:zone\": [item7],\n  },\n};\n\nconst resolveData = jest.fn(async ({ props }, { trigger }) => {\n  return {\n    props: { ...props, prop: \"Resolved\" },\n    readOnly: { prop: true },\n  };\n});\n\nconst config: Config = {\n  components: {\n    ComponentWithResolveProps: {\n      fields: { slot: { type: \"slot\" } },\n      defaultProps: { prop: \"example\" },\n      resolveData,\n      render: () => <div />,\n    },\n    ComponentWithoutResolveProps: {\n      fields: { slot: { type: \"slot\" } },\n      defaultProps: { prop: \"example\" },\n      render: () => <div />,\n    },\n  },\n};\n\ndescribe(\"resolve-data\", () => {\n  beforeEach(() => jest.clearAllMocks());\n\n  it(\"should resolve the data for all components in the data\", async () => {\n    expect(await resolveAllData(data, config)).toMatchInlineSnapshot(`\n      {\n        \"content\": [\n          {\n            \"props\": {\n              \"id\": \"MyComponent-1\",\n              \"prop\": \"Resolved\",\n              \"slot\": [\n                {\n                  \"props\": {\n                    \"id\": \"MyComponent-2\",\n                    \"prop\": \"Resolved\",\n                    \"slot\": [],\n                  },\n                  \"readOnly\": {\n                    \"prop\": true,\n                  },\n                  \"type\": \"ComponentWithResolveProps\",\n                },\n                {\n                  \"props\": {\n                    \"id\": \"MyComponent-3\",\n                    \"prop\": \"Resolved\",\n                    \"slot\": [\n                      {\n                        \"props\": {\n                          \"id\": \"MyComponent-4\",\n                          \"prop\": \"Resolved\",\n                          \"slot\": [],\n                        },\n                        \"readOnly\": {\n                          \"prop\": true,\n                        },\n                        \"type\": \"ComponentWithResolveProps\",\n                      },\n                    ],\n                  },\n                  \"readOnly\": {\n                    \"prop\": true,\n                  },\n                  \"type\": \"ComponentWithResolveProps\",\n                },\n              ],\n            },\n            \"readOnly\": {\n              \"prop\": true,\n            },\n            \"type\": \"ComponentWithResolveProps\",\n          },\n        ],\n        \"root\": {\n          \"props\": {\n            \"id\": \"root\",\n            \"title\": \"\",\n          },\n          \"type\": \"root\",\n        },\n        \"zones\": {\n          \"MyComponent-1:zone\": [\n            {\n              \"props\": {\n                \"id\": \"MyComponent-6\",\n                \"prop\": \"Original\",\n                \"slot\": [\n                  {\n                    \"props\": {\n                      \"id\": \"MyComponent-5\",\n                      \"prop\": \"Original\",\n                      \"slot\": [],\n                    },\n                    \"type\": \"ComponentWithoutResolveProps\",\n                  },\n                ],\n              },\n              \"type\": \"ComponentWithoutResolveProps\",\n            },\n          ],\n          \"MyComponent-5:zone\": [\n            {\n              \"props\": {\n                \"id\": \"MyComponent-7\",\n                \"prop\": \"Resolved\",\n                \"slot\": [],\n              },\n              \"readOnly\": {\n                \"prop\": true,\n              },\n              \"type\": \"ComponentWithResolveProps\",\n            },\n          ],\n        },\n      }\n    `);\n  });\n\n  it(\"should receive 'force' as the trigger\", async () => {\n    await resolveAllData(data, config);\n\n    expect(\n      resolveData.mock.calls.every((call) => call[1].trigger === \"force\")\n    ).toBe(true);\n  });\n\n  it(\"should receive the parent component in params\", async () => {\n    const item1_2_1 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1_2_1\", prop: \"Original\", slot: [] },\n    };\n    const item1_2 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1_2\", prop: \"Original\", slot: [item1_2_1] },\n    };\n    const item1_1 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1_1\", prop: \"Original\", slot: [] },\n    };\n    const item1 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1\", prop: \"Original\", slot: [item1_1] },\n    };\n    const data: Data = {\n      root: { props: {} },\n      content: [item1],\n      zones: {\n        \"MyComponent-1:zone\": [item1_2],\n      },\n    };\n\n    let receivedParentById: Record<string, ComponentData | null> = {};\n\n    const resolveData = jest.fn(async ({ props }, { parent }) => {\n      receivedParentById[props.id] = parent;\n\n      return {\n        props: { ...props, prop: \"Resolved\" },\n      };\n    });\n\n    const config: Config = {\n      components: {\n        ComponentWithResolveProps: {\n          fields: { slot: { type: \"slot\" } },\n          defaultProps: { prop: \"example\" },\n          resolveData,\n          render: () => <div />,\n        },\n      },\n    };\n\n    // When: --------------------\n    await resolveAllData(data, config);\n\n    // Then: --------------------\n    expect(\n      resolveData.mock.calls.every((call) => call[1].parent !== null)\n    ).toBe(true);\n    expect(receivedParentById[item1.props.id]?.props.id).toBe(\"root\");\n    expect(receivedParentById[item1_1.props.id]?.props.id).toBe(item1.props.id);\n    expect(receivedParentById[item1_2.props.id]?.props.id).toBe(item1.props.id);\n    expect(receivedParentById[item1_2_1.props.id]?.props.id).toBe(\n      item1_2.props.id\n    );\n  });\n\n  it(\"should resolve children after it resolved the parent\", async () => {\n    const item1_2_1 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1_2_1\", prop: \"Original\", slot: [] },\n    };\n    const item1_2 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1_2\", prop: \"Original\", slot: [item1_2_1] },\n    };\n    const item1_1 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1_1\", prop: \"Original\", slot: [] },\n    };\n    const item1 = {\n      type: \"ComponentWithResolveProps\",\n      props: { id: \"MyComponent-1\", prop: \"Original\", slot: [item1_1] },\n    };\n    const data: Data = {\n      root: { props: {} },\n      content: [item1],\n      zones: {\n        \"MyComponent-1:zone\": [item1_2],\n      },\n    };\n\n    let receivedParentById: Record<string, ComponentData | null> = {};\n\n    const resolveData = jest.fn(async ({ props }, { parent }) => {\n      receivedParentById[props.id] = parent;\n\n      return {\n        props: { ...props, prop: \"Resolved\" },\n      };\n    });\n\n    const config: Config = {\n      components: {\n        ComponentWithResolveProps: {\n          fields: { slot: { type: \"slot\" } },\n          defaultProps: { prop: \"example\" },\n          resolveData,\n          render: () => <div />,\n        },\n      },\n      root: {\n        resolveData,\n      },\n    };\n\n    // When: --------------------\n    await resolveAllData(data, config);\n\n    // Then: --------------------\n    expect(receivedParentById[item1.props.id]?.props.id).toBe(\"root\");\n    expect(receivedParentById[item1.props.id]?.props.prop).toBe(\"Resolved\");\n    expect(receivedParentById[item1_1.props.id]?.props.id).toBe(item1.props.id);\n    expect(receivedParentById[item1_1.props.id]?.props.prop).toBe(\"Resolved\");\n    expect(receivedParentById[item1_2.props.id]?.props.id).toBe(item1.props.id);\n    expect(receivedParentById[item1_2.props.id]?.props.prop).toBe(\"Resolved\");\n    expect(receivedParentById[item1_2_1.props.id]?.props.id).toBe(\n      item1_2.props.id\n    );\n    expect(receivedParentById[item1_2_1.props.id]?.props.prop).toBe(\"Resolved\");\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/resolve-component-data.spec.tsx",
    "content": "import { resolveComponentData, cache } from \"../resolve-component-data\";\nimport { createAppStore } from \"../../store\";\nimport { Config, Fields, Slot } from \"../../types\";\nimport { toComponent } from \"../data/to-component\";\n\n// export the cache and manually reset it or dynamically import the file\n\nconst appStore = createAppStore();\n\ninterface ComponentProps {\n  prop: string;\n  slot: Slot;\n  object: { slot: Slot };\n}\n\nconst myComponentFields: Fields<ComponentProps> = {\n  prop: { type: \"text\" },\n  slot: {\n    type: \"slot\",\n  },\n  object: {\n    type: \"object\",\n    objectFields: {\n      slot: {\n        type: \"slot\",\n      },\n    },\n  },\n};\n\nconst rootResolveData = jest.fn((rootData) => {\n  return {\n    ...rootData,\n    props: {\n      title: \"Resolved title\",\n      slot: [\n        {\n          type: \"MyComponentWithResolver\",\n          props: { id: \"123456789\", prop: \"Not yet resolved\" },\n        },\n      ],\n      object: {\n        slot: [\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"987654321\", prop: \"Not yet resolved\" },\n          },\n        ],\n      },\n      array: [\n        {\n          slot: [\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"987654321\", prop: \"Not yet resolved\" },\n            },\n          ],\n        },\n      ],\n    },\n    readOnly: { title: true },\n  };\n});\n\nconst componentResolveData = jest.fn(({ props }, { parent }) => {\n  return {\n    props: {\n      ...props,\n      prop: \"Hello, world\",\n    },\n    readOnly: { prop: true },\n  };\n});\n\nconst config: Config<{\n  root: {\n    title: string;\n    object: { slot: Slot };\n    slot: Slot;\n    array: { slot: Slot }[];\n  };\n}> = {\n  root: {\n    fields: {\n      title: { type: \"text\" },\n      object: { type: \"object\", objectFields: { slot: { type: \"slot\" } } },\n      slot: { type: \"slot\" },\n      array: {\n        type: \"array\",\n        arrayFields: {\n          slot: {\n            type: \"slot\",\n          },\n        },\n      },\n    },\n    resolveData: rootResolveData,\n  },\n  components: {\n    MyComponentWithResolver: {\n      fields: myComponentFields,\n      resolveData: componentResolveData,\n      render: () => <div />,\n    },\n    MyComponentWithoutResolver: {\n      fields: myComponentFields,\n      render: () => <div />,\n    },\n  },\n};\n\ndescribe(\"resolveComponentData\", () => {\n  beforeEach(() => {\n    cache.lastChange = {};\n    appStore.setState({ ...appStore.getInitialState(), config }, true);\n    jest.clearAllMocks();\n  });\n\n  it(\"should run resolvers for every node in the tree\", async () => {\n    const { node: newRoot, didChange } = await resolveComponentData(\n      toComponent(appStore.getState().state.data.root),\n      appStore.getState().config\n    );\n\n    expect(newRoot.props?.title).toBe(\"Resolved title\");\n    expect(newRoot.readOnly?.title).toBe(true);\n    expect(newRoot.props.slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.slot[0].readOnly.prop).toBe(true);\n    expect(newRoot.props.object.slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.object.slot[0].readOnly.prop).toBe(true);\n    expect(newRoot.props.array[0].slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.array[0].slot[0].readOnly.prop).toBe(true);\n    expect(didChange).toBe(true);\n  });\n\n  it(\"should run child resolvers even if parent doesn't have one\", async () => {\n    const { node: newRoot, didChange } = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithoutResolver\",\n        props: {\n          title: \"Resolved title\",\n          slot: [\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"123456789\", prop: \"Not yet resolved\" },\n            },\n          ],\n          object: {\n            slot: [\n              {\n                type: \"MyComponentWithResolver\",\n                props: { id: \"987654321\", prop: \"Not yet resolved\" },\n              },\n            ],\n          },\n        },\n      }),\n      appStore.getState().config\n    );\n\n    expect(newRoot.props?.title).toBe(\"Resolved title\");\n    expect(newRoot.props.slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.slot[0].readOnly.prop).toBe(true);\n    expect(newRoot.props.object.slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.object.slot[0].readOnly.prop).toBe(true);\n    expect(didChange).toBe(true);\n  });\n\n  it(\"should not re-run when node doesn't change\", async () => {\n    await resolveComponentData(\n      toComponent(appStore.getState().state.data.root),\n      appStore.getState().config\n    );\n\n    const { node: newRoot, didChange } = await resolveComponentData(\n      toComponent(appStore.getState().state.data.root),\n      appStore.getState().config\n    );\n\n    expect(rootResolveData).toHaveBeenCalledTimes(1);\n    expect(componentResolveData).toHaveBeenCalledTimes(3);\n    expect(newRoot.props?.title).toBe(\"Resolved title\");\n    expect(newRoot.readOnly?.title).toBe(true);\n    expect(newRoot.props.slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.slot[0].readOnly.prop).toBe(true);\n    expect(didChange).toBe(false);\n  });\n\n  it(\"should re-run if forced to\", async () => {\n    // When: ---------------\n    await resolveComponentData(\n      toComponent(appStore.getState().state.data.root),\n      appStore.getState().config\n    );\n\n    const { node: newRoot, didChange } = await resolveComponentData(\n      toComponent(appStore.getState().state.data.root),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"force\"\n    );\n\n    // Then: ---------------\n    expect(rootResolveData).toHaveBeenCalledTimes(2);\n    expect(componentResolveData).toHaveBeenCalledTimes(6);\n    expect(newRoot.props?.title).toBe(\"Resolved title\");\n    expect(newRoot.readOnly?.title).toBe(true);\n    expect(newRoot.props.slot[0].props.prop).toBe(\"Hello, world\");\n    expect(newRoot.props.slot[0].readOnly.prop).toBe(true);\n    expect(didChange).toBe(true);\n  });\n\n  it(\"should re-run when parent changes for 'move' triggers\", async () => {\n    // When: ---------------\n    const initialResolution = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithResolver\",\n        props: { id: \"moved\" },\n      }),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"load\",\n      {\n        type: \"MyComponentWithoutResolver\",\n        props: {\n          id: \"parent-1\",\n          slot: [\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"moved\" },\n            },\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"not-moved-2\" },\n            },\n          ],\n        },\n      }\n    );\n\n    const movedResolution = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithResolver\",\n        props: { id: \"moved\" },\n      }),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"move\",\n      {\n        type: \"MyComponentWithoutResolver\",\n        props: {\n          id: \"parent-2\",\n          slot: [\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"moved\" },\n            },\n          ],\n        },\n      }\n    );\n\n    // Then: ---------------\n    expect(componentResolveData).toHaveBeenCalledTimes(2);\n    expect(componentResolveData.mock.calls[0][1].parent).toStrictEqual({\n      type: \"MyComponentWithoutResolver\",\n      props: {\n        id: \"parent-1\",\n        slot: [\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"moved\" },\n          },\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"not-moved-2\" },\n          },\n        ],\n      },\n    });\n    expect(componentResolveData.mock.calls[1][1].parent).toStrictEqual({\n      type: \"MyComponentWithoutResolver\",\n      props: {\n        id: \"parent-2\",\n        slot: [\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"moved\" },\n          },\n        ],\n      },\n    });\n    expect(initialResolution.node.props.prop).toBe(\"Hello, world\");\n    expect(initialResolution.node.readOnly.prop).toBe(true);\n    expect(initialResolution.didChange).toBe(true);\n    expect(movedResolution.node.props.prop).toBe(\"Hello, world\");\n    expect(movedResolution.node.readOnly.prop).toBe(true);\n    expect(movedResolution.didChange).toBe(true);\n  });\n\n  it(\"shouldn't re-run when parent doesn't change for 'move' triggers\", async () => {\n    // When: ---------------\n    const parent = {\n      type: \"MyComponentWithoutResolver\",\n      props: {\n        id: \"parent-1\",\n        slot: [\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"moved\" },\n          },\n        ],\n      },\n    };\n\n    const initialResolution = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithResolver\",\n        props: { id: \"moved\" },\n      }),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"load\",\n      parent\n    );\n\n    const movedResolution = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithResolver\",\n        props: { id: \"moved\" },\n      }),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"move\",\n      parent\n    );\n\n    // Then: ---------------\n    expect(componentResolveData).toHaveBeenCalledTimes(1);\n    expect(componentResolveData.mock.calls[0][1].parent).toStrictEqual({\n      type: \"MyComponentWithoutResolver\",\n      props: {\n        id: \"parent-1\",\n        slot: [\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"moved\" },\n          },\n        ],\n      },\n    });\n    expect(initialResolution.node.props.prop).toBe(\"Hello, world\");\n    expect(initialResolution.node.readOnly.prop).toBe(true);\n    expect(initialResolution.didChange).toBe(true);\n    expect(movedResolution.node.props.prop).toBe(\"Hello, world\");\n    expect(movedResolution.node.readOnly.prop).toBe(true);\n    expect(movedResolution.didChange).toBe(false);\n  });\n\n  it(\"shouldn't run for 'move' triggers before the component resolves once for other actions\", async () => {\n    // When: ---------------\n    const movedResolution = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithResolver\",\n        props: { id: \"inserted\" },\n      }),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"move\",\n      {\n        type: \"MyComponentWithoutResolver\",\n        props: {\n          id: \"parent-1\",\n          slot: [\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"inserted\" },\n            },\n          ],\n        },\n      }\n    );\n\n    const insertedResolution = await resolveComponentData(\n      toComponent({\n        type: \"MyComponentWithResolver\",\n        props: { id: \"inserted\" },\n      }),\n      appStore.getState().config,\n      undefined,\n      undefined,\n      undefined,\n      \"insert\",\n      {\n        type: \"MyComponentWithoutResolver\",\n        props: {\n          id: \"parent-1\",\n          slot: [\n            {\n              type: \"MyComponentWithResolver\",\n              props: { id: \"inserted\" },\n            },\n          ],\n        },\n      }\n    );\n\n    // Then: ---------------\n    expect(componentResolveData).toHaveBeenCalledTimes(1);\n    expect(componentResolveData.mock.calls[0][1].parent).toStrictEqual({\n      type: \"MyComponentWithoutResolver\",\n      props: {\n        id: \"parent-1\",\n        slot: [\n          {\n            type: \"MyComponentWithResolver\",\n            props: { id: \"inserted\" },\n          },\n        ],\n      },\n    });\n    expect(insertedResolution.node.props.prop).toBe(\"Hello, world\");\n    expect(insertedResolution.node.readOnly.prop).toBe(true);\n    expect(insertedResolution.didChange).toBe(true);\n    expect(movedResolution.node).toStrictEqual({});\n    expect(movedResolution.didChange).toBe(false);\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/transform-props.spec.tsx",
    "content": "import { transformProps } from \"../transform-props\";\n\njest.spyOn(console, \"warn\").mockImplementation(() => {});\n\ndescribe(\"transformProps method\", () => {\n  it(\"should migrate props for the root\", () => {\n    expect(\n      transformProps(\n        { content: [], root: { props: { title: \"Hello, world\" } } },\n        {\n          root: (props) => ({\n            updatedTitle: props.title,\n          }),\n        }\n      )\n    ).toEqual({\n      content: [],\n      root: { props: { updatedTitle: \"Hello, world\" } },\n      zones: {},\n    });\n  });\n\n  // DEPRECATED\n  it(\"should migrate props for the legacy roots\", () => {\n    expect(\n      transformProps(\n        { content: [], root: { title: \"Hello, world\" } },\n        {\n          root: (props) => ({\n            updatedTitle: props.title,\n          }),\n        }\n      )\n    ).toEqual({\n      content: [],\n      root: { updatedTitle: \"Hello, world\" },\n      zones: {},\n    });\n  });\n\n  it(\"should migrate props for a specified component\", () => {\n    expect(\n      transformProps(\n        {\n          content: [\n            {\n              type: \"HeadingBlock\",\n              props: { title: \"Hello, world\", id: \"123\" },\n            },\n          ],\n\n          root: { props: { title: \"Hello, world\" } },\n          zones: {\n            MyZone: [\n              {\n                type: \"HeadingBlock\",\n                props: { title: \"Hello, other world\", id: \"456\" },\n              },\n            ],\n          },\n        },\n        {\n          HeadingBlock: (props) => ({\n            heading: props.title,\n          }),\n        }\n      )\n    ).toMatchInlineSnapshot(`\n      {\n        \"content\": [\n          {\n            \"props\": {\n              \"heading\": \"Hello, world\",\n              \"id\": \"123\",\n            },\n            \"type\": \"HeadingBlock\",\n          },\n        ],\n        \"root\": {\n          \"props\": {\n            \"title\": \"Hello, world\",\n          },\n        },\n        \"zones\": {\n          \"MyZone\": [\n            {\n              \"props\": {\n                \"id\": \"456\",\n                \"title\": \"Hello, other world\",\n              },\n              \"type\": \"HeadingBlock\",\n            },\n          ],\n        },\n      }\n    `);\n  });\n\n  it(\"should migrate props for slots\", () => {\n    expect(\n      transformProps(\n        {\n          content: [\n            {\n              type: \"Flex\",\n              props: {\n                id: \"Flex-1\",\n                content: [\n                  {\n                    type: \"HeadingBlock\",\n                    props: { title: \"Hello, other world\", id: \"456\" },\n                  },\n                ],\n              },\n            },\n          ],\n          root: {\n            props: {\n              content: [\n                {\n                  type: \"HeadingBlock\",\n                  props: { title: \"Hello, other world\", id: \"456\" },\n                },\n              ],\n            },\n          },\n        },\n        {\n          HeadingBlock: (props) => ({\n            heading: props.title,\n          }),\n        },\n        {\n          root: {\n            fields: { content: { type: \"slot\" } },\n          },\n          components: {\n            Flex: {\n              fields: { content: { type: \"slot\" } },\n              render: () => <div />,\n            },\n            HeadingBlock: {\n              fields: { title: { type: \"text\" } },\n              render: () => <div />,\n            },\n          },\n        }\n      )\n    ).toMatchInlineSnapshot(`\n      {\n        \"content\": [\n          {\n            \"props\": {\n              \"content\": [\n                {\n                  \"props\": {\n                    \"heading\": \"Hello, other world\",\n                    \"id\": \"456\",\n                  },\n                  \"type\": \"HeadingBlock\",\n                },\n              ],\n              \"id\": \"Flex-1\",\n            },\n            \"type\": \"Flex\",\n          },\n        ],\n        \"root\": {\n          \"props\": {\n            \"content\": [\n              {\n                \"props\": {\n                  \"heading\": \"Hello, other world\",\n                  \"id\": \"456\",\n                },\n                \"type\": \"HeadingBlock\",\n              },\n            ],\n          },\n        },\n        \"zones\": {},\n      }\n    `);\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/__tests__/use-breadcrumbs.spec.tsx",
    "content": "import { renderHook, render } from \"@testing-library/react\";\nimport { useBreadcrumbs } from \"../use-breadcrumbs\";\nimport { createAppStore, appStoreContext } from \"../../store\";\nimport { ComponentData, Config } from \"../../types\";\nimport { PropsWithChildren } from \"react\";\nimport { walkAppState } from \"../data/walk-app-state\";\n\nconst appStore = createAppStore();\n\nconst Context = (props: PropsWithChildren) => {\n  return (\n    <appStoreContext.Provider value={appStore}>\n      {props.children}\n    </appStoreContext.Provider>\n  );\n};\n\nfunction resetStores() {\n  // Reset the main app store\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n    },\n    true\n  );\n}\n\ndescribe(\"useBreadcrumbs\", () => {\n  beforeEach(() => {\n    resetStores();\n  });\n\n  it(\"returns an empty array if no path is found\", () => {\n    // No nodes, no selectedItem => path is undefined\n    const { result } = renderHook(() => useBreadcrumbs());\n    expect(result.current).toEqual([]);\n  });\n\n  it(\"returns a breadcrumb path including 'Page' for root\", () => {\n    const config: Config = {\n      components: {\n        MyComponent: {\n          label: \"My Component Label\",\n          render: () => <div />,\n        },\n      },\n    };\n\n    const testItem: ComponentData = {\n      type: \"MyComponent\",\n      props: { id: \"item-1\" },\n    };\n\n    const testItem2: ComponentData = {\n      type: \"MyComponent\",\n      props: { id: \"item-2\" },\n    };\n\n    appStore.setState({\n      config,\n      state: walkAppState(\n        {\n          ui: appStore.getState().state.ui,\n          data: {\n            content: [testItem],\n            root: {},\n            zones: {\n              \"item-1:zone\": [testItem2],\n            },\n          },\n          indexes: { nodes: {}, zones: {} },\n        },\n        config\n      ),\n      selectedItem: testItem2,\n    });\n\n    let result: any;\n\n    const Comp = () => {\n      result = useBreadcrumbs();\n      return <></>;\n    };\n\n    render(\n      <Context>\n        <Comp />\n      </Context>\n    );\n\n    // 5) Expect the result to include the \"Page\" crumb plus the item crumb\n    expect(result).toEqual([\n      { label: \"Page\", selector: null },\n      {\n        label: \"My Component Label\",\n        selector: { index: 0, zone: \"root:default-zone\" },\n      },\n    ]);\n  });\n\n  it(\"truncates the breadcrumb list to renderCount if provided\", () => {\n    const config: Config = {\n      components: {\n        MyComponent: {\n          label: \"My Component Label\",\n          render: () => <div />,\n        },\n      },\n    };\n\n    const testItem: ComponentData = {\n      type: \"MyComponent\",\n      props: { id: \"item-1\" },\n    };\n\n    const testItem2: ComponentData = {\n      type: \"MyComponent\",\n      props: { id: \"item-2\" },\n    };\n\n    appStore.setState({\n      config,\n      state: walkAppState(\n        {\n          ui: appStore.getState().state.ui,\n          data: {\n            content: [testItem],\n            root: {},\n            zones: {\n              \"item-1:zone\": [testItem2],\n            },\n          },\n          indexes: { nodes: {}, zones: {} },\n        },\n        config\n      ),\n      selectedItem: testItem2,\n    });\n\n    let result: any;\n\n    const Comp = () => {\n      result = useBreadcrumbs(1);\n      return <></>;\n    };\n\n    render(\n      <Context>\n        <Comp />\n      </Context>\n    );\n\n    expect(result).toEqual([\n      {\n        label: \"My Component Label\",\n        selector: { index: 0, zone: \"root:default-zone\" },\n      },\n    ]);\n  });\n\n  it(\"defaults to using the type name if config.components[type].label is missing\", () => {\n    // 1) Set up the config & selected item in the app store\n    const config: Config = {\n      components: {\n        MyComponent: {\n          render: () => <div />,\n        },\n      },\n    };\n\n    const testItem: ComponentData = {\n      type: \"MyComponent\",\n      props: { id: \"item-1\" },\n    };\n\n    const testItem2: ComponentData = {\n      type: \"MyComponent\",\n      props: { id: \"item-2\" },\n    };\n\n    appStore.setState({\n      config,\n      state: walkAppState(\n        {\n          ui: appStore.getState().state.ui,\n          data: {\n            content: [testItem],\n            root: {},\n            zones: {\n              \"item-1:zone\": [testItem2],\n            },\n          },\n          indexes: { nodes: {}, zones: {} },\n        },\n        config\n      ),\n      selectedItem: testItem2,\n    });\n\n    let result: any;\n\n    const Comp = () => {\n      result = useBreadcrumbs();\n      return <></>;\n    };\n\n    render(\n      <Context>\n        <Comp />\n      </Context>\n    );\n\n    // 5) Expect the result to include the \"Page\" crumb plus the item crumb\n    expect(result).toEqual([\n      { label: \"Page\", selector: null },\n      {\n        label: \"MyComponent\", // Fall back to type name\n        selector: { index: 0, zone: \"root:default-zone\" },\n      },\n    ]);\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/accumulate-transform.ts",
    "content": "export function accumulateTransform(el: HTMLElement) {\n  let matrix = new DOMMatrixReadOnly();\n  let n: HTMLElement | null = el.parentElement;\n\n  while (n && n !== document.documentElement) {\n    const t = getComputedStyle(n).transform;\n    if (t && t !== \"none\") {\n      matrix = new DOMMatrixReadOnly(t).multiply(matrix);\n    }\n    n = n.parentElement;\n  }\n\n  return { scaleX: matrix.a, scaleY: matrix.d };\n}\n"
  },
  {
    "path": "packages/core/lib/assign-refs.ts",
    "content": "export type Ref<ElementType = HTMLElement> =\n  | React.RefObject<ElementType | null>\n  | React.ForwardedRef<ElementType | null>\n  | ((element: ElementType | null) => void);\n\nexport function assignRef<ElementType = HTMLElement>(\n  ref: Ref<ElementType>,\n  node: ElementType | null\n) {\n  if (typeof ref === \"function\") {\n    ref(node);\n  } else if (ref && typeof ref === \"object\" && \"current\" in ref) {\n    ref.current = node;\n  }\n}\n\nexport function assignRefs<ElementType = HTMLElement>(\n  refs: Ref<ElementType>[],\n  node: ElementType | null\n) {\n  refs.forEach((ref) => {\n    assignRef<ElementType>(ref, node);\n  });\n}\n"
  },
  {
    "path": "packages/core/lib/bubble-pointer-event.ts",
    "content": "export interface BubbledPointerEventType extends PointerEvent {\n  originalTarget: EventTarget | null;\n}\n\n// Necessary to enable server build\nconst BaseEvent = typeof PointerEvent !== \"undefined\" ? PointerEvent : Event;\n\nexport class BubbledPointerEvent extends BaseEvent {\n  _originalTarget: EventTarget | null = null;\n\n  constructor(\n    type: string,\n    data: PointerEvent & { originalTarget: EventTarget | null }\n  ) {\n    super(type, data);\n    this.originalTarget = data.originalTarget;\n  }\n\n  // Necessary for Firefox\n  set originalTarget(target: EventTarget | null) {\n    this._originalTarget = target;\n  }\n\n  // Necessary for Firefox\n  get originalTarget() {\n    return this._originalTarget;\n  }\n}\n"
  },
  {
    "path": "packages/core/lib/data/__tests__/flatten-data.spec.tsx",
    "content": "import { Config, Data, Slot, UiState } from \"../../../types\";\nimport { flattenData } from \"../flatten-data\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport {\n  createAppStore,\n  defaultAppState as _defaultAppState,\n} from \"../../../store\";\n\ntype Props = {\n  Heading: {\n    title: string;\n  };\n  Container: {\n    Content: Slot;\n  };\n};\n\ntype RootProps = {\n  title: string;\n  slot: Slot;\n};\n\ntype UserConfig = Config<Props, RootProps>;\ntype UserData = Data<Props, RootProps>;\n\nconst dzZoneCompound = \"container-component:zone1\";\n\nconst defaultData: UserData = {\n  root: { props: { title: \"\", slot: [] } },\n  content: [],\n  zones: { [dzZoneCompound]: [] },\n};\n\nconst defaultUi: UiState = _defaultAppState.ui;\n\nconst defaultIndexes: PrivateAppState<UserData>[\"indexes\"] = {\n  nodes: {},\n  zones: {\n    \"root:slot\": { contentIds: [], type: \"slot\" },\n    [dzZoneCompound]: { contentIds: [], type: \"dropzone\" },\n  },\n};\n\nconst defaultState = {\n  data: defaultData,\n  ui: defaultUi,\n  indexes: defaultIndexes,\n};\n\nconst appStore = createAppStore();\n\nconst config: UserConfig = {\n  root: {\n    fields: { title: { type: \"text\" }, slot: { type: \"slot\" } },\n  },\n  components: {\n    Heading: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: { title: \"Title\" },\n      resolvePermissions: () => {\n        return {\n          drag: false,\n          duplicate: false,\n          delete: false,\n          edit: false,\n        };\n      },\n      render: () => <div />,\n    },\n    Container: {\n      fields: {\n        Content: { type: \"slot\" },\n      },\n      defaultProps: {\n        Content: [],\n      },\n      render: () => <div />,\n    },\n  },\n};\n\nconst mockState: PrivateAppState = {\n  ...defaultState,\n  data: {\n    ...defaultData,\n    content: [\n      {\n        type: \"Container\",\n        props: {\n          id: \"container-component\",\n          Content: [\n            {\n              type: \"Heading\",\n              props: {\n                title: \"Nested Title\",\n                id: \"heading-component\",\n              },\n            },\n          ],\n        },\n      },\n    ],\n    zones: {},\n  },\n};\n\ndescribe(\"flatten-data\", () => {\n  beforeEach(() => {\n    appStore.setState(\n      {\n        ...appStore.getInitialState(),\n        config,\n      },\n      true\n    );\n  });\n\n  it(\"should flatten data correctly\", () => {\n    const result = flattenData(mockState, config);\n\n    expect(result).toBeDefined();\n    expect(Array.isArray(result)).toBe(true);\n    expect(result.length).toBeGreaterThan(0);\n\n    // Should contain the root component\n    const rootComponent = result.find((item) => item.props.id === \"root\");\n    expect(rootComponent).toBeDefined();\n    expect(rootComponent?.type).toBe(\"root\");\n\n    // Should contain the Container component\n    const containerComponent = result.find(\n      (item) => item.props.id === \"container-component\"\n    );\n    expect(containerComponent).toBeDefined();\n    expect(containerComponent?.type).toBe(\"Container\");\n\n    // Should contain the Heading component\n    const headingComponent = result.find(\n      (item) => item.props.id === \"heading-component\"\n    );\n    expect(headingComponent).toBeDefined();\n    expect(headingComponent?.type).toBe(\"Heading\");\n    expect(headingComponent?.props.title).toBe(\"Nested Title\");\n  });\n\n  it(\"should return components for empty content\", () => {\n    const emptyContentState: PrivateAppState = {\n      ...defaultState,\n      data: {\n        ...defaultData,\n        content: [],\n        zones: {},\n      },\n    };\n\n    const result = flattenData(emptyContentState, config);\n    expect(result).toBeDefined();\n    expect(Array.isArray(result)).toBe(true);\n    // Should still contain root even with empty content\n    const rootComponent = result.find((item) => item.props.id === \"root\");\n    expect(rootComponent).toBeDefined();\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/data/__tests__/resolve-and-replace-data.spec.tsx",
    "content": "import { act } from \"@testing-library/react\";\nimport { createAppStore, defaultAppState } from \"../../../store\";\nimport { Config, ResolveDataTrigger } from \"../../../types\";\nimport { cache } from \"../../resolve-component-data\";\nimport { resolveAndReplaceData } from \"../resolve-and-replace-data\";\nimport { walkAppState } from \"../walk-app-state\";\n\nconst appStore = createAppStore();\n\nconst childResolveData = jest.fn(async (data, params) => {\n  return {\n    ...data,\n    props: {\n      resolvedProp: params.trigger,\n    },\n  };\n});\n\nconst config: Config = {\n  components: {\n    Parent: {\n      fields: { items: { type: \"slot\" } },\n      render: () => <div />,\n    },\n    Child: {\n      fields: {},\n      resolveData: childResolveData,\n      render: () => <div />,\n    },\n  },\n};\n\nconst child1 = {\n  type: \"Child\",\n  props: {\n    id: \"Child-1\",\n  },\n};\n\nfunction resetStores() {\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n      config,\n      state: walkAppState(\n        {\n          ...defaultAppState,\n          data: {\n            ...defaultAppState.data,\n            content: [\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-1\",\n                  items: [\n                    child1,\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-2\",\n                      },\n                    },\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-3\",\n                      },\n                    },\n                  ],\n                },\n              },\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-2\",\n                  items: [],\n                },\n              },\n            ],\n          },\n        },\n        config\n      ),\n    },\n    true\n  );\n}\n\ndescribe(\"resolveAndReplaceData\", () => {\n  beforeEach(async () => {\n    resetStores();\n    jest.clearAllMocks();\n    cache.lastChange = {};\n  });\n\n  it(\"resolves when called\", async () => {\n    // When: ---------------\n    await act(() => resolveAndReplaceData(child1, appStore.getState));\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(1);\n    const mockedReturn = await childResolveData.mock.results[0].value;\n    expect(mockedReturn.props.resolvedProp).toBeDefined();\n  });\n\n  it(\"resolves with a 'force' trigger by default\", async () => {\n    // When: ---------------\n    await act(() => resolveAndReplaceData(child1, appStore.getState));\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(1);\n    const mockedReturn = await childResolveData.mock.results[0].value;\n    expect(mockedReturn.props.resolvedProp).toBe(\"force\");\n  });\n\n  it(\"resolves for non default triggers\", async () => {\n    // Given: --------------\n    const trigger: ResolveDataTrigger = \"insert\";\n\n    // When: ---------------\n    await act(() => resolveAndReplaceData(child1, appStore.getState, trigger));\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(1);\n    const mockedReturn = await childResolveData.mock.results[0].value;\n    expect(mockedReturn.props.resolvedProp).toBe(trigger);\n  });\n\n  it(\"shows a warning if the id doesn't exist after resolving\", async () => {\n    // Given: --------------\n    const consoleWarnMock = jest.spyOn(console, \"warn\").mockImplementation();\n    const nonExistentComponent = {\n      type: \"Child\",\n      props: {\n        id: \"Doesn't exist\",\n      },\n    };\n\n    // When: ---------------\n    await act(() =>\n      resolveAndReplaceData(nonExistentComponent, appStore.getState)\n    );\n\n    // Then: ---------------\n    expect(consoleWarnMock).toHaveBeenCalledTimes(1);\n    consoleWarnMock.mockRestore();\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/data/__tests__/resolve-data-by-id.spec.tsx",
    "content": "import { act } from \"@testing-library/react\";\nimport { createAppStore, defaultAppState } from \"../../../store\";\nimport { Config } from \"../../../types\";\nimport { cache } from \"../../resolve-component-data\";\nimport { resolveDataById } from \"../resolve-data-by-id\";\nimport { walkAppState } from \"../walk-app-state\";\n\nconst appStore = createAppStore();\n\nconst childResolveData = jest.fn(async (data, params) => {\n  return {\n    ...data,\n    props: {\n      resolvedProp: params.trigger,\n    },\n  };\n});\n\nconst config: Config = {\n  components: {\n    Parent: {\n      fields: { items: { type: \"slot\" } },\n      render: () => <div />,\n    },\n    Child: {\n      fields: {},\n      resolveData: childResolveData,\n      render: () => <div />,\n    },\n  },\n};\n\nfunction resetStores() {\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n      config,\n      state: walkAppState(\n        {\n          ...defaultAppState,\n          data: {\n            ...defaultAppState.data,\n            content: [\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-1\",\n                  items: [\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-1\",\n                      },\n                    },\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-2\",\n                      },\n                    },\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-3\",\n                      },\n                    },\n                  ],\n                },\n              },\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-2\",\n                  items: [],\n                },\n              },\n            ],\n          },\n        },\n        config\n      ),\n    },\n    true\n  );\n}\n\ndescribe(\"resolveDataById\", () => {\n  beforeEach(async () => {\n    resetStores();\n    jest.clearAllMocks();\n    cache.lastChange = {};\n  });\n\n  it(\"resolves when called\", async () => {\n    // When: ---------------\n    await act(() => resolveDataById(\"Child-1\", appStore.getState));\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(1);\n    const mockedReturn = await childResolveData.mock.results[0].value;\n    expect(mockedReturn.props.resolvedProp).toBeDefined();\n  });\n\n  it(\"shows a warning and doesn't resolve if the id doesn't exist\", async () => {\n    // Given: --------------\n    const consoleWarnMock = jest.spyOn(console, \"warn\").mockImplementation();\n\n    // When: ---------------\n    await act(() => resolveDataById(\"Doesn't exist\", appStore.getState));\n\n    // Then: ---------------\n    expect(childResolveData).not.toHaveBeenCalled();\n    expect(consoleWarnMock).toHaveBeenCalledTimes(1);\n    consoleWarnMock.mockRestore();\n  });\n\n  it(\"shows a warning and doesn't resolve if the component was deleted\", async () => {\n    // Given: --------------\n    const dispatch = appStore.getState().dispatch;\n    const consoleWarnMock = jest.spyOn(console, \"warn\").mockImplementation();\n\n    // When: ---------------\n    await act(async () => {\n      dispatch({ type: \"remove\", index: 0, zone: \"Parent-1:items\" });\n      resolveDataById(\"Child-1\", appStore.getState);\n    });\n\n    // Then: ---------------\n    expect(childResolveData).not.toHaveBeenCalled();\n    expect(consoleWarnMock).toHaveBeenCalledTimes(1);\n    consoleWarnMock.mockRestore();\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/data/__tests__/resolve-data-by-selector.spec.tsx",
    "content": "import { act } from \"@testing-library/react\";\nimport { createAppStore, defaultAppState } from \"../../../store\";\nimport { Config } from \"../../../types\";\nimport { cache } from \"../../resolve-component-data\";\nimport { resolveDataBySelector } from \"../resolve-data-by-selector\";\nimport { walkAppState } from \"../walk-app-state\";\nimport { getItem } from \"../get-item\";\n\nconst appStore = createAppStore();\n\nconst childResolveData = jest.fn(async (data, params) => {\n  return {\n    ...data,\n    props: {\n      resolvedProp: params.trigger,\n    },\n  };\n});\n\nconst config: Config = {\n  components: {\n    Parent: {\n      fields: { items: { type: \"slot\" } },\n      render: () => <div />,\n    },\n    Child: {\n      fields: {},\n      resolveData: childResolveData,\n      render: () => <div />,\n    },\n  },\n};\n\nconst child1Selector = { zone: \"Parent-1:items\", index: 0 };\n\nfunction resetStores() {\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n      config,\n      state: walkAppState(\n        {\n          ...defaultAppState,\n          data: {\n            ...defaultAppState.data,\n            content: [\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-1\",\n                  items: [\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-1\",\n                      },\n                    },\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-2\",\n                      },\n                    },\n                    {\n                      type: \"Child\",\n                      props: {\n                        id: \"Child-3\",\n                      },\n                    },\n                  ],\n                },\n              },\n              {\n                type: \"Parent\",\n                props: {\n                  id: \"Parent-2\",\n                  items: [],\n                },\n              },\n            ],\n          },\n        },\n        config\n      ),\n    },\n    true\n  );\n}\n\ndescribe(\"resolveDataBySelector\", () => {\n  beforeEach(async () => {\n    resetStores();\n    jest.clearAllMocks();\n    cache.lastChange = {};\n  });\n\n  it(\"resolves when called\", async () => {\n    // When: ---------------\n    await act(() => resolveDataBySelector(child1Selector, appStore.getState));\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(1);\n    const mockedReturn = await childResolveData.mock.results[0].value;\n    expect(mockedReturn.props.resolvedProp).toBeDefined();\n  });\n\n  it(\"shows a warning and doesn't resolve if the selector doesn't point to an existing component\", async () => {\n    // Given: --------------\n    const nonExistentSelector = { zone: \"Parent-2:items\", index: 3 };\n    const consoleWarnMock = jest.spyOn(console, \"warn\").mockImplementation();\n\n    // When: ---------------\n    await act(() =>\n      resolveDataBySelector(nonExistentSelector, appStore.getState)\n    );\n\n    // Then: ---------------\n    expect(childResolveData).not.toHaveBeenCalled();\n    expect(consoleWarnMock).toHaveBeenCalledTimes(1);\n    consoleWarnMock.mockRestore();\n  });\n\n  it(\"resolves correctly when called immediately after inserting a component\", async () => {\n    // Given: --------------\n    const dispatch = appStore.getState().dispatch;\n    const targetSelector = { zone: \"Parent-1:items\", index: 0 };\n\n    // When: ---------------\n    await act(async () => {\n      dispatch({\n        type: \"insert\",\n        destinationIndex: targetSelector.index,\n        destinationZone: targetSelector.zone,\n        componentType: \"Child\",\n      });\n      resolveDataBySelector(targetSelector, appStore.getState, \"insert\");\n    });\n\n    // Then: ---------------\n    expect(childResolveData).toHaveBeenCalledTimes(1);\n    const mockedReturn = await childResolveData.mock.results[0].value;\n    expect(mockedReturn.props.resolvedProp).toBeDefined();\n    const componentInData = getItem(targetSelector, appStore.getState().state);\n    expect(componentInData?.props.resolvedProp).toBe(\"insert\");\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/data/__tests__/walk-app-state.spec.tsx",
    "content": "import { ComponentData, Config, Data, Slot, UiState } from \"../../../types\";\n\nimport {\n  createAppStore,\n  defaultAppState as _defaultAppState,\n} from \"../../../store\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport { walkAppState } from \"../walk-app-state\";\nimport { rootDroppableId } from \"../../root-droppable-id\";\nimport { flattenNode } from \"../flatten-node\";\n\ntype Props = {\n  Comp: {\n    prop: string;\n    slotA: Slot;\n    slotB: Slot;\n    array: { slot: Slot }[];\n    object: { slot: Slot };\n  };\n};\n\ntype RootProps = {\n  title: string;\n  slot: Slot;\n};\n\ntype UserConfig = Config<Props, RootProps>;\ntype UserData = Data<Props, RootProps>;\n\nconst dzZoneCompound = \"my-component:zone1\";\n\nconst defaultData: UserData = {\n  root: { props: { title: \"\", slot: [] } },\n  content: [],\n  zones: { [dzZoneCompound]: [] },\n};\n\nconst defaultUi: UiState = _defaultAppState.ui;\n\nconst defaultIndexes: PrivateAppState<UserData>[\"indexes\"] = {\n  nodes: {},\n  zones: {\n    \"root:slot\": { contentIds: [], type: \"slot\" },\n    [dzZoneCompound]: { contentIds: [], type: \"dropzone\" },\n  },\n};\n\nconst defaultState = {\n  data: defaultData,\n  ui: defaultUi,\n  indexes: defaultIndexes,\n};\n\nconst appStore = createAppStore();\n\ndescribe(\"walk-app-state\", () => {\n  const config: UserConfig = {\n    root: {\n      fields: { title: { type: \"text\" }, slot: { type: \"slot\" } },\n    },\n    components: {\n      Comp: {\n        fields: {\n          prop: { type: \"text\" },\n          slotA: { type: \"slot\" },\n          slotB: { type: \"slot\" },\n          array: { type: \"array\", arrayFields: { slot: { type: \"slot\" } } },\n          object: { type: \"object\", objectFields: { slot: { type: \"slot\" } } },\n        },\n        defaultProps: {\n          prop: \"example\",\n          slotA: [],\n          slotB: [],\n          array: [],\n          object: { slot: [] },\n        },\n        render: () => <div />,\n      },\n    },\n  };\n\n  const expectIndexed = (\n    state: PrivateAppState,\n    item: ComponentData | undefined,\n    path: string[],\n    index: number\n  ) => {\n    if (!item) return;\n\n    const zoneCompound = path[path.length - 1];\n\n    expect(state.indexes.zones[zoneCompound]?.contentIds[index]).toEqual(\n      item.props.id\n    );\n    expect(state.indexes.nodes[item.props.id].data).toEqual(item);\n    expect(state.indexes.nodes[item.props.id].flatData).toEqual(\n      flattenNode(item, config)\n    );\n    expect(state.indexes.nodes[item.props.id].path).toEqual(path);\n  };\n\n  beforeEach(() => {\n    appStore.setState(\n      {\n        ...appStore.getInitialState(),\n        config,\n      },\n      true\n    );\n  });\n\n  it(\"should generate the correct index for a given payload\", () => {\n    const state: PrivateAppState = walkAppState(\n      {\n        ...defaultState,\n        data: {\n          ...defaultData,\n          content: [\n            {\n              type: \"Comp\",\n              props: { id: \"my-component\", prop: \"Data\" },\n            },\n          ],\n          zones: {\n            \"my-component:zone\": [\n              {\n                type: \"Comp\",\n                props: { id: \"other-component\", prop: \"More example data\" },\n              },\n            ],\n            \"other-component:zone\": [\n              {\n                type: \"Comp\",\n                props: {\n                  id: \"another-id\",\n                  prop: \"Even more example data\",\n                  slotA: [\n                    {\n                      type: \"Comp\",\n                      props: { id: \"slotted-a-id\", prop: \"Inside a slot\" },\n                    },\n                  ],\n                  slotB: [\n                    {\n                      type: \"Comp\",\n                      props: { id: \"slotted-b-id\", prop: \"Inside a slot\" },\n                    },\n                  ],\n                  array: [\n                    {\n                      slot: [\n                        {\n                          type: \"Comp\",\n                          props: {\n                            id: \"array-slotted-a-id\",\n                            prop: \"Inside a slot, inside an array\",\n                          },\n                        },\n                      ],\n                    },\n                  ],\n                  object: {\n                    slot: [\n                      {\n                        type: \"Comp\",\n                        props: {\n                          id: \"object-slotted-a-id\",\n                          prop: \"Inside a slot, inside an object\",\n                        },\n                      },\n                    ],\n                  },\n                },\n              },\n            ],\n          },\n        },\n      },\n      config\n    );\n\n    expectIndexed(state, state.data.content[0], [rootDroppableId], 0);\n\n    expectIndexed(\n      state,\n      state.data.zones?.[\"my-component:zone\"][0],\n      [rootDroppableId, \"my-component:zone\"],\n      0\n    );\n\n    expectIndexed(\n      state,\n      state.data.zones?.[\"other-component:zone\"][0],\n      [rootDroppableId, \"my-component:zone\", \"other-component:zone\"],\n      0\n    );\n\n    expectIndexed(\n      state,\n      state.data.zones?.[\"other-component:zone\"][0].props.slotA[0],\n      [\n        rootDroppableId,\n        \"my-component:zone\",\n        \"other-component:zone\",\n        \"another-id:slotA\",\n      ],\n      0\n    );\n\n    expectIndexed(\n      state,\n      state.data.zones?.[\"other-component:zone\"][0].props.slotB[0],\n      [\n        rootDroppableId,\n        \"my-component:zone\",\n        \"other-component:zone\",\n        \"another-id:slotB\",\n      ],\n      0\n    );\n\n    expectIndexed(\n      state,\n      state.data.zones?.[\"other-component:zone\"][0].props.array[0].slot[0],\n      [\n        rootDroppableId,\n        \"my-component:zone\",\n        \"other-component:zone\",\n        \"another-id:array[0].slot\",\n      ],\n      0\n    );\n\n    expectIndexed(\n      state,\n      state.data.zones?.[\"other-component:zone\"][0].props.object.slot[0],\n      [\n        rootDroppableId,\n        \"my-component:zone\",\n        \"other-component:zone\",\n        \"another-id:object.slot\",\n      ],\n      0\n    );\n\n    expect(state.indexes).toMatchInlineSnapshot(`\n      {\n        \"nodes\": {\n          \"another-id\": {\n            \"data\": {\n              \"props\": {\n                \"array\": [\n                  {\n                    \"slot\": [\n                      {\n                        \"props\": {\n                          \"id\": \"array-slotted-a-id\",\n                          \"prop\": \"Inside a slot, inside an array\",\n                          \"slotA\": [],\n                          \"slotB\": [],\n                        },\n                        \"type\": \"Comp\",\n                      },\n                    ],\n                  },\n                ],\n                \"id\": \"another-id\",\n                \"object\": {\n                  \"slot\": [\n                    {\n                      \"props\": {\n                        \"id\": \"object-slotted-a-id\",\n                        \"prop\": \"Inside a slot, inside an object\",\n                        \"slotA\": [],\n                        \"slotB\": [],\n                      },\n                      \"type\": \"Comp\",\n                    },\n                  ],\n                },\n                \"prop\": \"Even more example data\",\n                \"slotA\": [\n                  {\n                    \"props\": {\n                      \"id\": \"slotted-a-id\",\n                      \"prop\": \"Inside a slot\",\n                      \"slotA\": [],\n                      \"slotB\": [],\n                    },\n                    \"type\": \"Comp\",\n                  },\n                ],\n                \"slotB\": [\n                  {\n                    \"props\": {\n                      \"id\": \"slotted-b-id\",\n                      \"prop\": \"Inside a slot\",\n                      \"slotA\": [],\n                      \"slotB\": [],\n                    },\n                    \"type\": \"Comp\",\n                  },\n                ],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"array.0.slot\": null,\n                \"id\": \"another-id\",\n                \"object.slot\": null,\n                \"prop\": \"Even more example data\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"other-component\",\n            \"path\": [\n              \"root:default-zone\",\n              \"my-component:zone\",\n              \"other-component:zone\",\n            ],\n            \"zone\": \"zone\",\n          },\n          \"array-slotted-a-id\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"array-slotted-a-id\",\n                \"prop\": \"Inside a slot, inside an array\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"array-slotted-a-id\",\n                \"prop\": \"Inside a slot, inside an array\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"another-id\",\n            \"path\": [\n              \"root:default-zone\",\n              \"my-component:zone\",\n              \"other-component:zone\",\n              \"another-id:array[0].slot\",\n            ],\n            \"zone\": \"array[0].slot\",\n          },\n          \"my-component\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"my-component\",\n                \"prop\": \"Data\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"my-component\",\n                \"prop\": \"Data\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"root\",\n            \"path\": [\n              \"root:default-zone\",\n            ],\n            \"zone\": \"default-zone\",\n          },\n          \"object-slotted-a-id\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"object-slotted-a-id\",\n                \"prop\": \"Inside a slot, inside an object\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"object-slotted-a-id\",\n                \"prop\": \"Inside a slot, inside an object\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"another-id\",\n            \"path\": [\n              \"root:default-zone\",\n              \"my-component:zone\",\n              \"other-component:zone\",\n              \"another-id:object.slot\",\n            ],\n            \"zone\": \"object.slot\",\n          },\n          \"other-component\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"other-component\",\n                \"prop\": \"More example data\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"other-component\",\n                \"prop\": \"More example data\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"my-component\",\n            \"path\": [\n              \"root:default-zone\",\n              \"my-component:zone\",\n            ],\n            \"zone\": \"zone\",\n          },\n          \"root\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"root\",\n                \"slot\": [],\n                \"title\": \"\",\n              },\n              \"type\": \"root\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"root\",\n                \"slot\": null,\n                \"title\": \"\",\n              },\n              \"type\": \"root\",\n            },\n            \"parentId\": null,\n            \"path\": [],\n            \"zone\": \"\",\n          },\n          \"slotted-a-id\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"slotted-a-id\",\n                \"prop\": \"Inside a slot\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"slotted-a-id\",\n                \"prop\": \"Inside a slot\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"another-id\",\n            \"path\": [\n              \"root:default-zone\",\n              \"my-component:zone\",\n              \"other-component:zone\",\n              \"another-id:slotA\",\n            ],\n            \"zone\": \"slotA\",\n          },\n          \"slotted-b-id\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"slotted-b-id\",\n                \"prop\": \"Inside a slot\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"slotted-b-id\",\n                \"prop\": \"Inside a slot\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"another-id\",\n            \"path\": [\n              \"root:default-zone\",\n              \"my-component:zone\",\n              \"other-component:zone\",\n              \"another-id:slotB\",\n            ],\n            \"zone\": \"slotB\",\n          },\n        },\n        \"zones\": {\n          \"another-id:array[0].slot\": {\n            \"contentIds\": [\n              \"array-slotted-a-id\",\n            ],\n            \"type\": \"slot\",\n          },\n          \"another-id:object.slot\": {\n            \"contentIds\": [\n              \"object-slotted-a-id\",\n            ],\n            \"type\": \"slot\",\n          },\n          \"another-id:slotA\": {\n            \"contentIds\": [\n              \"slotted-a-id\",\n            ],\n            \"type\": \"slot\",\n          },\n          \"another-id:slotB\": {\n            \"contentIds\": [\n              \"slotted-b-id\",\n            ],\n            \"type\": \"slot\",\n          },\n          \"array-slotted-a-id:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"array-slotted-a-id:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"my-component:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"my-component:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"my-component:zone\": {\n            \"contentIds\": [\n              \"other-component\",\n            ],\n            \"type\": \"dropzone\",\n          },\n          \"my-component:zone1\": {\n            \"contentIds\": [],\n            \"type\": \"dropzone\",\n          },\n          \"object-slotted-a-id:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"object-slotted-a-id:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"other-component:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"other-component:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"other-component:zone\": {\n            \"contentIds\": [\n              \"another-id\",\n            ],\n            \"type\": \"dropzone\",\n          },\n          \"root:default-zone\": {\n            \"contentIds\": [\n              \"my-component\",\n            ],\n            \"type\": \"root\",\n          },\n          \"root:slot\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"slotted-a-id:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"slotted-a-id:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"slotted-b-id:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"slotted-b-id:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n        },\n      }\n    `);\n  });\n\n  it(\"should default values for any undefined slots\", () => {\n    const state: PrivateAppState = walkAppState(\n      {\n        ...defaultState,\n        data: {\n          ...defaultData,\n          content: [\n            {\n              type: \"Comp\",\n              props: {\n                id: \"another-id\",\n                prop: \"Even more example data\",\n              },\n            },\n          ],\n          zones: {},\n        },\n      },\n      config\n    );\n\n    expect(state.indexes).toMatchInlineSnapshot(`\n      {\n        \"nodes\": {\n          \"another-id\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"another-id\",\n                \"prop\": \"Even more example data\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"another-id\",\n                \"prop\": \"Even more example data\",\n                \"slotA\": null,\n                \"slotB\": null,\n              },\n              \"type\": \"Comp\",\n            },\n            \"parentId\": \"root\",\n            \"path\": [\n              \"root:default-zone\",\n            ],\n            \"zone\": \"default-zone\",\n          },\n          \"root\": {\n            \"data\": {\n              \"props\": {\n                \"id\": \"root\",\n                \"slot\": [],\n                \"title\": \"\",\n              },\n              \"type\": \"root\",\n            },\n            \"flatData\": {\n              \"props\": {\n                \"id\": \"root\",\n                \"slot\": null,\n                \"title\": \"\",\n              },\n              \"type\": \"root\",\n            },\n            \"parentId\": null,\n            \"path\": [],\n            \"zone\": \"\",\n          },\n        },\n        \"zones\": {\n          \"another-id:slotA\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"another-id:slotB\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n          \"my-component:zone1\": {\n            \"contentIds\": [],\n            \"type\": \"dropzone\",\n          },\n          \"root:default-zone\": {\n            \"contentIds\": [\n              \"another-id\",\n            ],\n            \"type\": \"root\",\n          },\n          \"root:slot\": {\n            \"contentIds\": [],\n            \"type\": \"slot\",\n          },\n        },\n      }\n    `);\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/data/__tests__/walk-tree.spec.tsx",
    "content": "import { Config, Data, Slot } from \"../../../types\";\n\nimport { defaultAppState as _defaultAppState } from \"../../../store\";\n\nimport { walkTree } from \"../walk-tree\";\n\ntype Props = {\n  Comp: {\n    prop: string;\n    slotA?: Slot;\n    slotB?: Slot;\n  };\n};\n\ntype RootProps = {\n  title: string;\n  slot: Slot;\n};\n\ntype UserConfig = Config<Props, RootProps>;\ntype UserData = Data<Props, RootProps>;\n\nconst testData: UserData = {\n  root: { props: { title: \"\", slot: [] } },\n  content: [\n    {\n      type: \"Comp\",\n      props: { id: \"my-component\", prop: \"Data\", slotA: [], slotB: [] },\n    },\n  ],\n  zones: {\n    \"my-component:zone\": [\n      {\n        type: \"Comp\",\n        props: {\n          id: \"other-component\",\n          prop: \"More example data\",\n          slotA: [],\n          slotB: [],\n        },\n      },\n    ],\n    \"other-component:zone\": [\n      {\n        type: \"Comp\",\n        props: {\n          id: \"another-id\",\n          prop: \"Even more example data\",\n          slotA: [\n            {\n              type: \"Comp\",\n              props: {\n                id: \"slotted-a-id\",\n                prop: \"Inside a slot\",\n                slotA: [],\n                slotB: [],\n              },\n            },\n          ],\n          slotB: [\n            {\n              type: \"Comp\",\n              props: {\n                id: \"slotted-b-id\",\n                prop: \"Inside a slot\",\n                slotA: [],\n                slotB: [],\n              },\n            },\n          ],\n        },\n      },\n    ],\n  },\n};\n\ndescribe(\"walk-tree\", () => {\n  const config: UserConfig = {\n    root: {\n      fields: { title: { type: \"text\" }, slot: { type: \"slot\" } },\n    },\n    components: {\n      Comp: {\n        fields: {\n          prop: { type: \"text\" },\n          slotA: { type: \"slot\" },\n          slotB: { type: \"slot\" },\n        },\n        defaultProps: { prop: \"example\", slotA: [], slotB: [] },\n        render: () => <div />,\n      },\n    },\n  };\n\n  it(\"should trigger a function for each slot for a given Data\", () => {\n    const mockMap = jest.fn();\n\n    const data = walkTree(testData, config, mockMap);\n\n    expect(data).toEqual(testData);\n\n    expect(mockMap).toHaveBeenCalledWith([], {\n      parentId: \"root\",\n      propName: \"slot\",\n    });\n\n    expect(mockMap).toHaveBeenCalledWith(\n      [\n        {\n          props: {\n            id: \"slotted-a-id\",\n            prop: \"Inside a slot\",\n            slotA: [],\n            slotB: [],\n          },\n          type: \"Comp\",\n        },\n      ],\n      { parentId: \"another-id\", propName: \"slotA\" }\n    );\n\n    expect(mockMap).toHaveBeenCalledWith(\n      [\n        {\n          props: {\n            id: \"slotted-b-id\",\n            prop: \"Inside a slot\",\n            slotA: [],\n            slotB: [],\n          },\n          type: \"Comp\",\n        },\n      ],\n      { parentId: \"another-id\", propName: \"slotB\" }\n    );\n  });\n\n  it(\"should trigger a function for each slot for a given ComponentData\", () => {\n    const mockMap = jest.fn();\n\n    const zones = testData.zones ?? {};\n    const item = zones[\"other-component:zone\"][0];\n\n    const data = walkTree(item, config, mockMap);\n\n    expect(data).toEqual(item);\n\n    expect(mockMap).toHaveBeenCalledWith(\n      [\n        {\n          props: {\n            id: \"slotted-a-id\",\n            prop: \"Inside a slot\",\n            slotA: [],\n            slotB: [],\n          },\n          type: \"Comp\",\n        },\n      ],\n      { parentId: \"another-id\", propName: \"slotA\" }\n    );\n\n    expect(mockMap).toHaveBeenCalledWith(\n      [\n        {\n          props: {\n            id: \"slotted-b-id\",\n            prop: \"Inside a slot\",\n            slotA: [],\n            slotB: [],\n          },\n          type: \"Comp\",\n        },\n      ],\n      { parentId: \"another-id\", propName: \"slotB\" }\n    );\n  });\n\n  it(\"should map each item for each slot for a given Data\", () => {\n    const data = walkTree(testData, config, (content) =>\n      content.map((item) => ({\n        ...item,\n        props: { ...item.props, example: \"Hello, world\" },\n      }))\n    );\n\n    expect(data).toMatchInlineSnapshot(`\n      {\n        \"content\": [\n          {\n            \"props\": {\n              \"example\": \"Hello, world\",\n              \"id\": \"my-component\",\n              \"prop\": \"Data\",\n              \"slotA\": [],\n              \"slotB\": [],\n            },\n            \"type\": \"Comp\",\n          },\n        ],\n        \"root\": {\n          \"props\": {\n            \"slot\": [],\n            \"title\": \"\",\n          },\n        },\n        \"zones\": {\n          \"my-component:zone\": [\n            {\n              \"props\": {\n                \"id\": \"other-component\",\n                \"prop\": \"More example data\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n          ],\n          \"other-component:zone\": [\n            {\n              \"props\": {\n                \"id\": \"another-id\",\n                \"prop\": \"Even more example data\",\n                \"slotA\": [\n                  {\n                    \"props\": {\n                      \"example\": \"Hello, world\",\n                      \"id\": \"slotted-a-id\",\n                      \"prop\": \"Inside a slot\",\n                      \"slotA\": [],\n                      \"slotB\": [],\n                    },\n                    \"type\": \"Comp\",\n                  },\n                ],\n                \"slotB\": [\n                  {\n                    \"props\": {\n                      \"example\": \"Hello, world\",\n                      \"id\": \"slotted-b-id\",\n                      \"prop\": \"Inside a slot\",\n                      \"slotA\": [],\n                      \"slotB\": [],\n                    },\n                    \"type\": \"Comp\",\n                  },\n                ],\n              },\n              \"type\": \"Comp\",\n            },\n          ],\n        },\n      }\n    `);\n  });\n\n  it(\"should map each item for each slot for a given ComponentData\", () => {\n    const zones = testData.zones ?? {};\n    const item = zones[\"other-component:zone\"][0];\n\n    const data = walkTree(item, config, (content) =>\n      content.map((item) => ({\n        ...item,\n        props: { ...item.props, example: \"Hello, world\" },\n      }))\n    );\n\n    expect(data).toMatchInlineSnapshot(`\n      {\n        \"props\": {\n          \"id\": \"another-id\",\n          \"prop\": \"Even more example data\",\n          \"slotA\": [\n            {\n              \"props\": {\n                \"example\": \"Hello, world\",\n                \"id\": \"slotted-a-id\",\n                \"prop\": \"Inside a slot\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n          ],\n          \"slotB\": [\n            {\n              \"props\": {\n                \"example\": \"Hello, world\",\n                \"id\": \"slotted-b-id\",\n                \"prop\": \"Inside a slot\",\n                \"slotA\": [],\n                \"slotB\": [],\n              },\n              \"type\": \"Comp\",\n            },\n          ],\n        },\n        \"type\": \"Comp\",\n      }\n    `);\n  });\n\n  it(\"should trigger a function for each array slot for a given Data\", () => {\n    const config: Config = {\n      components: {\n        Comp: {\n          fields: {\n            prop: { type: \"text\" },\n            array: {\n              type: \"array\",\n              arrayFields: { arraySlot: { type: \"slot\" } },\n            },\n          },\n          render: () => <div />,\n        },\n      },\n    };\n\n    const testData: Data = {\n      root: { props: {} },\n      content: [\n        {\n          type: \"Comp\",\n          props: {\n            id: \"my-component\",\n            array: [\n              {\n                arraySlot: [\n                  {\n                    type: \"Comp\",\n                    props: {\n                      id: \"slotted-array-id\",\n                      prop: \"Inside array slot\",\n                      slotA: [],\n                      slotB: [],\n                    },\n                  },\n                ],\n              },\n            ],\n          },\n        },\n      ],\n      zones: {},\n    };\n\n    const mockMap = jest.fn();\n\n    const data = walkTree(testData, config, mockMap);\n\n    expect(data).toEqual(testData);\n\n    expect(mockMap).toHaveBeenCalledWith(\n      [\n        {\n          props: {\n            id: \"slotted-array-id\",\n            prop: \"Inside array slot\",\n            slotA: [],\n            slotB: [],\n          },\n          type: \"Comp\",\n        },\n      ],\n      { parentId: \"my-component\", propName: \"array[0].arraySlot\" }\n    );\n  });\n});\n"
  },
  {
    "path": "packages/core/lib/data/default-data.ts",
    "content": "import { Data } from \"../../types\";\n\nexport const defaultData = (data: Partial<Data>): Data => ({\n  ...data,\n  root: data.root || {},\n  content: data.content || [],\n});\n"
  },
  {
    "path": "packages/core/lib/data/default-slots.ts",
    "content": "import { Fields } from \"../../types\";\n\nexport const defaultSlots = (value: object, fields: Fields) =>\n  Object.keys(fields).reduce(\n    (acc, fieldName) =>\n      fields[fieldName].type === \"slot\" ? { [fieldName]: [], ...acc } : acc,\n    value\n  );\n"
  },
  {
    "path": "packages/core/lib/data/find-zones-for-area.ts",
    "content": "import { PrivateAppState } from \"../../types/Internal\";\n\nexport const findZonesForArea = (state: PrivateAppState, area: string) => {\n  return Object.keys(state.indexes.zones).filter(\n    (zone) => zone.split(\":\")[0] === area\n  );\n};\n"
  },
  {
    "path": "packages/core/lib/data/flatten-data.ts",
    "content": "import { ComponentData, Config, UserGenerics } from \"../../types\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"./walk-app-state\";\n\nexport const flattenData = <\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>(\n  state: PrivateAppState,\n  config: UserConfig\n) => {\n  const data: ComponentData[] = [];\n\n  walkAppState(\n    state,\n    config,\n    (content) => content,\n    (item) => {\n      data.push(item);\n      return item;\n    }\n  );\n\n  return data;\n};\n"
  },
  {
    "path": "packages/core/lib/data/flatten-node.ts",
    "content": "import flat from \"flat\";\nimport { ComponentData, Config, RootData, UserGenerics } from \"../../types\";\nimport { stripSlots } from \"./strip-slots\";\n\n// Explicitly destructure to account for flat module issues: https://github.com/puckeditor/puck/issues/1089\nconst { flatten, unflatten } = flat;\n\nconst isPureObject = (val: any) =>\n  val != null && Object.prototype.toString.call(val) === \"[object Object]\";\n\nconst emptyArrayStr = \"__puck_[]\";\nconst emptyObjectStr = \"__puck_{}\";\n\nfunction encodeEmptyObjects(props: Record<string, any> = {}) {\n  const result: Record<string, any> = {};\n\n  for (const key in props) {\n    if (!Object.prototype.hasOwnProperty.call(props, key)) continue;\n\n    const val = props[key];\n\n    if (Array.isArray(val) && val.length === 0) {\n      result[key] = emptyArrayStr;\n    } else if (isPureObject(val) && Object.keys(val).length === 0) {\n      result[key] = emptyObjectStr;\n    } else {\n      result[key] = val;\n    }\n  }\n\n  return result;\n}\n\nfunction decodeEmptyObjects(props: Record<string, any> = {}) {\n  const result: Record<string, any> = {};\n\n  for (const key in props) {\n    if (!Object.prototype.hasOwnProperty.call(props, key)) continue;\n\n    const val = props[key];\n\n    if (val === emptyArrayStr) {\n      result[key] = [];\n    } else if (val === emptyObjectStr) {\n      result[key] = {};\n    } else {\n      result[key] = val;\n    }\n  }\n\n  return result;\n}\n\nexport const flattenNode = <\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>(\n  node: ComponentData | RootData,\n  config: UserConfig\n) => {\n  return {\n    ...node,\n    props: encodeEmptyObjects(flatten(stripSlots(node, config).props)),\n  };\n};\n\nexport const expandNode = (node: ComponentData | RootData) => {\n  const props = unflatten(decodeEmptyObjects(node.props));\n\n  return {\n    ...node,\n    props,\n  };\n};\n"
  },
  {
    "path": "packages/core/lib/data/for-related-zones.ts",
    "content": "import { Content, Data } from \"../../types\";\nimport { getZoneId } from \"../get-zone-id\";\n\nexport function forRelatedZones<UserData extends Data>(\n  item: UserData[\"content\"][0],\n  data: UserData,\n  cb: (path: string[], zoneCompound: string, content: Content) => void,\n  path: string[] = []\n) {\n  Object.entries(data.zones || {}).forEach(([zoneCompound, content]) => {\n    const [parentId] = getZoneId(zoneCompound);\n\n    if (parentId === item.props.id) {\n      cb(path, zoneCompound, content);\n    }\n  });\n}\n"
  },
  {
    "path": "packages/core/lib/data/get-deep.ts",
    "content": "export const getDeep = (node: Record<string, any>, path: string): any => {\n  const pathParts = path.split(\".\");\n\n  return pathParts.reduce((acc, item) => {\n    if (!acc) return;\n\n    const [prop, indexStr] = item.replace(\"]\", \"\").split(\"[\");\n\n    const val = acc[prop];\n\n    if (indexStr && val) {\n      return val[parseInt(indexStr)];\n    }\n\n    return val;\n  }, node);\n};\n"
  },
  {
    "path": "packages/core/lib/data/get-ids-for-parent.ts",
    "content": "import { PrivateAppState } from \"../../types/Internal\";\n\nexport const getIdsForParent = (\n  zoneCompound: string,\n  state: PrivateAppState\n) => {\n  const [parentId] = zoneCompound.split(\":\");\n  const node = state.indexes.nodes[parentId];\n\n  return (node?.path || []).map((p) => p.split(\":\")[0]);\n};\n"
  },
  {
    "path": "packages/core/lib/data/get-item.ts",
    "content": "import { Data } from \"../../types\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { rootDroppableId } from \"../root-droppable-id\";\n\nexport type ItemSelector = {\n  index: number;\n  zone?: string;\n};\n\nexport function getItem<UserData extends Data>(\n  selector: ItemSelector,\n  state: PrivateAppState\n): UserData[\"content\"][0] | undefined {\n  const zone = state.indexes.zones?.[selector.zone || rootDroppableId];\n\n  return zone\n    ? state.indexes.nodes[zone.contentIds[selector.index]]?.data\n    : undefined;\n}\n"
  },
  {
    "path": "packages/core/lib/data/insert.ts",
    "content": "export const insert = (list: any[], index: number, item: any) => {\n  const result = Array.from(list || []);\n  result.splice(index, 0, item);\n\n  return result;\n};\n"
  },
  {
    "path": "packages/core/lib/data/make-state-public.ts",
    "content": "import { AppState, Data } from \"../../types\";\nimport { PrivateAppState } from \"../../types/Internal\";\n\nexport const makeStatePublic = <UserData extends Data>(\n  state: PrivateAppState<UserData>\n): AppState<UserData> => {\n  const { data, ui } = state;\n\n  return { data, ui };\n};\n"
  },
  {
    "path": "packages/core/lib/data/map-fields.ts",
    "content": "import {\n  ComponentData,\n  Config,\n  Content,\n  Field,\n  Fields,\n  RootData,\n} from \"../../types\";\nimport { defaultSlots } from \"./default-slots\";\n\nexport type MapFnParams<ThisField = Field> = {\n  value: any;\n  parentId: string;\n  propName: string;\n  field: ThisField;\n  propPath: string;\n};\n\ntype MapFn<T = any> = (params: MapFnParams) => T;\n\nexport type Mappers<T = EitherMapFn> = Partial<Record<Field[\"type\"], T>>;\n\ntype PromiseMapFn = MapFn<Promise<any>>;\n\ntype EitherMapFn = MapFn<any | Promise<any>>;\n\ntype WalkFieldOpts = {\n  value: unknown;\n  fields: Fields;\n  mappers: Mappers;\n  propKey?: string;\n  propPath?: string;\n  id?: string;\n  config: Config;\n  recurseSlots?: boolean;\n};\n\ntype WalkObjectOpts = {\n  value: Record<string, any>;\n  fields: Fields;\n  mappers: Mappers;\n  id: string;\n  getPropPath: (str: string) => string;\n  config: Config;\n  recurseSlots?: boolean;\n};\n\nconst isPromise = <T = unknown>(v: any): v is Promise<T> =>\n  !!v && typeof v.then === \"function\";\n\nconst flatten = (values: Record<string, any>[]) =>\n  values.reduce((acc, item) => ({ ...acc, ...item }), {});\n\nconst containsPromise = (arr: any[]) => arr.some(isPromise);\n\nexport const walkField = ({\n  value,\n  fields,\n  mappers,\n  propKey = \"\",\n  propPath = \"\",\n  id = \"\",\n  config,\n  recurseSlots = false,\n}: WalkFieldOpts): any | Promise<any> => {\n  const fieldType = fields[propKey]?.type;\n  const map = mappers[fieldType];\n\n  if (map && fieldType === \"slot\") {\n    const content = (value as Content) || [];\n\n    const mappedContent = recurseSlots\n      ? content.map((el) => {\n          const componentConfig = config.components[el.type];\n\n          if (!componentConfig) {\n            throw new Error(`Could not find component config for ${el.type}`);\n          }\n\n          const fields = componentConfig.fields ?? {};\n\n          return walkField({\n            value: { ...el, props: defaultSlots(el.props, fields) },\n            fields,\n            mappers,\n            id: el.props.id,\n            config,\n            recurseSlots,\n          });\n        })\n      : content;\n\n    if (containsPromise(mappedContent)) {\n      return Promise.all(mappedContent);\n    }\n\n    return map({\n      value: mappedContent,\n      parentId: id,\n      propName: propPath,\n      field: fields[propKey],\n      propPath,\n    });\n  } else if (map && fields[propKey]) {\n    return map({\n      value,\n      parentId: id,\n      propName: propKey,\n      field: fields[propKey],\n      propPath,\n    });\n  }\n\n  if (value && typeof value === \"object\") {\n    if (Array.isArray(value)) {\n      const arrayFields =\n        fields[propKey]?.type === \"array\" ? fields[propKey].arrayFields : null;\n\n      if (!arrayFields) return value;\n\n      const newValue = value.map((el, idx) =>\n        walkField({\n          value: el,\n          fields: arrayFields,\n          mappers,\n          propKey,\n          propPath: `${propPath}[${idx}]`,\n          id,\n          config,\n          recurseSlots,\n        })\n      );\n\n      if (containsPromise(newValue)) {\n        return Promise.all(newValue);\n      }\n\n      return newValue;\n    } else if (\"$$typeof\" in value) {\n      return value;\n    } else {\n      const objectFields =\n        fields[propKey]?.type === \"object\"\n          ? fields[propKey].objectFields\n          : fields;\n\n      return walkObject({\n        value,\n        fields: objectFields,\n        mappers,\n        id,\n        getPropPath: (k) => `${propPath}.${k}`,\n        config,\n        recurseSlots,\n      });\n    }\n  }\n\n  return value;\n};\n\nconst walkObject = ({\n  value,\n  fields,\n  mappers,\n  id,\n  getPropPath,\n  config,\n  recurseSlots,\n}: WalkObjectOpts): Record<string, any> => {\n  const newProps = Object.entries(value).map(([k, v]) => {\n    const opts: WalkFieldOpts = {\n      value: v,\n      fields,\n      mappers,\n      propKey: k,\n      propPath: getPropPath(k),\n      id,\n      config,\n      recurseSlots,\n    };\n\n    const newValue = walkField(opts);\n\n    if (isPromise(newValue)) {\n      return newValue.then((resolvedValue: any) => ({\n        [k]: resolvedValue,\n      }));\n    }\n\n    return {\n      [k]: newValue,\n    };\n  }, {});\n\n  if (containsPromise(newProps)) {\n    return Promise.all(newProps).then(flatten);\n  }\n\n  return flatten(newProps);\n};\n\nexport function mapFields<T extends ComponentData | RootData>(\n  item: T,\n  mappers: Mappers<MapFn>,\n  config: Config,\n  recurseSlots?: boolean,\n  shouldDefaultSlots?: boolean\n): T;\n\nexport function mapFields<T extends ComponentData | RootData>(\n  item: T,\n  mappers: Mappers<PromiseMapFn>,\n  config: Config,\n  recurseSlots?: boolean,\n  shouldDefaultSlots?: boolean\n): Promise<T>;\n\nexport function mapFields(\n  item: any,\n  mappers: Mappers,\n  config: Config,\n  recurseSlots: boolean = false,\n  shouldDefaultSlots: boolean = true\n): any {\n  const itemType = \"type\" in item ? item.type : \"root\";\n\n  const componentConfig =\n    itemType === \"root\" ? config.root : config.components?.[itemType];\n\n  const newProps = walkObject({\n    value: shouldDefaultSlots\n      ? defaultSlots(item.props ?? {}, componentConfig?.fields ?? {})\n      : item.props,\n    fields: componentConfig?.fields ?? {},\n    mappers,\n    id: item.props ? item.props.id ?? \"root\" : \"root\",\n    getPropPath: (k) => k,\n    config,\n    recurseSlots,\n  });\n\n  if (isPromise(newProps)) {\n    return newProps.then((resolvedProps) => ({\n      ...item,\n      props: resolvedProps,\n    }));\n  }\n\n  return {\n    ...item,\n    props: newProps,\n  };\n}\n"
  },
  {
    "path": "packages/core/lib/data/populate-ids.ts",
    "content": "import { ComponentData, ComponentDataOptionalId, Config } from \"../../types\";\nimport { generateId } from \"../generate-id\";\nimport { walkTree } from \"./walk-tree\";\n\nexport const populateIds = (\n  data: ComponentData,\n  config: Config,\n  override: boolean = false\n): ComponentData => {\n  const id = generateId(data.type);\n\n  return walkTree(\n    {\n      ...data,\n      props: override ? { ...data.props, id } : { ...data.props },\n    },\n    config,\n    (contents) =>\n      contents.map((item: ComponentDataOptionalId) => {\n        const id = generateId(item.type);\n\n        return {\n          ...item,\n          props: override ? { ...item.props, id } : { id, ...item.props },\n        };\n      })\n  );\n};\n"
  },
  {
    "path": "packages/core/lib/data/remove.ts",
    "content": "export const remove = (list: any[], index: number) => {\n  const result = Array.from(list);\n  result.splice(index, 1);\n\n  return result;\n};\n"
  },
  {
    "path": "packages/core/lib/data/reorder.ts",
    "content": "export const reorder = (list: any[], startIndex: number, endIndex: number) => {\n  const result = Array.from(list);\n  const [removed] = result.splice(startIndex, 1);\n  result.splice(endIndex, 0, removed);\n\n  return result;\n};\n"
  },
  {
    "path": "packages/core/lib/data/replace.ts",
    "content": "export const replace = <T>(list: T[], index: number, newItem: T) => {\n  const result = Array.from(list);\n  result.splice(index, 1);\n  result.splice(index, 0, newItem);\n\n  return result;\n};\n"
  },
  {
    "path": "packages/core/lib/data/resolve-and-replace-data.ts",
    "content": "import { useAppStoreApi } from \"../../store\";\nimport { ComponentData, ResolveDataTrigger } from \"../../types\";\nimport { getSelectorForId } from \"../get-selector-for-id\";\nimport { toComponent } from \"./to-component\";\n\nexport async function resolveAndReplaceData(\n  currentData: ComponentData,\n  getState: ReturnType<typeof useAppStoreApi>[\"getState\"],\n  trigger: ResolveDataTrigger = \"force\"\n) {\n  const resolvedResult = await getState().resolveComponentData(\n    currentData,\n    trigger\n  );\n  if (!resolvedResult.didChange) return;\n\n  const itemSelector = getSelectorForId(\n    getState().state,\n    resolvedResult.node.props.id\n  );\n  if (!itemSelector) {\n    console.warn(\n      `Warning: Could not find component with id \"${currentData.props.id}\" to resolve its data. Component may have been removed or the id is invalid.`\n    );\n    return;\n  }\n\n  getState().dispatch({\n    type: \"replace\",\n    data: toComponent(resolvedResult.node),\n    destinationIndex: itemSelector.index,\n    destinationZone: itemSelector.zone,\n  });\n}\n"
  },
  {
    "path": "packages/core/lib/data/resolve-data-by-id.ts",
    "content": "import { useAppStoreApi } from \"../../store\";\nimport { ResolveDataTrigger } from \"../../types\";\nimport { PuckNodeData } from \"../../types/Internal\";\nimport { resolveAndReplaceData } from \"./resolve-and-replace-data\";\n\nexport async function resolveDataById(\n  id: string,\n  getState: ReturnType<typeof useAppStoreApi>[\"getState\"],\n  trigger?: ResolveDataTrigger\n) {\n  const node: PuckNodeData | undefined = getState().state.indexes.nodes[id];\n\n  if (!node) {\n    console.warn(\n      `Warning: Could not find component with id \"${id}\" to resolve its data. Component may have been removed or the id is invalid.`\n    );\n    return;\n  }\n\n  await resolveAndReplaceData(node.data, getState, trigger);\n}\n"
  },
  {
    "path": "packages/core/lib/data/resolve-data-by-selector.ts",
    "content": "import { useAppStoreApi } from \"../../store\";\nimport { ResolveDataTrigger } from \"../../types\";\nimport { getItem, ItemSelector } from \"./get-item\";\nimport { toComponent } from \"./to-component\";\nimport { resolveAndReplaceData } from \"./resolve-and-replace-data\";\n\nexport async function resolveDataBySelector(\n  selector: ItemSelector,\n  getState: ReturnType<typeof useAppStoreApi>[\"getState\"],\n  trigger?: ResolveDataTrigger\n) {\n  const item = getItem(selector, getState().state);\n\n  if (!item) {\n    console.warn(\n      `Warning: Could not find component for selector \"${JSON.stringify(\n        selector\n      )}\" to resolve its data. Component may have been removed or the selector is invalid.`\n    );\n    return;\n  }\n\n  const itemAsComponent = toComponent(item);\n\n  await resolveAndReplaceData(itemAsComponent, getState, trigger);\n}\n"
  },
  {
    "path": "packages/core/lib/data/set-deep.ts",
    "content": "/**\n * Helper function to set a value based on a dot-notated path\n */\nexport function setDeep<T extends Record<string, any>>(\n  node: T,\n  path: string,\n  newVal: any\n): T {\n  const parts = path.split(\".\");\n  const newNode = { ...node };\n\n  let cur: Record<string, any> = newNode;\n\n  for (let i = 0; i < parts.length; i++) {\n    // Separate the “prop” piece and an optional “[index]” part.\n    const [prop, idxStr] = parts[i].replace(\"]\", \"\").split(\"[\");\n    const isLast = i === parts.length - 1;\n\n    if (idxStr !== undefined) {\n      if (!Array.isArray(cur[prop])) {\n        cur[prop] = [];\n      }\n\n      const idx = Number(idxStr);\n\n      if (isLast) {\n        // We’ve reached the leaf → assign.\n        cur[prop][idx] = newVal;\n        continue;\n      }\n\n      // Ensure the next level container exists.\n      if (cur[prop][idx] === undefined) cur[prop][idx] = {};\n\n      cur = cur[prop][idx];\n\n      continue;\n    }\n\n    if (isLast) {\n      cur[prop] = newVal;\n      continue;\n    }\n\n    if (cur[prop] === undefined) {\n      cur[prop] = {};\n    }\n\n    cur = cur[prop];\n  }\n\n  return { ...node, ...newNode };\n}\n"
  },
  {
    "path": "packages/core/lib/data/setup-zone.ts",
    "content": "import { Data } from \"../../types\";\nimport { rootDroppableId } from \"../root-droppable-id\";\n\n// Force 'zones' to always be present and non-undefined\ntype WithZones<T extends Data> = T & { zones: NonNullable<T[\"zones\"]> };\n\nexport const setupZone = <UserData extends Data>(\n  data: UserData,\n  zoneKey: string\n): Required<WithZones<UserData>> => {\n  if (zoneKey === rootDroppableId) {\n    return data as Required<WithZones<UserData>>;\n  }\n\n  // Preprocess to ensure zones is not undefined\n  const newData = {\n    ...data,\n    zones: data.zones ? { ...data.zones } : {},\n  };\n\n  newData.zones[zoneKey] = newData.zones[zoneKey] || [];\n\n  return newData as Required<WithZones<UserData>>;\n};\n"
  },
  {
    "path": "packages/core/lib/data/strip-slots.ts",
    "content": "import { ComponentData, Config, RootData } from \"../../types\";\nimport { mapFields } from \"./map-fields\";\n\nexport const stripSlots = (\n  data: ComponentData | RootData,\n  config: Config\n): ComponentData | RootData => {\n  // Strip out slots to prevent re-renders of parents when child changes\n  return mapFields(data, { slot: () => null }, config);\n};\n"
  },
  {
    "path": "packages/core/lib/data/to-component.ts",
    "content": "import { ComponentData, RootData } from \"../../types\";\n\nexport const toComponent = (item: ComponentData | RootData): ComponentData => {\n  return \"type\" in item\n    ? (item as ComponentData)\n    : {\n        ...item,\n        props: { ...item.props, id: \"root\" },\n        type: \"root\",\n      };\n};\n"
  },
  {
    "path": "packages/core/lib/data/to-root.ts",
    "content": "import { ComponentData, RootData } from \"../../types\";\n\nexport const toRoot = (item: ComponentData | RootData): RootData => {\n  if (\"type\" in item && item.type !== \"root\") {\n    throw new Error(\"Converting non-root item to root.\");\n  }\n\n  const { readOnly } = item;\n\n  if (item.props) {\n    if (\"id\" in item.props) {\n      const { id, ...props } = item.props;\n\n      return { props, readOnly };\n    }\n\n    return { props: item.props, readOnly };\n  }\n\n  return { props: {}, readOnly };\n};\n"
  },
  {
    "path": "packages/core/lib/data/walk-app-state.ts",
    "content": "import { forRelatedZones } from \"./for-related-zones\";\nimport { rootDroppableId } from \"../root-droppable-id\";\nimport {\n  ComponentData,\n  Config,\n  Content,\n  Data,\n  RootDataWithProps,\n} from \"../../types\";\nimport {\n  NodeIndex,\n  PrivateAppState,\n  ZoneIndex,\n  ZoneType,\n} from \"../../types/Internal\";\nimport { mapFields } from \"./map-fields\";\nimport { flattenNode } from \"./flatten-node\";\nimport { toComponent } from \"./to-component\";\n\n/**\n * Walk the Puck state, generate indexes and make modifications to nodes.\n *\n * @param state The initial state\n * @param mapContent A callback to modify the content of a DropZone or slot. Called for all DropZones, slots and the root content.\n * @param mapNodeOrSkip A callback to modify a node. Called for every node in the tree. Returning a node will cause it to be reindexed. Conversely, returning `null` will skip indexing for that node.\n *\n * @returns The updated state\n */\nexport function walkAppState<UserData extends Data = Data>(\n  state: PrivateAppState<UserData>,\n  config: Config,\n  mapContent: (\n    content: Content,\n    zoneCompound: string,\n    zoneType: ZoneType\n  ) => Content | void = (content) => content,\n  mapNodeOrSkip: (\n    item: ComponentData,\n    path: string[],\n    index: number\n  ) => ComponentData | null = (item) => item\n): PrivateAppState<UserData> {\n  let newZones: Record<string, Content> = {};\n  const newZoneIndex: ZoneIndex = {};\n  const newNodeIndex: NodeIndex = {};\n\n  const processContent = (\n    path: string[],\n    zoneCompound: string,\n    content: Content,\n    zoneType: ZoneType,\n    newId?: string\n  ): [string, Content] => {\n    const [parentId] = zoneCompound.split(\":\");\n    const mappedContent =\n      (mapContent(content, zoneCompound, zoneType) ?? content) || [];\n\n    const [_, zone] = zoneCompound.split(\":\");\n    const newZoneCompound = `${newId || parentId}:${zone}`;\n\n    const newContent = mappedContent.map((zoneChild, index) =>\n      processItem(zoneChild, [...path, newZoneCompound], index)\n    );\n\n    newZoneIndex[newZoneCompound] = {\n      contentIds: newContent.map((item) => item.props.id),\n      type: zoneType,\n    };\n\n    return [newZoneCompound, newContent];\n  };\n\n  const processRelatedZones = (\n    item: ComponentData,\n    newId: string,\n    initialPath: string[]\n  ) => {\n    forRelatedZones(\n      item,\n      state.data,\n      (relatedPath, relatedZoneCompound, relatedContent) => {\n        const [zoneCompound, newContent] = processContent(\n          relatedPath,\n          relatedZoneCompound,\n          relatedContent,\n          \"dropzone\",\n          newId\n        );\n\n        newZones[zoneCompound] = newContent;\n      },\n      initialPath\n    );\n  };\n\n  const processItem = (\n    item: ComponentData,\n    path: string[],\n    index: number\n  ): ComponentData => {\n    const mappedItem = mapNodeOrSkip(item, path, index);\n\n    // Only modify the item if the user has returned it, enabling us to prevent unnecessary mapping and creating new references, which results in re-renders\n    if (!mappedItem) return item;\n\n    const id = mappedItem.props.id;\n\n    const newProps = {\n      ...mapFields(\n        mappedItem,\n        {\n          slot: ({ value, parentId, propPath }) => {\n            const content = value as Content;\n            const zoneCompound = `${parentId}:${propPath}`;\n\n            const [_, newContent] = processContent(\n              path,\n              zoneCompound,\n              content,\n              \"slot\",\n              parentId\n            );\n\n            return newContent;\n          },\n        },\n        config\n      ).props,\n      id,\n    };\n\n    processRelatedZones(item, id, path);\n\n    const newItem = { ...mappedItem, props: newProps };\n\n    const thisZoneCompound = path[path.length - 1];\n    const [parentId, zone] = thisZoneCompound\n      ? thisZoneCompound.split(\":\")\n      : [null, \"\"];\n\n    newNodeIndex[id] = {\n      data: newItem,\n      flatData: flattenNode(newItem, config) as ComponentData,\n      path,\n      parentId,\n      zone,\n    };\n\n    // For now, we strip type and id from root. This may change in future.\n    const finalData: any = { ...newItem, props: { ...newItem.props } };\n\n    if (newProps.id === \"root\") {\n      delete finalData[\"type\"];\n      delete finalData.props[\"id\"];\n    }\n\n    return finalData;\n  };\n\n  const zones = state.data.zones || {};\n\n  const [_, newContent] = processContent(\n    [],\n    rootDroppableId,\n    state.data.content,\n    \"root\"\n  );\n\n  const processedContent = newContent;\n\n  const zonesAlreadyProcessed = Object.keys(newZones);\n\n  Object.keys(zones || {}).forEach((zoneCompound) => {\n    const [parentId] = zoneCompound.split(\":\");\n\n    // Don't reprocess zones already processed as related zones\n    if (zonesAlreadyProcessed.includes(zoneCompound)) {\n      return;\n    }\n\n    const [_, newContent] = processContent(\n      [rootDroppableId],\n      zoneCompound,\n      zones[zoneCompound],\n      \"dropzone\",\n      parentId\n    );\n\n    newZones[zoneCompound] = newContent;\n  }, newZones);\n\n  let rootAsComponent: ComponentData = toComponent({\n    props: { ...(state.data.root.props ?? state.data.root) },\n  });\n\n  if (state.data.root.readOnly) {\n    rootAsComponent.readOnly = state.data.root.readOnly;\n  }\n\n  const processedRoot = processItem(rootAsComponent, [], -1);\n\n  const root = {\n    ...state.data.root,\n    ...processedRoot,\n  } as RootDataWithProps;\n\n  return {\n    ...state,\n    data: {\n      root,\n      content: processedContent,\n      zones: {\n        ...state.data.zones,\n        ...newZones,\n      },\n    } as UserData,\n    indexes: {\n      nodes: { ...state.indexes.nodes, ...newNodeIndex },\n      zones: { ...state.indexes.zones, ...newZoneIndex },\n    },\n  };\n}\n"
  },
  {
    "path": "packages/core/lib/data/walk-tree.ts",
    "content": "import {\n  ComponentData,\n  Config,\n  Content,\n  RootData,\n  UserGenerics,\n} from \"../../types\";\nimport { mapFields } from \"./map-fields\";\n\ntype WalkTreeOptions = {\n  parentId: string;\n  propName: string;\n};\n\nexport function walkTree<\n  T extends ComponentData | RootData | G[\"UserData\"],\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n>(\n  data: T,\n  config: UserConfig,\n  callbackFn: (data: Content, options: WalkTreeOptions) => Content | null | void\n): T {\n  const walkItem = <\n    ItemType extends\n      | G[\"UserComponentData\"]\n      | G[\"UserData\"][\"root\"]\n      | G[\"UserData\"]\n  >(\n    item: ItemType\n  ): ItemType => {\n    return mapFields(\n      item as ComponentData,\n      {\n        slot: ({ value, parentId, propName }) => {\n          const content = value as Content;\n\n          return callbackFn(content, { parentId, propName }) ?? content;\n        },\n      },\n      config,\n      true\n    ) as ItemType;\n  };\n\n  if (\"props\" in data) {\n    return walkItem(data as any) as T;\n  }\n\n  const _data = data as G[\"UserData\"];\n  const zones = _data.zones ?? {};\n\n  const mappedContent = _data.content.map(walkItem) as ComponentData[];\n\n  return {\n    root: walkItem(_data.root),\n    content:\n      callbackFn(mappedContent, {\n        parentId: \"root\",\n        propName: \"default-zone\",\n      }) ?? mappedContent,\n    zones: Object.keys(zones).reduce(\n      (acc, zoneCompound) => ({\n        ...acc,\n        [zoneCompound]: zones[zoneCompound].map(walkItem),\n      }),\n      {}\n    ),\n  } as T;\n}\n"
  },
  {
    "path": "packages/core/lib/dnd/NestedDroppablePlugin.ts",
    "content": "import { DragDropManager } from \"@dnd-kit/dom\";\nimport { Plugin } from \"@dnd-kit/abstract\";\n\nimport type { Droppable } from \"@dnd-kit/dom\";\n\nimport { effects, untracked } from \"@dnd-kit/state\";\nimport { throttle } from \"../throttle\";\nimport { ComponentDndData } from \"../../components/DraggableComponent\";\nimport { DropZoneDndData } from \"../../components/DropZone\";\nimport { getFrame } from \"../get-frame\";\nimport { GlobalPosition } from \"../global-position\";\nimport {\n  BubbledPointerEvent,\n  BubbledPointerEventType,\n} from \"../bubble-pointer-event\";\nimport { rootAreaId, rootDroppableId } from \"../root-droppable-id\";\n\ntype NestedDroppablePluginOptions = {\n  onChange: (\n    params: {\n      area: string | null;\n      zone: string | null;\n    },\n    manager: DragDropManager\n  ) => void;\n};\n\nconst depthSort = (candidates: Droppable[]) => {\n  return candidates.sort((a, b) => {\n    const aData = a.data as ComponentDndData | DropZoneDndData;\n    const bData = b.data as ComponentDndData | DropZoneDndData;\n\n    // Use depth instead of ref, as this is 1) faster and 2) handles cases where a and b share a ref\n    if (aData.depth > bData.depth) {\n      return 1;\n    }\n\n    if (bData.depth > aData.depth) {\n      return -1;\n    }\n\n    return 0;\n  });\n};\n\nconst getZoneId = (candidate: Droppable | undefined) => {\n  let id: string | null = candidate?.id as string;\n\n  if (!candidate) return null;\n\n  if (candidate.type === \"component\") {\n    const data = candidate.data as ComponentDndData;\n\n    if (data.containsActiveZone) {\n      id = null;\n    } else {\n      id = data.zone;\n    }\n  } else if (candidate.type === \"void\") {\n    return \"void\";\n  }\n\n  return id;\n};\n\nconst BUFFER = 6;\n\nconst getPointerCollisions = (\n  position: GlobalPosition,\n  manager: DragDropManager\n) => {\n  const candidates: Droppable[] = [];\n\n  let elements = position.target.ownerDocument.elementsFromPoint(\n    position.x,\n    position.y\n  );\n\n  const previewFrame = elements.find((el) =>\n    el.getAttribute(\"data-puck-preview\")\n  );\n\n  // Restrict to drawer element if pointer is over drawer. This is necessary if\n  // the drawer is over dnd elements.\n  const drawer = elements.find((el) => el.getAttribute(\"data-puck-drawer\"));\n  if (drawer) {\n    elements = [drawer];\n  }\n\n  // If cursor is over iframe (but not drawer), and user is in host doc, go into the iframe doc\n  // This occurs when dragging in new items\n  if (previewFrame) {\n    // Perf: Consider moving this outside of this plugin\n    const frame = getFrame();\n\n    if (frame) {\n      elements = frame.elementsFromPoint(position.frame.x, position.frame.y);\n    }\n  }\n\n  if (elements) {\n    for (let i = 0; i < elements.length; i++) {\n      const element = elements[i];\n\n      const dropzoneId = element.getAttribute(\"data-puck-dropzone\");\n      const id = element.getAttribute(\"data-puck-dnd\");\n      const isVoid = element.hasAttribute(\"data-puck-dnd-void\");\n\n      // Only include this candidate if we're within a threshold of the bounding box\n      if (BUFFER && (dropzoneId || id) && !isVoid) {\n        const box = element.getBoundingClientRect();\n\n        const contractedBox = {\n          left: box.left + BUFFER,\n          right: box.right - BUFFER,\n          top: box.top + BUFFER,\n          bottom: box.bottom - BUFFER,\n        };\n\n        if (\n          position.frame.x < contractedBox.left ||\n          position.frame.x > contractedBox.right ||\n          position.frame.y > contractedBox.bottom ||\n          position.frame.y < contractedBox.top\n        ) {\n          continue;\n        }\n      }\n\n      if (dropzoneId) {\n        const droppable = manager.registry.droppables.get(dropzoneId);\n\n        if (droppable) {\n          candidates.push(droppable);\n        }\n      }\n\n      if (id) {\n        const droppable = manager.registry.droppables.get(id);\n\n        if (droppable) {\n          candidates.push(droppable);\n        }\n      }\n    }\n  }\n\n  return candidates;\n};\n\nexport const findDeepestCandidate = (\n  position: GlobalPosition,\n  manager: DragDropManager\n) => {\n  const candidates = getPointerCollisions(position, manager);\n\n  if (candidates.length > 0) {\n    const sortedCandidates = depthSort(candidates);\n\n    const draggable = manager.dragOperation.source;\n\n    const draggedCandidateIndex = sortedCandidates.findIndex(\n      (candidate) => candidate.id === draggable?.id\n    );\n\n    const draggedCandidateId = draggable?.id;\n\n    let filteredCandidates = [...sortedCandidates];\n\n    if (draggedCandidateId && draggedCandidateIndex > -1) {\n      // Removed dragged candidate\n      filteredCandidates.splice(draggedCandidateIndex, 1);\n    }\n\n    // Remove any descendants\n    filteredCandidates = filteredCandidates.filter((candidate) => {\n      const candidateData = candidate.data as\n        | ComponentDndData\n        | DropZoneDndData;\n\n      if (draggedCandidateId && draggedCandidateIndex > -1) {\n        if (candidateData.path.indexOf(draggedCandidateId) > -1) {\n          return false;\n        }\n      }\n\n      if (candidate.type === \"dropzone\") {\n        const candidateData = candidate.data as DropZoneDndData;\n\n        // Remove non-droppable zones\n        if (!candidateData.isDroppableTarget) {\n          return false;\n        }\n\n        // Remove if dragged item is area\n        if (candidateData.areaId === draggedCandidateId) {\n          return false;\n        }\n      } else if (candidate.type === \"component\") {\n        const candidateData = candidate.data as ComponentDndData;\n\n        // Remove non-droppable zones\n        if (!candidateData.inDroppableZone) {\n          return false;\n        }\n      }\n\n      return true;\n    });\n\n    filteredCandidates.reverse();\n\n    const primaryCandidate = filteredCandidates[0];\n\n    if (!primaryCandidate) return { zone: null, area: null };\n\n    const primaryCandidateData = primaryCandidate.data as\n      | ComponentDndData\n      | DropZoneDndData;\n    const primaryCandidateIsComponent =\n      \"containsActiveZone\" in primaryCandidateData;\n    const zone = getZoneId(primaryCandidate);\n    const area =\n      primaryCandidateIsComponent && primaryCandidateData.containsActiveZone\n        ? filteredCandidates[0].id\n        : filteredCandidates[0]?.data.areaId;\n\n    return { zone, area };\n  }\n\n  return {\n    zone: rootDroppableId,\n    area: rootAreaId,\n  };\n};\n\nexport const createNestedDroppablePlugin = (\n  { onChange }: NestedDroppablePluginOptions,\n  id: string\n): any =>\n  class NestedDroppablePlugin extends Plugin<DragDropManager, {}> {\n    constructor(manager: DragDropManager, options?: {}) {\n      super(manager);\n\n      if (typeof window === \"undefined\") {\n        return;\n      }\n\n      this.registerEffect(() => {\n        const handleMove = (event: BubbledPointerEventType | PointerEvent) => {\n          const target = (\n            event instanceof BubbledPointerEvent // Necessary for Firefox\n              ? event.originalTarget || event.target\n              : event.target\n          ) as HTMLElement;\n\n          const position = new GlobalPosition(target, {\n            x: event.clientX,\n            y: event.clientY,\n          });\n\n          const elements = document.elementsFromPoint(\n            position.global.x,\n            position.global.y\n          );\n\n          const overEl = elements.some((el) => el.id === id);\n\n          if (overEl) {\n            onChange(findDeepestCandidate(position, manager), manager);\n          }\n        };\n\n        const handleMoveThrottled = throttle(handleMove, 50);\n\n        const handlePointerMove = (event: PointerEvent) => {\n          handleMoveThrottled(event);\n        };\n\n        document.body.addEventListener(\"pointermove\", handlePointerMove, {\n          capture: true, // dndkit's PointerSensor prevents propagation during drag\n        });\n\n        const cleanup = () => {\n          document.body.removeEventListener(\"pointermove\", handlePointerMove, {\n            capture: true,\n          });\n        };\n\n        return cleanup;\n      });\n    }\n  };\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/collision-debug.ts",
    "content": "import { Point } from \"@dnd-kit/geometry\";\n\nconst DEBUG = false;\n\nconst debugElements: Record<\n  string,\n  { svg: SVGSVGElement; line: SVGLineElement; text: SVGTextElement }\n> = {};\n\nlet timeout: NodeJS.Timeout;\n\nexport const collisionDebug = (\n  a: Point,\n  b: Point,\n  id: string,\n  color: string,\n  label?: string | null\n) => {\n  if (!DEBUG) return;\n\n  const debugId = `${id}-debug`;\n\n  clearTimeout(timeout);\n\n  timeout = setTimeout(() => {\n    Object.entries(debugElements).forEach(([id, { svg }]) => {\n      svg.remove();\n\n      delete debugElements[id];\n    });\n  }, 1000);\n\n  requestAnimationFrame(() => {\n    const existingEl = debugElements[debugId];\n\n    let line = debugElements[debugId]?.line;\n    let text = debugElements[debugId]?.text;\n\n    if (!existingEl) {\n      const svgNs = \"http://www.w3.org/2000/svg\";\n      const svg = document.createElementNS(svgNs, \"svg\");\n      line = document.createElementNS(svgNs, \"line\");\n      text = document.createElementNS(svgNs, \"text\");\n\n      // svg.setAttribute(\"xmlns\", \"http://www.w3.org/2000/svg\");\n      svg.setAttribute(\"id\", debugId);\n      svg.setAttribute(\n        \"style\",\n        \"position: fixed; height: 100%; width: 100%; pointer-events: none; top: 0px; left: 0px;\"\n      );\n      svg.appendChild(line);\n      svg.appendChild(text);\n\n      text.setAttribute(\"fill\", `black`);\n\n      document.body.appendChild(svg);\n\n      debugElements[debugId] = { svg, line, text };\n    }\n\n    line.setAttribute(\"x1\", a.x.toString());\n    line.setAttribute(\"x2\", b.x.toString());\n    line.setAttribute(\"y1\", a.y.toString());\n    line.setAttribute(\"y2\", b.y.toString());\n    line.setAttribute(\"style\", `stroke:${color};stroke-width:2`);\n\n    text.setAttribute(\"x\", (a.x - (a.x - b.x) / 2).toString());\n    text.setAttribute(\"y\", (a.y - (a.y - b.y) / 2).toString());\n\n    if (label) {\n      text.innerHTML = label;\n    }\n  });\n};\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/directional/index.ts",
    "content": "import { CollisionType, DragOperation, Droppable } from \"@dnd-kit/abstract\";\nimport { Point } from \"@dnd-kit/geometry\";\nimport { collisionDebug } from \"../collision-debug\";\n\nlet distanceChange: \"increasing\" | \"decreasing\" = \"increasing\";\n\n/**\n * Collide if we're moving towards an item.\n */\nexport const directionalCollision = (\n  input: { dragOperation: DragOperation; droppable: Droppable },\n  previous: Point\n) => {\n  const { dragOperation, droppable } = input;\n  const { shape: dropShape } = droppable;\n  const { position } = dragOperation;\n  const dragShape = dragOperation.shape?.current;\n\n  if (!dragShape || !dropShape) return null;\n\n  const dropCenter = dropShape.center;\n\n  const distanceToPrevious = Math.sqrt(\n    Math.pow(dropCenter.x - previous.x, 2) +\n      Math.pow(dropCenter.y - previous.y, 2)\n  );\n\n  const distanceToCurrent = Math.sqrt(\n    Math.pow(dropCenter.x - position.current.x, 2) +\n      Math.pow(dropCenter.y - position.current.y, 2)\n  );\n\n  distanceChange =\n    distanceToCurrent === distanceToPrevious\n      ? distanceChange\n      : distanceToCurrent < distanceToPrevious\n      ? \"decreasing\"\n      : \"increasing\";\n\n  collisionDebug(\n    dragShape.center,\n    dropCenter,\n    droppable.id.toString(),\n    \"rebeccapurple\"\n  );\n\n  if (distanceChange === \"decreasing\") {\n    return {\n      id: droppable.id,\n      value: 1,\n      type: CollisionType.Collision,\n    };\n  }\n\n  return null;\n};\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/dynamic/get-direction.ts",
    "content": "import { Point } from \"@dnd-kit/geometry\";\nimport { DragAxis, Direction } from \"../../../../types\";\n\nexport const getDirection = (dragAxis: DragAxis, delta: Point): Direction => {\n  if (dragAxis === \"dynamic\") {\n    if (Math.abs(delta.y) > Math.abs(delta.x)) {\n      return delta.y === 0 ? null : delta.y > 0 ? \"down\" : \"up\";\n    } else {\n      return delta.x === 0 ? null : delta.x > 0 ? \"right\" : \"left\";\n    }\n  } else if (dragAxis === \"x\") {\n    return delta.x === 0 ? null : delta.x > 0 ? \"right\" : \"left\";\n  }\n\n  return delta.y === 0 ? null : delta.y > 0 ? \"down\" : \"up\";\n};\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/dynamic/get-midpoint-impact.ts",
    "content": "import { Shape } from \"@dnd-kit/geometry\";\nimport { Direction } from \"../../../../types\";\n\n/**\n * Determine whether or not the leading edge of the dragShape (the edge that is on\n * the side of the direction of travel) is over the midpoint of the dropShape.\n *\n * @param dragShape The shape of the draggable\n * @param dropShape The shape of the droppable\n * @param direction The direction of travel\n * @param offsetMultiplier An optional offset multiplier\n *\n * @returns A boolean describingw hether or not the leadingEdge of the dragShape is over the mid-point of the dropShape\n */\nexport const getMidpointImpact = (\n  dragShape: Shape,\n  dropShape: Shape,\n  direction: Direction,\n  offsetMultiplier: number = 0\n): Boolean => {\n  const dragRect = dragShape.boundingRectangle;\n  const dropCenter = dropShape.center;\n\n  if (direction === \"down\") {\n    const offset = offsetMultiplier * dropShape.boundingRectangle.height;\n    return dragRect.bottom >= dropCenter.y + offset;\n  } else if (direction === \"up\") {\n    const offset = offsetMultiplier * dropShape.boundingRectangle.height;\n    return dragRect.top < dropCenter.y - offset;\n  } else if (direction === \"left\") {\n    const offset = offsetMultiplier * dropShape.boundingRectangle.width;\n    return dropCenter.x - offset >= dragRect.left;\n  }\n\n  // direction === \"right\"\n  const offset = offsetMultiplier * dropShape.boundingRectangle.width;\n  return dragRect.right - offset >= dropCenter.x;\n};\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/dynamic/index.ts",
    "content": "import {\n  Collision,\n  CollisionDetector,\n  CollisionPriority,\n  CollisionType,\n  UniqueIdentifier,\n} from \"@dnd-kit/abstract\";\nimport { directionalCollision } from \"../directional\";\nimport { getDirection } from \"./get-direction\";\nimport { getMidpointImpact } from \"./get-midpoint-impact\";\nimport { trackMovementInterval } from \"./track-movement-interval\";\nimport { collisionDebug } from \"../collision-debug\";\nimport { closestCorners } from \"@dnd-kit/collision\";\nimport { DragAxis, Direction } from \"../../../../types\";\nimport { collisionStore } from \"./store\";\n\nlet flushNext: UniqueIdentifier = \"\";\n\n/**\n * A factory for creating a \"dynamic\" collision detector\n *\n * A dynamic collision combines mid-point and directional collisions for smooth, flicker-free dragging with complex\n * layouts.\n *\n * Midpoint-based detection creates a natural snapping effect, inspired by react-beautiful-dnd. Each collision is\n * provided with the direction of movement, so the user can determine the appropriate place to inject the item based\n * on their layout or document direction.\n *\n * If a draggable is being dragged towards a droppable with the same ID, this is deemed the highest priority collision\n * to prevent flickering with complex layout shifts during drag.\n *\n * @param dragAxis Restrict mid-point detection to a given axis, providing a traditional sortable list detection.\n * @param midpointOffset A percentage offset from the midpoint. Defaults to 5% (0.05) from the mid-point in the\n * direction of travel, helping to create \"dead zone\" in the center of the item.\n *\n * @returns\n */\nexport const createDynamicCollisionDetector = (\n  dragAxis: DragAxis,\n  midpointOffset: number = 0.05\n) =>\n  ((input) => {\n    const { dragOperation, droppable } = input;\n\n    const { position } = dragOperation;\n    const dragShape = dragOperation.shape?.current;\n    const { shape: dropShape } = droppable;\n\n    if (!dragShape || !dropShape) {\n      return null;\n    }\n\n    const { center: dragCenter } = dragShape;\n\n    const { fallbackEnabled } = collisionStore.getState();\n\n    const interval = trackMovementInterval(position.current, dragAxis);\n\n    const data = {\n      direction: interval.direction,\n    };\n\n    const { center: dropCenter } = dropShape;\n\n    const overMidpoint = getMidpointImpact(\n      dragShape,\n      dropShape,\n      interval.direction,\n      midpointOffset\n    );\n\n    if (dragOperation.source?.id === droppable.id) {\n      // If the droppable and draggable are the same item, we check if we're moving towards the droppable.\n      // If we are, we always return that as the highest priority collision target to prevent unexpected\n      // movement in complex grid layouts\n\n      const collision = directionalCollision(input, interval.previous);\n\n      collisionDebug(dragCenter, dropCenter, droppable.id.toString(), \"yellow\");\n\n      if (collision) {\n        return {\n          ...collision,\n          priority: CollisionPriority.Highest,\n          data,\n        };\n      }\n    }\n\n    const intersectionArea = dragShape.intersectionArea(dropShape);\n    const intersectionRatio = intersectionArea / dropShape.area;\n\n    if (intersectionArea && overMidpoint) {\n      collisionDebug(\n        dragCenter,\n        dropCenter,\n        droppable.id.toString(),\n        \"green\",\n        interval.direction\n      );\n\n      const collision: Collision = {\n        id: droppable.id,\n        value: intersectionRatio,\n        priority: CollisionPriority.High,\n        type: CollisionType.Collision,\n      };\n\n      // HACK: Flush ID if it's already in use by temporarily setting the id to something invalid. This forces dnd-kit to trigger a new dragmove event.\n      const shouldFlushId = flushNext === droppable.id;\n\n      flushNext = \"\";\n\n      return { ...collision, id: shouldFlushId ? \"flush\" : collision.id, data };\n    }\n\n    if (fallbackEnabled && dragOperation.source?.id !== droppable.id) {\n      // Only calculate fallbacks when the draggable sits within the droppable's axis projection\n      const xAxisIntersection =\n        dropShape.boundingRectangle.right > dragShape.boundingRectangle.left &&\n        dropShape.boundingRectangle.left < dragShape.boundingRectangle.right;\n\n      const yAxisIntersection =\n        dropShape.boundingRectangle.bottom > dragShape.boundingRectangle.top &&\n        dropShape.boundingRectangle.top < dragShape.boundingRectangle.bottom;\n\n      // If drag axis is Y, then lock to x-axis (vertical) intersect. Otherwise lock to y-axis (horizontal) intersect.\n      if ((dragAxis === \"y\" && xAxisIntersection) || yAxisIntersection) {\n        const fallbackCollision = closestCorners(input);\n\n        if (fallbackCollision) {\n          // For fallback collisions, we use a direction determined by the center points of the two items\n          const direction = getDirection(dragAxis, {\n            x: dragShape.center.x - (droppable.shape?.center.x || 0),\n            y: dragShape.center.y - (droppable.shape?.center.y || 0),\n          });\n\n          data.direction = direction;\n\n          // Fallback collision exists for an intersecting item\n          // In this scenario, we trigger a \"void\" fallback transaction,\n          // which is prioritised over other fallback transactions\n          // Because dnd-kit won't trigger a dragmove event unless the\n          // target ID changes, we introduce an ID \"flushing\" hack\n          if (intersectionArea) {\n            collisionDebug(\n              dragCenter,\n              dropCenter,\n              droppable.id.toString(),\n              \"red\",\n              direction || \"\"\n            );\n\n            // HACK: We always flush the ID after this collision to ensure dnd-kit triggers onDragMove\n            flushNext = droppable.id;\n\n            return {\n              ...fallbackCollision,\n              priority: CollisionPriority.Low,\n              data,\n            };\n          }\n\n          collisionDebug(\n            dragCenter,\n            dropCenter,\n            droppable.id.toString(),\n            \"orange\",\n            direction || \"\"\n          );\n\n          return {\n            ...fallbackCollision,\n            priority: CollisionPriority.Lowest,\n            data,\n          };\n        }\n      }\n    }\n\n    collisionDebug(dragCenter, dropCenter, droppable.id.toString(), \"hotpink\");\n\n    return null;\n  }) as CollisionDetector;\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/dynamic/store.ts",
    "content": "import { createStore } from \"zustand/vanilla\";\nimport { Direction } from \"../../../../types\";\n\nexport const collisionStore = createStore<{\n  fallbackEnabled: boolean;\n}>(() => ({\n  fallbackEnabled: false,\n}));\n"
  },
  {
    "path": "packages/core/lib/dnd/collision/dynamic/track-movement-interval.ts",
    "content": "import { Point } from \"@dnd-kit/geometry\";\nimport { getDirection } from \"./get-direction\";\nimport { Direction, DragAxis } from \"../../../../types\";\n\ntype Interval = {\n  current: Point;\n  delta: Point;\n  previous: Point;\n  direction: Direction;\n};\n\nconst INTERVAL_SENSITIVITY = 10;\n\nconst intervalCache: Interval = {\n  current: { x: 0, y: 0 },\n  delta: { x: 0, y: 0 },\n  previous: { x: 0, y: 0 },\n  direction: null,\n};\n\n/**\n * A method for tracking and getting the current movement interval, including:\n *\n * - `current` - the current point to track\n * - `previous` - the previous point, captured when the delta is greater than the INTERVAL_SENSITIVITY\n * - `delta` - the delta between the two points\n * - `direction` - the direction of travel of the delta, locked to an axis\n *\n * @param point The latest point to track.\n * @param dragAxis The axis to lock the direction to. If the value is \"dynamic\", it can be either axis based on the greater value.\n *\n * @returns Current movement interval\n */\nexport const trackMovementInterval = (\n  point: Point,\n  dragAxis: DragAxis = \"dynamic\"\n) => {\n  intervalCache.current = point;\n\n  intervalCache.delta = {\n    x: point.x - intervalCache.previous.x,\n    y: point.y - intervalCache.previous.y,\n  };\n\n  intervalCache.direction =\n    getDirection(dragAxis, intervalCache.delta) || intervalCache.direction;\n\n  if (\n    Math.abs(intervalCache.delta.x) > INTERVAL_SENSITIVITY ||\n    Math.abs(intervalCache.delta.y) > INTERVAL_SENSITIVITY\n  ) {\n    intervalCache.previous = Point.from(point);\n  }\n\n  return intervalCache;\n};\n"
  },
  {
    "path": "packages/core/lib/dnd/use-on-drag-finished.ts",
    "content": "import { useCallback } from \"react\";\nimport { useAppStoreApi } from \"../../store\";\n\nexport const useOnDragFinished = (\n  cb: (finished: boolean) => void,\n  deps: any[] = []\n) => {\n  const appStore = useAppStoreApi();\n\n  return useCallback(() => {\n    let dispose: () => void = () => {};\n\n    const processDragging = (isDragging: boolean) => {\n      if (isDragging) {\n        cb(false);\n      } else {\n        setTimeout(() => {\n          cb(true);\n        }, 0); // Run outside of React thread, as otherwise state can still race callback and cause unexpected renders\n\n        if (dispose) dispose();\n      }\n    };\n\n    const isDragging = appStore.getState().state.ui.isDragging;\n\n    processDragging(isDragging);\n\n    if (isDragging) {\n      dispose = appStore.subscribe(\n        (s) => s.state.ui.isDragging,\n        (isDragging) => {\n          processDragging(isDragging);\n        }\n      );\n    }\n\n    return dispose;\n  }, [appStore, ...deps]);\n};\n"
  },
  {
    "path": "packages/core/lib/dnd/use-rendered-callback.ts",
    "content": "import { useDragDropManager } from \"@dnd-kit/react\";\nimport { DependencyList, useCallback } from \"react\";\n\n/**\n * Returns a callback that only triggers when dnd-kit has finished\n * rendering. This is useful when working with state managers\n * that operate outside of the React lifecycle, like Zustand, as\n * dnd-kit cannot defer the rendering until it's finished.\n *\n * This may change in a future release\n *\n * @param callback\n * @param deps\n * @returns\n */\nexport function useRenderedCallback<T extends Function>(\n  callback: T,\n  deps: DependencyList\n) {\n  const manager = useDragDropManager();\n\n  return useCallback(\n    async (...args: any) => {\n      await manager?.renderer.rendering;\n\n      return callback(...args);\n    },\n    [...deps, manager]\n  );\n}\n"
  },
  {
    "path": "packages/core/lib/dnd/use-sensors.ts",
    "content": "import { useState } from \"react\";\nimport { PointerSensor } from \"@dnd-kit/react\";\nimport { isElement } from \"@dnd-kit/dom/utilities\";\nimport { type Distance } from \"@dnd-kit/geometry\";\n\nexport interface DelayConstraint {\n  value: number;\n  tolerance: Distance;\n}\n\nexport interface DistanceConstraint {\n  value: Distance;\n  tolerance?: Distance;\n}\n\nexport interface ActivationConstraints {\n  distance?: DistanceConstraint;\n  delay?: DelayConstraint;\n}\n\nconst touchDefault = { delay: { value: 200, tolerance: 10 } };\nconst otherDefault = {\n  delay: { value: 200, tolerance: 10 },\n  distance: { value: 5 },\n};\n\nexport const useSensors = (\n  {\n    other = otherDefault,\n    mouse,\n    touch = touchDefault,\n  }: {\n    mouse?: ActivationConstraints;\n    touch?: ActivationConstraints;\n    other?: ActivationConstraints;\n  } = {\n    touch: touchDefault,\n    other: otherDefault,\n  }\n) => {\n  const [sensors] = useState(() => [\n    PointerSensor.configure({\n      activationConstraints(event, source) {\n        const { pointerType, target } = event;\n\n        if (\n          pointerType === \"mouse\" &&\n          isElement(target) &&\n          (source.handle === target || source.handle?.contains(target))\n        ) {\n          return mouse;\n        }\n\n        if (pointerType === \"touch\") {\n          return touch;\n        }\n\n        return other;\n      },\n    }),\n  ]);\n\n  return sensors;\n};\n"
  },
  {
    "path": "packages/core/lib/field-transforms/build-mappers.ts",
    "content": "import {\n  ComponentData,\n  Config,\n  ExtractField,\n  Field,\n  UserGenerics,\n} from \"../../types\";\nimport { MapFnParams, Mappers } from \"../data/map-fields\";\nimport {\n  FieldTransformFn,\n  FieldTransforms,\n} from \"../../types/API/FieldTransforms\";\n\n/**\n * Converts transformers to mappers\n *\n * Transformers are the same as mappers, except they receive the additional `isReadOnly` param.\n * This converts transformers to mappers by adding the `isReadOnly` param.\n */\nexport function buildMappers<\n  T extends ComponentData,\n  UserConfig extends Config,\n  G extends UserGenerics<UserConfig>\n>(\n  transforms: FieldTransforms,\n  readOnly?: T[\"readOnly\"],\n  forceReadOnly?: boolean\n) {\n  return Object.keys(transforms).reduce<Mappers>((acc, _fieldType) => {\n    const fieldType = _fieldType as Field[\"type\"]; // Not strictly true, as could include user fields, but this should be safe enough\n\n    return {\n      ...acc,\n      [fieldType]: ({\n        parentId,\n        ...params\n      }: MapFnParams<ExtractField<G[\"UserField\"], Field[\"type\"]>>) => {\n        const wildcardPath = params.propPath.replace(/\\[\\d+\\]/g, \"[*]\");\n\n        const isReadOnly =\n          readOnly?.[params.propPath] ||\n          readOnly?.[wildcardPath] ||\n          forceReadOnly ||\n          false;\n\n        const fn = transforms[fieldType] as FieldTransformFn<\n          ExtractField<G[\"UserField\"], Field[\"type\"]>\n        >;\n\n        return fn?.({\n          ...params,\n          isReadOnly,\n          componentId: parentId,\n        });\n      },\n    };\n  }, {});\n}\n"
  },
  {
    "path": "packages/core/lib/field-transforms/default-transforms/inline-text-transform.tsx",
    "content": "import { InlineTextField } from \"../../../components/InlineTextField\";\nimport { FieldTransforms } from \"../../../types/API/FieldTransforms\";\n\nexport const getInlineTextTransform = (): FieldTransforms => ({\n  text: ({ value, componentId, field, propPath, isReadOnly }) => {\n    if (field.contentEditable) {\n      return (\n        <InlineTextField\n          propPath={propPath}\n          componentId={componentId}\n          value={value}\n          opts={{ disableLineBreaks: true }}\n          isReadOnly={isReadOnly}\n        />\n      );\n    }\n\n    return value;\n  },\n  textarea: ({ value, componentId, field, propPath, isReadOnly }) => {\n    if (field.contentEditable) {\n      return (\n        <InlineTextField\n          propPath={propPath}\n          componentId={componentId}\n          value={value}\n          isReadOnly={isReadOnly}\n        />\n      );\n    }\n\n    return value;\n  },\n  custom: ({ value, componentId, field, propPath, isReadOnly }) => {\n    if (field.contentEditable && typeof value === \"string\") {\n      return (\n        <InlineTextField\n          propPath={propPath}\n          componentId={componentId}\n          value={value}\n          isReadOnly={isReadOnly}\n        />\n      );\n    }\n\n    return value;\n  },\n});\n"
  },
  {
    "path": "packages/core/lib/field-transforms/default-transforms/rich-text-transform.tsx",
    "content": "\"use client\";\nimport { EditorFallback } from \"../../../components/RichTextEditor/components/EditorFallback\";\nimport { RichTextRenderFallback } from \"../../../components/RichTextEditor/components/RenderFallback\";\nimport { FieldTransforms } from \"../../../types/API/FieldTransforms\";\nimport { useAppStoreApi } from \"../../../store\";\nimport { setDeep } from \"../../../lib/data/set-deep\";\nimport { registerOverlayPortal } from \"../../../lib/overlay-portal\";\nimport {\n  useEffect,\n  useRef,\n  useCallback,\n  memo,\n  MouseEvent,\n  lazy,\n  Suspense,\n} from \"react\";\nimport type { Editor as TipTapEditor, JSONContent } from \"@tiptap/react\";\nimport { getSelectorForId } from \"../../get-selector-for-id\";\nimport { RichtextField, UiState } from \"../../../types\";\n\nconst Editor = lazy(() =>\n  import(\"../../../components/RichTextEditor/components/Editor\").then((m) => ({\n    default: m.Editor,\n  }))\n);\n\nconst RichTextRender = lazy(() =>\n  import(\"../../../components/RichTextEditor/components/Render\").then((m) => ({\n    default: m.RichTextRender,\n  }))\n);\n\nconst InlineEditorWrapper = memo(\n  ({\n    value,\n    componentId,\n    propPath,\n    field,\n    id,\n  }: {\n    value: string;\n    componentId: string;\n    propPath: string;\n    field: RichtextField;\n    id: string;\n  }) => {\n    const portalRef = useRef<HTMLDivElement>(null);\n    const appStoreApi = useAppStoreApi();\n\n    const onClickHandler = (e: MouseEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n    };\n\n    const onClickCaptureHandler = (e: MouseEvent) => {\n      e.preventDefault();\n      e.stopPropagation();\n\n      const itemSelector = getSelectorForId(\n        appStoreApi.getState().state,\n        componentId\n      );\n\n      appStoreApi.getState().setUi({ itemSelector });\n    };\n\n    // Register portal once\n    useEffect(() => {\n      if (!portalRef.current) return;\n      const cleanup = registerOverlayPortal(portalRef.current, {\n        disableDragOnFocus: true,\n      });\n      return () => cleanup?.();\n    }, [portalRef.current]);\n\n    const handleChange = useCallback(\n      async (content: string | JSONContent, ui?: Partial<UiState>) => {\n        const appStore = appStoreApi.getState();\n        const node = appStore.state.indexes.nodes[componentId];\n        const zoneCompound = `${node.parentId}:${node.zone}`;\n        const index =\n          appStore.state.indexes.zones[zoneCompound]?.contentIds.indexOf(\n            componentId\n          );\n\n        const newProps = setDeep(node.data.props, propPath, content);\n\n        const resolvedData = await appStore.resolveComponentData(\n          { ...node.data, props: newProps },\n          \"replace\"\n        );\n\n        appStore.dispatch({\n          type: \"replace\",\n          data: resolvedData.node,\n          destinationIndex: index,\n          destinationZone: zoneCompound,\n          ui,\n        });\n      },\n      [appStoreApi, componentId, propPath]\n    );\n\n    const handleFocus = useCallback(\n      (editor: TipTapEditor) => {\n        appStoreApi.setState({\n          currentRichText: {\n            inlineComponentId: componentId,\n            inline: true,\n            field,\n            editor,\n            id,\n          },\n        });\n      },\n      [field, componentId]\n    );\n\n    if (!field.contentEditable)\n      return (\n        <Suspense fallback={<RichTextRenderFallback content={value} />}>\n          <RichTextRender content={value} field={field} />\n        </Suspense>\n      );\n\n    const editorProps = {\n      content: value,\n      onChange: handleChange,\n      field: field,\n      inline: true,\n      onFocus: handleFocus,\n      id: id,\n      name: propPath,\n    };\n\n    return (\n      <div\n        ref={portalRef}\n        onClick={onClickHandler}\n        onClickCapture={onClickCaptureHandler}\n      >\n        <Suspense fallback={<EditorFallback {...editorProps} />}>\n          <Editor {...editorProps} />\n        </Suspense>\n      </div>\n    );\n  }\n);\n\nInlineEditorWrapper.displayName = \"InlineEditorWrapper\";\n\nexport const getRichTextTransform = (): FieldTransforms => ({\n  richtext: ({ value, componentId, field, propPath, isReadOnly }) => {\n    const { contentEditable = true, tiptap } = field;\n    if (contentEditable === false || isReadOnly) {\n      return <RichTextRender content={value} field={field} />;\n    }\n\n    const id = `${componentId}_${field.type}_${propPath}`;\n\n    return (\n      <InlineEditorWrapper\n        key={id}\n        value={value}\n        componentId={componentId}\n        propPath={propPath}\n        field={field}\n        id={id}\n      />\n    );\n  },\n});\n"
  },
  {
    "path": "packages/core/lib/field-transforms/default-transforms/slot-transform.tsx",
    "content": "import { ReactNode } from \"react\";\nimport { DropZoneProps } from \"../../../components/DropZone/types\";\nimport { Content } from \"../../../types\";\nimport { FieldTransforms } from \"../../../types/API/FieldTransforms\";\n\nexport const getSlotTransform = (\n  renderSlotEdit: (dzProps: DropZoneProps & { content: Content }) => ReactNode,\n  renderSlotRender: (\n    dzProps: DropZoneProps & { content: Content }\n  ) => ReactNode = renderSlotEdit\n): FieldTransforms => ({\n  slot: ({ value: content, propName, field, isReadOnly }) => {\n    const render = isReadOnly ? renderSlotRender : renderSlotEdit;\n\n    const Slot = (dzProps: DropZoneProps) =>\n      render({\n        allow: field?.type === \"slot\" ? field.allow : [],\n        disallow: field?.type === \"slot\" ? field.disallow : [],\n        ...dzProps,\n        zone: propName,\n        content,\n      });\n\n    return Slot;\n  },\n});\n"
  },
  {
    "path": "packages/core/lib/field-transforms/use-field-transforms-tracked.tsx",
    "content": "\"use client\";\n\nimport { ComponentData, Config } from \"../../types\";\nimport { useMemo, useRef } from \"react\";\nimport { mapFields, Mappers } from \"../data/map-fields\";\nimport { FieldTransforms } from \"../../types/API/FieldTransforms\";\nimport { buildMappers } from \"./build-mappers\";\n\nexport function useFieldTransformsTracked<\n  T extends ComponentData,\n  UserConfig extends Config\n>(\n  config: UserConfig,\n  item: T,\n  transforms: FieldTransforms,\n  readOnly?: T[\"readOnly\"],\n  forceReadOnly?: boolean\n): T[\"props\"] {\n  const prevProps = useRef<Record<string, any>>(null);\n  const prevResult = useRef<Record<string, any>>(item.props);\n\n  const mappers = useMemo<Mappers>(\n    () => buildMappers(transforms, readOnly, forceReadOnly),\n    [transforms, readOnly, forceReadOnly]\n  );\n\n  const transformedProps = useMemo(() => {\n    // Filter to changed fields only (shallow comparison)\n    const changedProps: Record<string, any> = {};\n\n    const componentConfig =\n      item.type === \"root\" ? config.root : config.components?.[item.type];\n\n    let changeIncludesSlot = false;\n\n    for (const fieldName in item.props) {\n      const fieldType = componentConfig?.fields?.[fieldName]?.type;\n\n      if (\n        !prevProps.current ||\n        item.props[fieldName] !== prevProps.current[fieldName]\n      ) {\n        changedProps[fieldName] = item.props[fieldName];\n\n        if (fieldType === \"slot\") {\n          changeIncludesSlot = true;\n        }\n      }\n    }\n\n    // Always include ID\n    changedProps.id = item.props.id;\n\n    prevProps.current = item.props;\n\n    const mapped = mapFields(\n      { ...item, props: changedProps },\n      mappers,\n      config,\n      false,\n      changeIncludesSlot\n    ).props;\n\n    prevResult.current = { ...prevResult.current, ...mapped };\n\n    return prevResult.current;\n  }, [config, item, mappers]);\n\n  const mergedProps = useMemo(\n    () => ({ ...item.props, ...transformedProps }),\n    [item.props, transformedProps]\n  );\n\n  return mergedProps;\n}\n"
  },
  {
    "path": "packages/core/lib/field-transforms/use-field-transforms.tsx",
    "content": "import { ComponentData, Config } from \"../../types\";\nimport { useMemo } from \"react\";\nimport { mapFields, Mappers } from \"../data/map-fields\";\nimport { FieldTransforms } from \"../../types/API/FieldTransforms\";\nimport { buildMappers } from \"./build-mappers\";\n\nexport function useFieldTransforms<\n  T extends ComponentData,\n  UserConfig extends Config\n>(\n  config: UserConfig,\n  item: T,\n  transforms: FieldTransforms,\n  readOnly?: T[\"readOnly\"],\n  forceReadOnly?: boolean\n): T[\"props\"] {\n  const mappers = useMemo<Mappers>(\n    () => buildMappers(transforms, readOnly, forceReadOnly),\n    [transforms, readOnly, forceReadOnly]\n  );\n\n  const transformedProps = useMemo(() => {\n    return mapFields(item, mappers, config).props;\n  }, [config, item, mappers]);\n\n  const mergedProps = useMemo(\n    () => ({ ...item.props, ...transformedProps }),\n    [item.props, transformedProps]\n  );\n\n  return mergedProps;\n}\n"
  },
  {
    "path": "packages/core/lib/filter-data-attrs.ts",
    "content": "const dataAttrRe = /^(data-.*)$/;\n\nexport const filterDataAttrs = (props: Record<string, any>) => {\n  let filteredProps: Record<string, any> = {};\n\n  for (const prop in props) {\n    if (\n      Object.prototype.hasOwnProperty.call(props, prop) &&\n      dataAttrRe.test(prop)\n    ) {\n      filteredProps[prop] = props[prop];\n    }\n  }\n\n  return filteredProps;\n};\n"
  },
  {
    "path": "packages/core/lib/filter.ts",
    "content": "export const filter = (obj: Record<any, any>, validKeys: string[]) => {\n  return validKeys.reduce((acc, item) => {\n    if (typeof obj[item] !== \"undefined\") {\n      return { ...acc, [item]: obj[item] };\n    }\n\n    return acc;\n  }, {});\n};\n"
  },
  {
    "path": "packages/core/lib/frame-context.tsx",
    "content": "\"use client\";\n\nimport React, {\n  createContext,\n  useContext,\n  useRef,\n  RefObject,\n  useMemo,\n} from \"react\";\n\ninterface FrameContextType {\n  frameRef: RefObject<HTMLDivElement | null>;\n}\n\nconst FrameContext = createContext<FrameContextType | null>(null);\n\n// Provider component\nexport const FrameProvider: React.FC<{ children: React.ReactNode }> = ({\n  children,\n}) => {\n  const frameRef = useRef<HTMLDivElement>(null);\n\n  const value = useMemo(\n    () => ({\n      frameRef,\n    }),\n    []\n  );\n\n  return (\n    <FrameContext.Provider value={value}>{children}</FrameContext.Provider>\n  );\n};\n\nexport const useCanvasFrame = (): FrameContextType => {\n  const context = useContext(FrameContext);\n\n  if (context === null) {\n    throw new Error(\"useCanvasFrame must be used within a FrameProvider\");\n  }\n\n  return context;\n};\n"
  },
  {
    "path": "packages/core/lib/generate-id.ts",
    "content": "import { v4 as uuidv4 } from \"uuid\";\n\nexport const generateId = (type?: string | number) =>\n  type ? `${type}-${uuidv4()}` : uuidv4();\n"
  },
  {
    "path": "packages/core/lib/get-changed.ts",
    "content": "import { ComponentData } from \"../types\";\nimport { deepEqual } from \"fast-equals\";\n\nexport const getChanged = (\n  newItem: Omit<Partial<ComponentData<any>>, \"type\"> | undefined,\n  oldItem: Omit<Partial<ComponentData<any>>, \"type\"> | null | undefined\n) => {\n  return newItem\n    ? Object.keys(newItem.props || {}).reduce((acc, item) => {\n        const newItemProps = newItem?.props || {};\n        const oldItemProps = oldItem?.props || {};\n\n        return {\n          ...acc,\n          [item]: !deepEqual(oldItemProps[item], newItemProps[item]),\n        };\n      }, {})\n    : {};\n};\n"
  },
  {
    "path": "packages/core/lib/get-class-name-factory.ts",
    "content": "import classnames from \"classnames\";\n\ntype OptionsObj = Record<string, any>;\ntype Options = string | OptionsObj;\n\nexport const getGlobalClassName = (rootClass: string, options: Options) => {\n  if (typeof options === \"string\") {\n    return `${rootClass}-${options}`;\n  } else {\n    const mappedOptions: Options = {};\n    for (let option in options) {\n      mappedOptions[`${rootClass}--${option}`] = options[option];\n    }\n\n    return classnames({\n      [rootClass]: true,\n      ...mappedOptions,\n    });\n  }\n};\n\nconst getClassNameFactory =\n  (\n    rootClass: string,\n    styles: Record<string, string>,\n    config: { baseClass?: string } = { baseClass: \"\" }\n  ) =>\n  (options: Options = {}) => {\n    if (typeof options === \"string\") {\n      const descendant = options;\n\n      const style = styles[`${rootClass}-${descendant}`];\n\n      if (style) {\n        return config.baseClass + styles[`${rootClass}-${descendant}`] || \"\";\n      }\n\n      return \"\";\n    } else if (typeof options === \"object\") {\n      const modifiers = options;\n\n      const prefixedModifiers: OptionsObj = {};\n\n      for (let modifier in modifiers) {\n        prefixedModifiers[styles[`${rootClass}--${modifier}`]] =\n          modifiers[modifier];\n      }\n\n      const c = styles[rootClass];\n\n      return (\n        config.baseClass +\n        classnames({\n          [c]: !!c, // only apply the class if it exists\n          ...prefixedModifiers,\n        })\n      );\n    } else {\n      return config.baseClass + styles[rootClass] || \"\";\n    }\n  };\n\nexport default getClassNameFactory;\n"
  },
  {
    "path": "packages/core/lib/get-deep-dir.ts",
    "content": "type Dir = \"ltr\" | \"rtl\";\n\nexport function getDeepDir(el: Element | null | undefined) {\n  function findDir(node: Element | null): Dir {\n    if (!node) return \"ltr\";\n\n    const d = node.getAttribute(\"dir\") as Dir | \"\";\n\n    return d || findDir(node.parentElement);\n  }\n\n  return el ? findDir(el) : \"ltr\";\n}\n"
  },
  {
    "path": "packages/core/lib/get-deep-scroll-position.ts",
    "content": "export function getDeepScrollPosition(element: HTMLElement) {\n  let totalScroll = {\n    x: 0,\n    y: 0,\n  };\n\n  let current: HTMLElement | null = element;\n\n  while (current && current !== document.documentElement) {\n    const parent: HTMLElement | null = current.parentElement;\n\n    if (parent) {\n      totalScroll.x += parent.scrollLeft;\n      totalScroll.y += parent.scrollTop;\n    }\n\n    current = parent;\n  }\n\n  return totalScroll;\n}\n"
  },
  {
    "path": "packages/core/lib/get-frame.ts",
    "content": "export const getFrame = () => {\n  if (typeof window === \"undefined\") return;\n\n  let frameEl: Element | Document | null | undefined =\n    document.querySelector(\"#preview-frame\");\n\n  if (frameEl?.tagName === \"IFRAME\") {\n    return (frameEl as HTMLIFrameElement)!.contentDocument || document;\n  }\n\n  return frameEl?.ownerDocument || document;\n};\n"
  },
  {
    "path": "packages/core/lib/get-selector-for-id.ts",
    "content": "import { PrivateAppState } from \"../types/Internal\";\n\nexport const getSelectorForId = (state: PrivateAppState, id: string) => {\n  const node = state.indexes.nodes[id];\n\n  if (!node) return;\n\n  const zoneCompound = `${node.parentId}:${node.zone}`;\n\n  const index = state.indexes.zones[zoneCompound].contentIds.indexOf(id);\n\n  return { zone: zoneCompound, index };\n};\n"
  },
  {
    "path": "packages/core/lib/get-zone-id.ts",
    "content": "import { rootDroppableId } from \"./root-droppable-id\";\n\nexport const getZoneId = (zoneCompound?: string) => {\n  if (!zoneCompound) {\n    return [];\n  }\n\n  if (zoneCompound && zoneCompound.indexOf(\":\") > -1) {\n    return zoneCompound.split(\":\");\n  }\n\n  return [rootDroppableId, zoneCompound];\n};\n"
  },
  {
    "path": "packages/core/lib/get-zoom-config.ts",
    "content": "import { getBox } from \"css-box-model\";\nimport { AppState } from \"../types\";\n\nconst RESET_ZOOM_SMALLER_THAN_FRAME = true;\n\nexport const getZoomConfig = (\n  uiViewport: AppState[\"ui\"][\"viewports\"][\"current\"],\n  frame: HTMLElement,\n  zoom: number\n) => {\n  const box = getBox(frame);\n\n  const { width: frameWidth, height: frameHeight } = box.contentBox;\n\n  const viewportHeight =\n    uiViewport.height === \"auto\" ? frameHeight : uiViewport.height;\n\n  let rootHeight = 0;\n  let autoZoom = 1;\n\n  if (\n    typeof uiViewport.width === \"number\" &&\n    (uiViewport.width > frameWidth || viewportHeight > frameHeight)\n  ) {\n    const widthZoom = Math.min(frameWidth / uiViewport.width, 1);\n    const heightZoom = Math.min(frameHeight / viewportHeight, 1);\n\n    zoom = widthZoom;\n\n    if (widthZoom < heightZoom) {\n      rootHeight = viewportHeight / zoom;\n    } else {\n      rootHeight = viewportHeight;\n      zoom = heightZoom;\n    }\n\n    autoZoom = zoom;\n  } else {\n    if (RESET_ZOOM_SMALLER_THAN_FRAME) {\n      autoZoom = 1;\n      zoom = 1;\n      rootHeight = viewportHeight;\n    }\n  }\n\n  return { autoZoom, rootHeight, zoom };\n};\n"
  },
  {
    "path": "packages/core/lib/global-position.ts",
    "content": "interface Position {\n  x: number;\n  y: number;\n}\n\nexport class GlobalPosition {\n  target: Element;\n  original: Position;\n  private scaleFactor: number = 1;\n  private frameEl: HTMLIFrameElement | null = null;\n  private frameRect: DOMRect | null = null;\n\n  constructor(target: Element, original: Position) {\n    this.target = target;\n    this.original = original;\n\n    this.frameEl = document.querySelector(\"iframe#preview-frame\");\n\n    if (this.frameEl) {\n      this.frameRect = this.frameEl.getBoundingClientRect();\n\n      this.scaleFactor =\n        this.frameRect.width / (this.frameEl.contentWindow?.innerWidth || 1);\n    }\n  }\n\n  get x() {\n    return this.original.x;\n  }\n\n  get y() {\n    return this.original.y;\n  }\n\n  get global() {\n    if (document !== this.target.ownerDocument && this.frameRect) {\n      return {\n        x: this.x * this.scaleFactor + this.frameRect.left,\n        y: this.y * this.scaleFactor + this.frameRect.top,\n      };\n    }\n\n    return this.original;\n  }\n\n  get frame() {\n    if (document === this.target.ownerDocument && this.frameRect) {\n      return {\n        x: (this.x - this.frameRect.left) / this.scaleFactor,\n        y: (this.y - this.frameRect.top) / this.scaleFactor,\n      };\n    }\n\n    return this.original;\n  }\n}\n"
  },
  {
    "path": "packages/core/lib/group-zones-by-component.ts",
    "content": "import { getZoneId } from \"./get-zone-id\";\nimport { Data, Content } from \"../types\";\n\nexport const groupZonesByComponent = (data: Data) => {\n  const zoneEntries = Object.entries(data.zones ?? {});\n\n  return zoneEntries.reduce<\n    Record<string, { zoneCompound: string; content: Content }[]>\n  >((acc, [zoneCompound, zoneContent]) => {\n    const [componentId, zoneName] = getZoneId(zoneCompound);\n\n    if (!componentId.length || !zoneName.length) return acc;\n\n    if (!acc[componentId]) {\n      acc[componentId] = [];\n    }\n\n    acc[componentId].push({ zoneCompound, content: zoneContent });\n\n    return acc;\n  }, {});\n};\n"
  },
  {
    "path": "packages/core/lib/index.ts",
    "content": "export * from \"./filter\";\nexport { default as getClassNameFactory } from \"./get-class-name-factory\";\nexport * from \"./data/reorder\";\nexport * from \"./data/replace\";\nexport * from \"./use-reset-auto-zoom\";\n"
  },
  {
    "path": "packages/core/lib/insert-component.ts",
    "content": "import { InsertAction } from \"../reducer\";\nimport { insertAction } from \"../reducer/actions/insert\";\nimport { useAppStoreApi } from \"../store\";\nimport { generateId } from \"./generate-id\";\nimport { getItem } from \"./data/get-item\";\nimport { getSelectorForId } from \"./get-selector-for-id\";\n\n// Makes testing easier without mocks\nexport const insertComponent = async (\n  componentType: string,\n  zone: string,\n  index: number,\n  appStore: ReturnType<typeof useAppStoreApi>\n) => {\n  const { getState } = appStore;\n\n  // Reuse newData so ID retains parity between dispatch and resolver\n  const id = generateId(componentType);\n\n  const insertActionData: InsertAction = {\n    type: \"insert\",\n    componentType,\n    destinationIndex: index,\n    destinationZone: zone,\n    id,\n  };\n\n  const stateBefore = getState().state;\n  const insertedState = insertAction(stateBefore, insertActionData, getState());\n\n  // Dispatch the insert, immediately\n  const dispatch = getState().dispatch;\n  dispatch({\n    ...insertActionData, // Dispatch insert rather set, as user's may rely on this via onAction\n\n    // We must always record history here so the insert is added to user history\n    // If the user has defined a resolveData method, they will end up with 2 history\n    // entries on insert - one for the initial insert, and one when the data resolves\n    recordHistory: true,\n  });\n\n  const itemSelector = { index, zone };\n\n  // Select the item, immediately\n  dispatch({ type: \"setUi\", ui: { itemSelector } });\n\n  const itemData = getItem(itemSelector, insertedState);\n  if (!itemData) return;\n\n  // Run any resolvers\n  const resolveComponentData = getState().resolveComponentData;\n  const resolved = await resolveComponentData(itemData, \"insert\");\n  if (!resolved.didChange) return;\n\n  // Use latest position, in case it has moved\n  const latestItemSelector = getSelectorForId(getState().state, id);\n  if (!latestItemSelector) return;\n\n  dispatch({\n    type: \"replace\",\n    destinationZone: latestItemSelector.zone,\n    destinationIndex: latestItemSelector.index,\n    data: resolved.node,\n  });\n};\n"
  },
  {
    "path": "packages/core/lib/is-ios.ts",
    "content": "export const isIos = () =>\n  [\n    \"iPad Simulator\",\n    \"iPhone Simulator\",\n    \"iPod Simulator\",\n    \"iPad\",\n    \"iPhone\",\n    \"iPod\",\n  ].includes(navigator.platform) ||\n  // iPad on iOS 13 detection\n  (navigator.userAgent.includes(\"Mac\") && \"ontouchend\" in document);\n"
  },
  {
    "path": "packages/core/lib/load-overrides.ts",
    "content": "import { Overrides, Plugin } from \"../types\";\n\nexport const loadOverrides = ({\n  overrides,\n  plugins,\n}: {\n  overrides?: Partial<Overrides>;\n  plugins?: Plugin[];\n}) => {\n  const collected: Partial<Overrides> = { ...overrides };\n\n  plugins?.forEach((plugin) => {\n    if (!plugin.overrides) return;\n\n    Object.keys(plugin.overrides).forEach((_overridesType) => {\n      const overridesType = _overridesType as keyof Overrides;\n\n      if (!plugin.overrides?.[overridesType]) return;\n\n      if (overridesType === \"fieldTypes\") {\n        const fieldTypes = plugin.overrides!.fieldTypes!;\n        Object.keys(fieldTypes).forEach((fieldType) => {\n          collected.fieldTypes = collected.fieldTypes || {};\n\n          const childNode = collected.fieldTypes[fieldType];\n\n          const Comp = (props: any) =>\n            fieldTypes[fieldType]!({\n              ...props,\n              children: childNode ? childNode(props) : props.children,\n            });\n\n          collected.fieldTypes[fieldType] = Comp;\n        });\n\n        return;\n      }\n\n      const childNode = collected[overridesType];\n\n      const Comp = (props: any) =>\n        plugin.overrides![overridesType]!({\n          ...props,\n          children: childNode ? childNode(props) : props.children,\n        });\n\n      collected[overridesType] = Comp;\n    });\n  });\n\n  return collected;\n};\n"
  },
  {
    "path": "packages/core/lib/migrate.ts",
    "content": "import { defaultAppState } from \"../store/default-app-state\";\nimport type {\n  ComponentData,\n  Config,\n  Content,\n  Data,\n  UserGenerics,\n  WithId,\n} from \"../types\";\nimport { walkAppState } from \"./data/walk-app-state\";\nimport { walkTree } from \"./data/walk-tree\";\n\ntype MigrationOptions<UserConfig extends Config> = {\n  migrateDynamicZonesForComponent?: {\n    [ComponentName in keyof UserConfig[\"components\"]]: (\n      props: WithId<UserGenerics<UserConfig>[\"UserProps\"][ComponentName]>,\n      zones: Record<string, Content>\n    ) => ComponentData[\"props\"];\n  };\n};\n\ntype Migration = (\n  props: Data & { [key: string]: any },\n  config?: Config,\n  migrationOptions?: MigrationOptions<Config>\n) => Data;\n\nconst migrations: Migration[] = [\n  // Migrate root to root.props\n  (data) => {\n    const rootProps = data.root.props || data.root;\n\n    if (Object.keys(data.root).length > 0 && !data.root.props) {\n      console.warn(\n        \"Migration applied: Root props moved from `root` to `root.props`.\"\n      );\n\n      return {\n        ...data,\n        root: {\n          props: {\n            ...rootProps,\n          },\n        },\n      };\n    }\n\n    return data;\n  },\n\n  // Migrate zones to slots\n  (data, config, migrationOptions) => {\n    if (!config) return data;\n\n    console.log(\"Migrating DropZones to slots...\");\n\n    const updatedItems: Record<string, ComponentData> = {};\n    const appState = { ...defaultAppState, data };\n    const { indexes } = walkAppState(appState, config);\n\n    const deletedCompounds: string[] = [];\n\n    walkAppState(appState, config, (content, zoneCompound, zoneType) => {\n      if (zoneType === \"dropzone\") {\n        const [id, slotName] = zoneCompound.split(\":\");\n\n        const nodeData = indexes.nodes[id].data;\n        const componentType = nodeData.type;\n\n        const configForComponent =\n          id === \"root\" ? config.root : config.components[componentType];\n\n        if (configForComponent?.fields?.[slotName]?.type === \"slot\") {\n          // Migrate this to slot\n          updatedItems[id] = {\n            ...nodeData,\n            props: {\n              ...nodeData.props,\n              ...updatedItems[id]?.props,\n              [slotName]: content,\n            },\n          };\n\n          deletedCompounds.push(zoneCompound);\n        }\n\n        return content;\n      }\n\n      return content;\n    });\n\n    const updated = walkAppState(\n      appState,\n      config,\n      (content) => content,\n      (item) => {\n        return updatedItems[item.props.id] ?? item;\n      }\n    );\n\n    deletedCompounds.forEach((zoneCompound) => {\n      const [_, propName] = zoneCompound.split(\":\");\n      console.log(\n        `✓ Success: Migrated \"${zoneCompound}\" from DropZone to slot field \"${propName}\"`\n      );\n      delete updated.data.zones?.[zoneCompound];\n    });\n\n    // Migrate zones created by dynamic arrays\n    if (migrationOptions?.migrateDynamicZonesForComponent) {\n      const unmigratedZonesGrouped: Record<\n        string,\n        Record<string, Content>\n      > = {};\n\n      Object.keys(updated.data.zones ?? {}).forEach((zoneCompound) => {\n        const [componentId, propName] = zoneCompound.split(\":\");\n        const content = updated.data.zones?.[zoneCompound];\n\n        if (!content) {\n          return;\n        }\n\n        if (!unmigratedZonesGrouped[componentId]) {\n          unmigratedZonesGrouped[componentId] = {};\n        }\n\n        if (!unmigratedZonesGrouped[componentId][propName]) {\n          unmigratedZonesGrouped[componentId][propName] = content;\n        }\n      });\n\n      Object.keys(unmigratedZonesGrouped).forEach((componentId) => {\n        updated.data = walkTree(updated.data, config, (content) => {\n          return content.map((child) => {\n            if (child.props.id !== componentId) {\n              return child;\n            }\n\n            const migrateFn =\n              migrationOptions?.migrateDynamicZonesForComponent?.[child.type];\n\n            if (!migrateFn) {\n              return child;\n            }\n\n            const zones = unmigratedZonesGrouped[componentId];\n            const migratedProps = migrateFn(child.props, zones);\n\n            Object.keys(zones).forEach((propName) => {\n              const zoneCompound = `${componentId}:${propName}`;\n              console.log(`✓ Success: Migrated \"${zoneCompound}\" DropZone`);\n              delete updated.data.zones?.[zoneCompound];\n            });\n\n            return {\n              ...child,\n              props: migratedProps,\n            };\n          });\n        });\n      });\n    }\n\n    Object.keys(updated.data.zones ?? {}).forEach((zoneCompound) => {\n      const [_, propName] = zoneCompound.split(\":\");\n\n      throw new Error(\n        `Could not migrate DropZone \"${zoneCompound}\" to slot field. No slot exists with the name \"${propName}\".`\n      );\n    });\n\n    delete updated.data.zones;\n\n    return updated.data;\n  },\n];\n\nexport function migrate<UserConfig extends Config = Config>(\n  data: Data,\n  config?: UserConfig,\n  migrationOptions?: MigrationOptions<UserConfig>\n): Data {\n  return migrations?.reduce(\n    (acc, migration) => migration(acc, config, migrationOptions),\n    data\n  ) as Data;\n}\n"
  },
  {
    "path": "packages/core/lib/move-component.ts",
    "content": "import { useAppStoreApi } from \"../store\";\nimport { ItemSelector } from \"./data/get-item\";\nimport { getSelectorForId } from \"./get-selector-for-id\";\nimport { rootDroppableId } from \"./root-droppable-id\";\n\n/**\n * Moves a component, resolves its data, and updates the appStore state.\n * @param id - Id of the component to move.\n * @param sourceSelector - The current position of the component.\n * @param destinationSelector - The target position to move the component to.\n * @param appStore - The appStore instance where the component is.\n * @returns A promise that resolves when the move operation is complete.\n */\nexport const moveComponent = async (\n  id: string,\n  sourceSelector: ItemSelector,\n  destinationSelector: ItemSelector,\n  appStore: ReturnType<typeof useAppStoreApi>\n) => {\n  const dispatch = appStore.getState().dispatch;\n  dispatch({\n    type: \"move\",\n    sourceIndex: sourceSelector.index,\n    sourceZone: sourceSelector.zone ?? rootDroppableId,\n    destinationIndex: destinationSelector.index,\n    destinationZone: destinationSelector.zone ?? rootDroppableId,\n    recordHistory: false,\n  });\n\n  const componentData = appStore.getState().state.indexes.nodes[id]?.data;\n  if (!componentData) return;\n\n  const resolveComponentData = appStore.getState().resolveComponentData;\n  const resolvedData = await resolveComponentData(componentData, \"move\");\n\n  // Use latest position, in case it has moved\n  const latestItemSelector = getSelectorForId(\n    appStore.getState().state,\n    componentData.props.id\n  );\n  if (!latestItemSelector) return;\n\n  if (resolvedData.didChange)\n    dispatch({\n      type: \"replace\",\n      data: resolvedData.node,\n      destinationIndex: latestItemSelector.index,\n      destinationZone: latestItemSelector.zone ?? rootDroppableId,\n    });\n};\n"
  },
  {
    "path": "packages/core/lib/on-scroll-end.ts",
    "content": "export const onScrollEnd = (\n  frame: Document | Element | null | undefined,\n  cb: () => void\n) => {\n  let scrollTimeout: NodeJS.Timeout;\n\n  const callback = function () {\n    clearTimeout(scrollTimeout);\n    scrollTimeout = setTimeout(function () {\n      cb();\n\n      frame?.removeEventListener(\"scroll\", callback);\n    }, 50);\n  };\n\n  frame?.addEventListener(\"scroll\", callback);\n\n  // Abort if no scroll timeout was configured after 50ms\n  setTimeout(() => {\n    if (!scrollTimeout) {\n      cb();\n    }\n  }, 50);\n};\n"
  },
  {
    "path": "packages/core/lib/overlay-portal/index.tsx",
    "content": "import \"./styles.css\";\n\nexport const registerOverlayPortal = (\n  el: HTMLElement | null | undefined,\n  opts: {\n    disableDrag?: boolean;\n    disableDragOnFocus?: boolean;\n  } = {}\n) => {\n  if (!el) return;\n\n  const { disableDrag = false, disableDragOnFocus = true } = opts;\n\n  const stopPropagation = (e: MouseEvent) => {\n    e.stopPropagation();\n  };\n\n  el.addEventListener(\"mouseover\", stopPropagation, {\n    capture: true,\n  });\n\n  const onFocus = () => {\n    setTimeout(() => {\n      el.addEventListener(\"pointerdown\", stopPropagation, {\n        capture: true,\n      });\n    }, 200);\n  };\n\n  const onBlur = () => {\n    el.removeEventListener(\"pointerdown\", stopPropagation, {\n      capture: true,\n    });\n  };\n\n  if (disableDrag) {\n    el.addEventListener(\"pointerdown\", stopPropagation, {\n      capture: true,\n    });\n  } else if (disableDragOnFocus) {\n    el.addEventListener(\"focus\", onFocus, { capture: true });\n    el.addEventListener(\"blur\", onBlur, { capture: true });\n  }\n\n  el.setAttribute(\"data-puck-overlay-portal\", \"true\");\n\n  return () => {\n    el.removeEventListener(\"mouseover\", stopPropagation, {\n      capture: true,\n    });\n\n    if (disableDrag) {\n      el.removeEventListener(\"pointerdown\", stopPropagation, {\n        capture: true,\n      });\n    } else if (disableDragOnFocus) {\n      el.removeEventListener(\"focus\", onFocus, { capture: true });\n      el.removeEventListener(\"blur\", onBlur, { capture: true });\n    }\n\n    el.removeAttribute(\"data-puck-overlay-portal\");\n  };\n};\n"
  },
  {
    "path": "packages/core/lib/overlay-portal/styles.css",
    "content": "[data-puck-overlay-portal],\n[data-puck-overlay-portal] * {\n  pointer-events: auto !important;\n}\n\n[data-puck-overlay-portal]:hover {\n  outline: 2px var(--puck-color-azure-09) dashed;\n  outline-offset: 2px;\n}\n\n[data-puck-overlay-portal]:focus-within {\n  outline: 2px var(--puck-color-azure-07) dashed;\n  outline-offset: 2px;\n}\n"
  },
  {
    "path": "packages/core/lib/plugin-debug.tsx",
    "content": "/* eslint-disable react-hooks/rules-of-hooks */\nimport { createUsePuck } from \"./use-puck\";\nimport { Plugin } from \"../types\";\nimport { ActionBar } from \"../components/ActionBar\";\nimport { IconButton } from \"../components/IconButton\";\nimport { LogsIcon } from \"lucide-react\";\nimport { useAppStoreApi } from \"../store\";\n\nconst usePuck = createUsePuck();\n\nexport const debugPlugin: Plugin = {\n  overrides: {\n    actionBar: ({ children, parentAction }) => {\n      const item = usePuck((s) => s.selectedItem);\n      return (\n        <ActionBar>\n          <ActionBar.Group>\n            {parentAction}\n            <ActionBar.Label label={item?.props.id} />\n          </ActionBar.Group>\n\n          <ActionBar.Group>{children}</ActionBar.Group>\n          <ActionBar.Group>\n            <ActionBar.Action onClick={() => console.log(item)}>\n              <LogsIcon />\n            </ActionBar.Action>\n          </ActionBar.Group>\n        </ActionBar>\n      );\n    },\n    headerActions: ({ children }) => {\n      const appStoreApi = useAppStoreApi();\n\n      return (\n        <>\n          <IconButton\n            onClick={() => {\n              // No way to get appState without re-rendering\n              console.log(appStoreApi.getState());\n            }}\n            title=\"Log state\"\n          >\n            <LogsIcon />\n          </IconButton>\n          {children}\n        </>\n      );\n    },\n  },\n};\n"
  },
  {
    "path": "packages/core/lib/resolve-all-data.ts",
    "content": "import {\n  ComponentData,\n  Config,\n  Content,\n  Data,\n  DefaultComponents,\n  DefaultRootFieldProps,\n  Metadata,\n  RootData,\n} from \"../types\";\nimport { resolveComponentData } from \"./resolve-component-data\";\nimport { groupZonesByComponent } from \"./group-zones-by-component\";\nimport { defaultData } from \"./data/default-data\";\nimport { toComponent } from \"./data/to-component\";\nimport { mapFields } from \"./data/map-fields\";\n\nexport async function resolveAllData<\n  Components extends DefaultComponents = DefaultComponents,\n  RootProps extends Record<string, any> = DefaultRootFieldProps\n>(\n  data: Partial<Data>,\n  config: Config,\n  metadata: Metadata = {},\n  onResolveStart?: (item: ComponentData) => void,\n  onResolveEnd?: (item: ComponentData) => void\n) {\n  const defaultedData = defaultData(data);\n\n  const zonesByComponent = groupZonesByComponent(defaultedData);\n\n  let resolvedZones: Record<string, Content> = {};\n\n  const resolveNode = async <T extends ComponentData | RootData>(\n    _node: T,\n    parent: ComponentData | null\n  ) => {\n    const node = toComponent(_node);\n\n    onResolveStart?.(node);\n\n    const resolved = (\n      await resolveComponentData(\n        node,\n        config,\n        metadata,\n        () => {},\n        () => {},\n        \"force\",\n        parent\n      )\n    ).node as T;\n\n    const resolvedAsComponent = toComponent(resolved);\n\n    // Resolve any slots concurrently\n    const resolvedDeepPromise = mapFields(\n      resolved,\n      {\n        slot: ({ value }) => processContent(value, resolvedAsComponent),\n      },\n      config\n    ) as unknown as Promise<T>;\n\n    let resolveZonePromises: Promise<void>[] = [];\n\n    // Resolve any zones concurrently\n    if (zonesByComponent[resolvedAsComponent.props.id]) {\n      resolveZonePromises = zonesByComponent[resolvedAsComponent.props.id].map(\n        async ({ zoneCompound, content }) => {\n          resolvedZones[zoneCompound] = await processContent(\n            content,\n            resolvedAsComponent\n          );\n        }\n      );\n    }\n\n    // Await all concurrent children\n    const resolvedDeep = await resolvedDeepPromise;\n    await Promise.all(resolveZonePromises);\n\n    onResolveEnd?.(toComponent(resolvedDeep));\n\n    return resolvedDeep;\n  };\n\n  const processContent = async (\n    content: Content,\n    parent: ComponentData | null\n  ) => {\n    return Promise.all(content.map((item) => resolveNode(item, parent)));\n  };\n\n  const result: Data = defaultData({});\n\n  result.root = await resolveNode(defaultedData.root, null);\n  result.content = await processContent(\n    defaultedData.content,\n    toComponent(result.root)\n  );\n  result.zones = resolvedZones;\n\n  return result as Data<Components, RootProps>;\n}\n"
  },
  {
    "path": "packages/core/lib/resolve-component-data.ts",
    "content": "import {\n  ComponentData,\n  Config,\n  Content,\n  Metadata,\n  ResolveDataTrigger,\n  RootDataWithProps,\n} from \"../types\";\nimport { mapFields } from \"./data/map-fields\";\nimport { toComponent } from \"./data/to-component\";\nimport { getChanged } from \"./get-changed\";\nimport { deepEqual } from \"fast-equals\";\n\nexport const cache: {\n  lastChange: Record<string, any>;\n} = { lastChange: {} };\n\nexport const resolveComponentData = async <\n  T extends ComponentData | RootDataWithProps\n>(\n  item: T,\n  config: Config,\n  metadata: Metadata = {},\n  onResolveStart?: (item: T) => void,\n  onResolveEnd?: (item: T) => void,\n  trigger: ResolveDataTrigger = \"replace\",\n  parent: ComponentData | null = null\n) => {\n  const configForItem =\n    \"type\" in item && item.type !== \"root\"\n      ? config.components[item.type]\n      : config.root;\n\n  const resolvedItem: T = {\n    ...item,\n  };\n\n  const shouldRunResolver = configForItem?.resolveData && item.props;\n\n  const id = \"id\" in item.props ? item.props.id : \"root\";\n\n  if (shouldRunResolver) {\n    const {\n      item: oldItem = null,\n      resolved = {},\n      parentId: oldParentId = null,\n    } = cache.lastChange[id] || {};\n    // Skip inserted nodes for \"move\" trigger\n    // This is done this way to mitigate race conditions on insertion\n    const isRootOrInserted = oldParentId === null;\n    const parentChanged = !isRootOrInserted && parent?.props.id !== oldParentId;\n    const dataChanged = item && !deepEqual(item, oldItem);\n\n    const shouldSkip =\n      (trigger === \"move\" && !parentChanged) ||\n      (trigger !== \"move\" && trigger !== \"force\" && !dataChanged);\n\n    if (shouldSkip) {\n      return { node: resolved, didChange: false };\n    }\n\n    const changed = getChanged(item, oldItem) as any;\n\n    if (onResolveStart) {\n      onResolveStart(item);\n    }\n\n    const { props: resolvedProps, readOnly = {} } =\n      await configForItem.resolveData!(item, {\n        changed,\n        lastData: oldItem,\n        metadata: { ...metadata, ...configForItem.metadata },\n        trigger,\n        parent,\n      });\n\n    resolvedItem.props = {\n      ...item.props,\n      ...resolvedProps,\n    };\n\n    if (Object.keys(readOnly).length) {\n      resolvedItem.readOnly = readOnly;\n    }\n  }\n\n  const itemAsComponentData: ComponentData = toComponent(resolvedItem);\n\n  let itemWithResolvedChildren = await mapFields(\n    resolvedItem,\n    {\n      slot: async ({ value }) => {\n        const content = value as Content;\n\n        return await Promise.all(\n          content.map(\n            async (childItem) =>\n              (\n                await resolveComponentData(\n                  childItem as T,\n                  config,\n                  metadata,\n                  onResolveStart,\n                  onResolveEnd,\n                  trigger,\n                  itemAsComponentData\n                )\n              ).node\n          )\n        );\n      },\n    },\n    config\n  );\n\n  if (shouldRunResolver && onResolveEnd) {\n    onResolveEnd(resolvedItem);\n  }\n\n  cache.lastChange[id] = {\n    item: item,\n    resolved: itemWithResolvedChildren,\n    parentId: parent?.props.id,\n  };\n\n  return {\n    node: itemWithResolvedChildren,\n    didChange: !deepEqual(item, itemWithResolvedChildren),\n  };\n};\n"
  },
  {
    "path": "packages/core/lib/root-droppable-id.ts",
    "content": "export const rootAreaId = \"root\";\nexport const rootZone = \"default-zone\";\nexport const rootDroppableId = `${rootAreaId}:${rootZone}`;\n"
  },
  {
    "path": "packages/core/lib/scroll-into-view.ts",
    "content": "export const scrollIntoView = (el: HTMLElement) => {\n  const oldStyle = { ...el.style };\n\n  el.style.scrollMargin = \"256px\";\n\n  if (el) {\n    el?.scrollIntoView({ behavior: \"smooth\" });\n\n    el.style.scrollMargin = oldStyle.scrollMargin || \"\";\n  }\n};\n"
  },
  {
    "path": "packages/core/lib/shallow-equal.ts",
    "content": "/**\n * Does a shallow equality check between two objects, ignoring specified keys.\n * @returns true if the objects are shallowly equal (excluding ignored keys), false otherwise.\n */\nexport function shallowEqual(\n  obj1: any,\n  obj2: any,\n  keysToIgnore: readonly string[] = []\n): boolean {\n  // Quick reference/value check\n  if (Object.is(obj1, obj2)) return true;\n\n  // Check for null and non-objects\n  if (\n    typeof obj1 !== \"object\" ||\n    obj1 === null ||\n    typeof obj2 !== \"object\" ||\n    obj2 === null\n  ) {\n    return false;\n  }\n\n  // Guard against non-POJO false positives (e.g., Date, RegExp)\n  if (Object.getPrototypeOf(obj1) !== Object.getPrototypeOf(obj2)) {\n    return false;\n  }\n\n  const ignore = new Set(keysToIgnore);\n\n  const keys1 = Object.keys(obj1).filter((k) => !ignore.has(k));\n  const keys2 = Object.keys(obj2).filter((k) => !ignore.has(k));\n\n  if (keys1.length !== keys2.length) return false;\n\n  for (let i = 0; i < keys1.length; i++) {\n    const currKey = keys1[i];\n\n    // Check if obj2 has the key\n    if (!Object.prototype.hasOwnProperty.call(obj2, currKey)) return false;\n\n    // Check if values are the same\n    const val1 = obj1[currKey];\n    const val2 = obj2[currKey];\n    if (!Object.is(val1, val2)) return false;\n  }\n\n  return true;\n}\n"
  },
  {
    "path": "packages/core/lib/throttle.ts",
    "content": "export function timeout(callback: () => void, duration: number): () => void {\n  const id = setTimeout(callback, duration);\n\n  return () => clearTimeout(id);\n}\n\nexport function throttle<T extends (...args: any[]) => any>(\n  func: T,\n  limit: number\n): (...args: Parameters<T>) => void {\n  const time = () => performance.now();\n  let cancel: (() => void) | undefined;\n  let lastRan = 0; // Start with 0 to indicate it hasn't run yet\n\n  return function (this: any, ...args: Parameters<T>) {\n    const now = time();\n    const context = this;\n\n    if (now - lastRan >= limit) {\n      // If enough time has passed, run the function immediately\n      func.apply(context, args);\n      lastRan = now;\n    } else {\n      // Otherwise, schedule it to run after the remaining time\n      cancel?.(); // Cancel any previously scheduled call\n      cancel = timeout(() => {\n        func.apply(context, args);\n        lastRan = time();\n      }, limit - (now - lastRan));\n    }\n  };\n}\n"
  },
  {
    "path": "packages/core/lib/transform-props.ts",
    "content": "import { walkTree } from \"./data/walk-tree\";\nimport {\n  Config,\n  Data,\n  DefaultComponentProps,\n  DefaultComponents,\n  DefaultRootFieldProps,\n} from \"../types\";\nimport { defaultData } from \"./data/default-data\";\n\ntype PropTransform<\n  Components extends DefaultComponents = DefaultComponents,\n  RootProps extends DefaultComponentProps = DefaultRootFieldProps\n> = Partial<\n  {\n    [ComponentName in keyof Components]: (\n      props: Components[ComponentName] & { [key: string]: any }\n    ) => Components[ComponentName];\n  } & { root: (props: RootProps & { [key: string]: any }) => RootProps }\n>;\n\nexport function transformProps<\n  Components extends DefaultComponents = DefaultComponents,\n  RootProps extends DefaultComponentProps = DefaultRootFieldProps\n>(\n  data: Partial<Data>,\n  propTransforms: PropTransform<Components, RootProps>,\n  config: Config = { components: {} }\n): Data {\n  const mapItem = (item: any) => {\n    if (propTransforms[item.type]) {\n      return {\n        ...item,\n        props: {\n          id: item.props.id,\n          ...propTransforms[item.type]!(item.props as any),\n        },\n      };\n    }\n\n    return item;\n  };\n\n  const defaultedData = defaultData(data);\n\n  // DEPRECATED - handle legacy root props\n  const rootProps = defaultedData.root.props || defaultedData.root;\n  let newRoot = { ...defaultedData.root };\n  if (propTransforms[\"root\"]) {\n    newRoot.props = propTransforms[\"root\"](rootProps as any);\n  }\n\n  const dataWithUpdatedRoot = { ...defaultedData, root: newRoot };\n\n  const updatedData = walkTree(dataWithUpdatedRoot, config, (content) =>\n    content.map(mapItem)\n  );\n\n  // DEPRECATED - handle legacy root props\n  if (!defaultedData.root.props) {\n    updatedData.root = updatedData.root.props as any;\n  }\n\n  return updatedData;\n}\n"
  },
  {
    "path": "packages/core/lib/use-breadcrumbs.ts",
    "content": "import { useMemo } from \"react\";\nimport { useAppStore, useAppStoreApi } from \"../store\";\nimport { ItemSelector } from \"./data/get-item\";\n\nexport type Breadcrumb = {\n  label: string;\n  selector: ItemSelector | null;\n  zoneCompound?: string;\n};\n\nexport const useBreadcrumbs = (renderCount?: number) => {\n  const selectedId = useAppStore((s) => s.selectedItem?.props.id);\n  const config = useAppStore((s) => s.config);\n  const path = useAppStore((s) => s.state.indexes.nodes[selectedId]?.path);\n  const appStore = useAppStoreApi();\n\n  return useMemo<Breadcrumb[]>(() => {\n    const breadcrumbs =\n      path?.map((zoneCompound) => {\n        const [componentId] = zoneCompound.split(\":\");\n\n        if (componentId === \"root\") {\n          return {\n            label: config?.root?.label || \"Page\",\n            selector: null,\n          };\n        }\n\n        const node = appStore.getState().state.indexes.nodes[componentId];\n        const parentId = node.path[node.path.length - 1];\n        const contentIds =\n          appStore.getState().state.indexes.zones[parentId]?.contentIds || [];\n        const index = contentIds.indexOf(componentId);\n\n        const label = node\n          ? config.components[node.data.type]?.label ?? node.data.type\n          : \"Component\";\n\n        return {\n          label,\n          selector: node\n            ? {\n                index,\n                zone: node.path[node.path.length - 1],\n              }\n            : null,\n        };\n      }) || [];\n\n    if (renderCount) {\n      return breadcrumbs.slice(breadcrumbs.length - renderCount);\n    }\n\n    return breadcrumbs;\n  }, [path, renderCount]);\n};\n"
  },
  {
    "path": "packages/core/lib/use-component-list.tsx",
    "content": "import { ReactNode, useEffect, useState } from \"react\";\nimport { ComponentList } from \"../components/ComponentList\";\nimport { useAppStore } from \"../store\";\n\nexport const useComponentList = () => {\n  const [componentList, setComponentList] = useState<ReactNode[]>();\n  const config = useAppStore((s) => s.config);\n  const uiComponentList = useAppStore((s) => s.state.ui.componentList);\n\n  useEffect(() => {\n    if (Object.keys(uiComponentList).length > 0) {\n      const matchedComponents: string[] = [];\n\n      let _componentList: ReactNode[];\n\n      _componentList = Object.entries(uiComponentList).map(\n        ([categoryKey, category]) => {\n          if (!category.components) {\n            return null;\n          }\n\n          category.components.forEach((componentName) => {\n            matchedComponents.push(componentName as string);\n          });\n\n          if (category.visible === false) {\n            return null;\n          }\n\n          return (\n            <ComponentList\n              id={categoryKey}\n              key={categoryKey}\n              title={category.title || categoryKey}\n            >\n              {category.components.map((componentName, i) => {\n                const componentConf = config.components[componentName] || {};\n\n                return (\n                  <ComponentList.Item\n                    key={componentName}\n                    label={(componentConf[\"label\"] ?? componentName) as string}\n                    name={componentName as string}\n                    index={i}\n                  />\n                );\n              })}\n            </ComponentList>\n          );\n        }\n      );\n\n      const remainingComponents = Object.keys(config.components).filter(\n        (component) => matchedComponents.indexOf(component) === -1\n      );\n\n      if (\n        remainingComponents.length > 0 &&\n        !uiComponentList.other?.components &&\n        uiComponentList.other?.visible !== false\n      ) {\n        _componentList.push(\n          <ComponentList\n            id=\"other\"\n            key=\"other\"\n            title={uiComponentList.other?.title || \"Other\"}\n          >\n            {remainingComponents.map((componentName, i) => {\n              const componentConf = config.components[componentName] || {};\n\n              return (\n                <ComponentList.Item\n                  key={componentName}\n                  name={componentName as string}\n                  label={(componentConf[\"label\"] ?? componentName) as string}\n                  index={i}\n                />\n              );\n            })}\n          </ComponentList>\n        );\n      }\n\n      setComponentList(_componentList);\n    }\n  }, [config.categories, config.components, uiComponentList]);\n\n  return componentList;\n};\n"
  },
  {
    "path": "packages/core/lib/use-context-store.tsx",
    "content": "import { Context, createContext, ReactNode, useContext, useState } from \"react\";\nimport { createStore, StoreApi, useStore } from \"zustand\";\nimport { subscribeWithSelector } from \"zustand/middleware\";\nimport { useShallow } from \"zustand/react/shallow\";\n\ntype ExtractState<S> = S extends {\n  getState: () => infer T;\n}\n  ? T\n  : never;\n\n/**\n * Use a Zustand store via context\n */\nexport function useContextStore<T, U>(\n  context: Context<StoreApi<T>>,\n  selector: (s: ExtractState<StoreApi<T>>) => U\n): U {\n  const store = useContext(context);\n\n  if (!store) {\n    throw new Error(\"useContextStore must be used inside context\");\n  }\n\n  return useStore<StoreApi<T>, U>(store, useShallow(selector));\n}\n\nexport function createStoreProvider<ValueType>(\n  ContextComponent: Context<StoreApi<ValueType>>\n) {\n  const StoreProvider = ({\n    children,\n    value,\n  }: {\n    children: ReactNode;\n    value: ValueType;\n  }) => {\n    const [store] = useState(() => createStore<ValueType>(() => value));\n\n    return (\n      <ContextComponent.Provider value={store}>\n        {children}\n      </ContextComponent.Provider>\n    );\n  };\n\n  return StoreProvider;\n}\n\nexport function createContextStore<ValueType>(defaultValue: ValueType) {\n  const ctx = createContext<StoreApi<ValueType>>(\n    createStore(subscribeWithSelector(() => defaultValue))\n  );\n\n  return {\n    ctx,\n    Provider: createStoreProvider(ctx),\n  };\n}\n"
  },
  {
    "path": "packages/core/lib/use-delete-hotkeys.ts",
    "content": "import { useCallback } from \"react\";\nimport { useHotkey } from \"./use-hotkey\";\nimport { useAppStoreApi } from \"../store\";\n\nconst isElementVisible = (element: HTMLElement): boolean => {\n  let current: HTMLElement | null = element;\n\n  while (current && current !== document.body) {\n    const style = window.getComputedStyle(current);\n\n    if (\n      style.display === \"none\" ||\n      style.visibility === \"hidden\" ||\n      style.opacity === \"0\" ||\n      current.getAttribute(\"aria-hidden\") === \"true\" ||\n      current.hasAttribute(\"hidden\")\n    ) {\n      return false;\n    }\n\n    current = current.parentElement;\n  }\n\n  return true;\n};\n\nconst shouldBlockDeleteHotkey = (e?: KeyboardEvent): boolean => {\n  if (e?.defaultPrevented) return true;\n\n  const origin =\n    (e?.composedPath?.()[0] as Element | undefined) ||\n    (e?.target as Element | undefined) ||\n    (document.activeElement as Element | null);\n\n  if (origin instanceof HTMLElement) {\n    const tag = origin.tagName.toLowerCase();\n\n    if (tag === \"input\" || tag === \"textarea\" || tag === \"select\") return true;\n    if (origin.isContentEditable) return true;\n\n    const role = origin.getAttribute(\"role\");\n    if (\n      role === \"textbox\" ||\n      role === \"combobox\" ||\n      role === \"searchbox\" ||\n      role === \"listbox\" ||\n      role === \"grid\"\n    ) {\n      return true;\n    }\n  }\n\n  const modal = document.querySelector(\n    'dialog[open], [aria-modal=\"true\"], [role=\"dialog\"], [role=\"alertdialog\"]'\n  );\n\n  if (modal && isElementVisible(modal as HTMLElement)) {\n    return true;\n  }\n\n  return false;\n};\n\nexport const useDeleteHotkeys = () => {\n  const appStore = useAppStoreApi();\n\n  const deleteSelectedComponent = useCallback(\n    (e?: KeyboardEvent) => {\n      if (shouldBlockDeleteHotkey(e)) {\n        return false;\n      }\n\n      const { state, dispatch, permissions, selectedItem } =\n        appStore.getState();\n      const sel = state.ui?.itemSelector;\n\n      // Swallow key in canvas context to avoid browser back navigation.\n      if (!sel?.zone || !selectedItem) return true;\n\n      if (!permissions.getPermissions({ item: selectedItem }).delete)\n        return true;\n\n      dispatch({\n        type: \"remove\",\n        index: sel.index,\n        zone: sel.zone,\n      });\n      return true;\n    },\n    [appStore]\n  );\n\n  useHotkey({ delete: true }, deleteSelectedComponent);\n  useHotkey({ backspace: true }, deleteSelectedComponent);\n};\n"
  },
  {
    "path": "packages/core/lib/use-frame.ts",
    "content": "import { useEffect, useState } from \"react\";\nimport { getFrame } from \"./get-frame\";\n\nexport const useFrame = () => {\n  const [el, setEl] = useState<Document>();\n\n  useEffect(() => {\n    const frame = getFrame();\n\n    if (frame) {\n      setEl(frame);\n    }\n  }, []);\n\n  return el;\n};\n"
  },
  {
    "path": "packages/core/lib/use-hotkey.ts",
    "content": "import { useEffect } from \"react\";\nimport { create } from \"zustand\";\nimport { subscribeWithSelector } from \"zustand/middleware\";\n\nconst keys = [\n  \"ctrl\",\n  \"meta\",\n  \"shift\",\n  \"a\",\n  \"b\",\n  \"c\",\n  \"d\",\n  \"e\",\n  \"f\",\n  \"g\",\n  \"h\",\n  \"i\",\n  \"j\",\n  \"k\",\n  \"l\",\n  \"m\",\n  \"n\",\n  \"o\",\n  \"p\",\n  \"q\",\n  \"r\",\n  \"s\",\n  \"t\",\n  \"u\",\n  \"v\",\n  \"w\",\n  \"x\",\n  \"y\",\n  \"z\",\n  \"delete\",\n  \"backspace\",\n  \"altRight\",\n] as const;\n\ntype KeyStrict = (typeof keys)[number];\ntype KeyMapStrict = Partial<Record<KeyStrict, boolean>>;\ntype KeyMap = Partial<Record<string, boolean>>;\ntype KeyCodeMap = Record<string, KeyStrict>;\n\nconst keyCodeMap: KeyCodeMap = {\n  ControlLeft: \"ctrl\",\n  ControlRight: \"ctrl\",\n  MetaLeft: \"meta\",\n  MetaRight: \"meta\",\n  ShiftLeft: \"shift\",\n  ShiftRight: \"shift\",\n  KeyA: \"a\",\n  KeyB: \"b\",\n  KeyC: \"c\",\n  KeyD: \"d\",\n  KeyE: \"e\",\n  KeyF: \"f\",\n  KeyG: \"g\",\n  KeyH: \"h\",\n  KeyI: \"i\",\n  KeyJ: \"j\",\n  KeyK: \"k\",\n  KeyL: \"l\",\n  KeyM: \"m\",\n  KeyN: \"n\",\n  KeyO: \"o\",\n  KeyP: \"p\",\n  KeyQ: \"q\",\n  KeyR: \"r\",\n  KeyS: \"s\",\n  KeyT: \"t\",\n  KeyU: \"u\",\n  KeyV: \"v\",\n  KeyW: \"w\",\n  KeyX: \"x\",\n  KeyY: \"y\",\n  KeyZ: \"z\",\n  Delete: \"delete\",\n  Backspace: \"backspace\",\n  AltRight: \"altRight\",\n};\n\nconst useHotkeyStore = create<{\n  held: KeyMap;\n  hold: (key: string) => void;\n  release: (key: string) => void;\n  reset: (held?: KeyMapStrict) => void;\n  triggers: Record<string, { combo: KeyMapStrict; cb: Function }>;\n}>()(\n  subscribeWithSelector((set) => ({\n    held: {},\n    hold: (key) =>\n      set((s) => (s.held[key] ? s : { held: { ...s.held, [key]: true } })),\n    release: (key) =>\n      set((s) => (s.held[key] ? { held: { ...s.held, [key]: false } } : s)),\n    reset: (held = {}) => set(() => ({ held })),\n    triggers: {},\n  }))\n);\n\nexport const monitorHotkeys = (doc: Document) => {\n  const onKeyDown = (e: KeyboardEvent) => {\n    // If altGraphKey (Alt Right) is pressed, register altRight instead of mapping ControlRight to ctrl\n    if (e.getModifierState(\"AltGraph\")) {\n      useHotkeyStore.getState().hold(\"altRight\");\n      return;\n    }\n\n    const key = keyCodeMap[e.code];\n\n    if (key) {\n      useHotkeyStore.getState().hold(key);\n\n      const { held, triggers } = useHotkeyStore.getState();\n\n      Object.values(triggers).forEach(({ combo, cb }) => {\n        const conditionMet =\n          Object.entries(combo).every(\n            ([key, value]) => value === !!held[key]\n          ) &&\n          Object.entries(held).every(\n            ([key, value]) => value === !!(combo as KeyMap)[key]\n          );\n\n        // Call hotkey with event; skip preventDefault if callback returns false to allow native input behavior.\n        if (conditionMet) {\n          const handled = cb(e);\n          if (handled !== false) {\n            e.preventDefault();\n          }\n        }\n      });\n\n      // Only retain hold on modifiers\n      if (key !== \"meta\" && key !== \"ctrl\" && key !== \"shift\") {\n        useHotkeyStore.getState().release(key);\n      }\n    }\n  };\n\n  const onKeyUp = (e: KeyboardEvent) => {\n    // Check if Alt Right (AltGraph) was released\n    if (!e.getModifierState(\"AltGraph\") && e.code === \"ControlRight\") {\n      useHotkeyStore.getState().release(\"altRight\");\n      return;\n    }\n\n    const key = keyCodeMap[e.code];\n\n    if (key) {\n      if (key === \"meta\") {\n        // Release all keys when releasing meta, as macOS prevents keyUp detection from other keys when meta is held\n        useHotkeyStore.getState().reset();\n      } else {\n        useHotkeyStore.getState().release(key);\n      }\n    }\n  };\n\n  const onVisibilityChanged = (e: Event) => {\n    // Reset keys when tab changes\n    if (document.visibilityState === \"hidden\") {\n      useHotkeyStore.getState().reset();\n    }\n  };\n\n  const onBlur = () => {\n    useHotkeyStore.getState().reset();\n  };\n\n  window.addEventListener(\"blur\", onBlur);\n  doc.addEventListener(\"keydown\", onKeyDown);\n  doc.addEventListener(\"keyup\", onKeyUp);\n  doc.addEventListener(\"visibilitychange\", onVisibilityChanged);\n\n  return () => {\n    doc.removeEventListener(\"keydown\", onKeyDown);\n    doc.removeEventListener(\"keyup\", onKeyUp);\n    doc.removeEventListener(\"visibilitychange\", onVisibilityChanged);\n    window.removeEventListener(\"blur\", onBlur);\n  };\n};\n\nexport const useMonitorHotkeys = () => {\n  useEffect(() => monitorHotkeys(document), []);\n};\n\nexport const useHotkey = (combo: KeyMapStrict, cb: Function) => {\n  useEffect(\n    () =>\n      useHotkeyStore.setState((s) => ({\n        triggers: {\n          ...s.triggers,\n          [`${Object.keys(combo).join(\"+\")}`]: { combo, cb },\n        },\n      })),\n    []\n  );\n};\n"
  },
  {
    "path": "packages/core/lib/use-inject-css.ts",
    "content": "import { useEffect, useState } from \"react\";\nimport { getFrame } from \"./get-frame\";\n\nconst styles = ``;\n\nexport const useInjectStyleSheet = (\n  initialStyles: string,\n  iframeEnabled?: boolean\n) => {\n  const [el, setEl] = useState<HTMLStyleElement>();\n\n  useEffect(() => {\n    setEl(document.createElement(\"style\"));\n  }, []);\n\n  useEffect(() => {\n    if (!el || typeof window === \"undefined\") {\n      return;\n    }\n\n    el.innerHTML = initialStyles;\n\n    if (iframeEnabled) {\n      const frame = getFrame();\n      frame?.head?.appendChild(el);\n    }\n\n    document.head.appendChild(el);\n  }, [iframeEnabled, el]);\n\n  return el;\n};\n\nexport const useInjectGlobalCss = (iframeEnabled?: boolean) => {\n  return useInjectStyleSheet(styles, iframeEnabled);\n};\n"
  },
  {
    "path": "packages/core/lib/use-loaded-overrides.ts",
    "content": "import { useMemo } from \"react\";\nimport { loadOverrides } from \"./load-overrides\";\nimport { Overrides, Plugin } from \"../types\";\n\nexport const useLoadedOverrides = ({\n  overrides,\n  plugins,\n}: {\n  overrides?: Partial<Overrides>;\n  plugins?: Plugin[];\n}) => {\n  return useMemo(() => {\n    return loadOverrides({ overrides, plugins });\n  }, [plugins, overrides]);\n};\n"
  },
  {
    "path": "packages/core/lib/use-on-value-change.ts",
    "content": "import { useRef, useEffect } from \"react\";\n\nexport function useOnValueChange<T>(\n  value: T,\n  onChange: (value: T, oldValue: T) => void,\n  compare: (value: T, oldValue: T) => boolean = Object.is,\n  deps: any[] = []\n) {\n  const tracked = useRef<T>(value);\n\n  useEffect(() => {\n    const oldValue = tracked.current;\n\n    if (!compare(value, oldValue)) {\n      tracked.current = value;\n      onChange(value, oldValue);\n    }\n  }, [onChange, compare, value, ...deps]);\n}\n"
  },
  {
    "path": "packages/core/lib/use-parent.ts",
    "content": "import { useAppStore, useAppStoreApi } from \"../store\";\n\nexport const useParent = () => {\n  const appStore = useAppStoreApi();\n\n  const selectedItem = appStore.getState().selectedItem;\n  const parent = useAppStore((s) => {\n    const node = s.state.indexes.nodes[selectedItem?.props.id];\n    return node?.parentId ? s.state.indexes.nodes[node.parentId] : null;\n  });\n\n  return parent?.data ?? null;\n};\n"
  },
  {
    "path": "packages/core/lib/use-preview-mode-hotkeys.ts",
    "content": "import { useCallback } from \"react\";\nimport { useHotkey } from \"./use-hotkey\";\nimport { useAppStoreApi } from \"../store\";\n\nexport const usePreviewModeHotkeys = () => {\n  const appStore = useAppStoreApi();\n  const toggleInteractive = useCallback(() => {\n    const dispatch = appStore.getState().dispatch;\n\n    dispatch({\n      type: \"setUi\",\n      ui: (ui) => ({\n        previewMode: ui.previewMode === \"edit\" ? \"interactive\" : \"edit\",\n      }),\n    });\n  }, [appStore]);\n\n  useHotkey({ meta: true, i: true }, toggleInteractive);\n  useHotkey({ ctrl: true, i: true }, toggleInteractive); // Windows\n};\n"
  },
  {
    "path": "packages/core/lib/use-puck.ts",
    "content": "import {\n  Config,\n  UserGenerics,\n  ResolveDataTrigger,\n  ComponentData,\n} from \"../types\";\nimport { createContext, useContext, useEffect, useState } from \"react\";\nimport { AppStore, useAppStoreApi } from \"../store\";\nimport {\n  GetPermissions,\n  RefreshPermissions,\n} from \"../store/slices/permissions\";\nimport { HistorySlice } from \"../store/slices/history\";\nimport { createStore, StoreApi, useStore } from \"zustand\";\nimport { makeStatePublic } from \"./data/make-state-public\";\nimport { getItem, ItemSelector } from \"./data/get-item\";\nimport { resolveDataById } from \"./data/resolve-data-by-id\";\nimport { resolveDataBySelector } from \"./data/resolve-data-by-selector\";\nimport { getSelectorForId } from \"./get-selector-for-id\";\n\nexport type UsePuckData<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n> = {\n  appState: G[\"UserPublicAppState\"];\n  config: UserConfig;\n  dispatch: AppStore[\"dispatch\"];\n  getPermissions: GetPermissions<UserConfig>;\n  refreshPermissions: RefreshPermissions<UserConfig>;\n  resolveDataById: (id: string, trigger?: ResolveDataTrigger) => void;\n  resolveDataBySelector: (\n    selector: ItemSelector,\n    trigger?: ResolveDataTrigger\n  ) => void;\n  selectedItem: G[\"UserComponentData\"] | null;\n  getItemBySelector: (\n    selector: ItemSelector\n  ) => G[\"UserComponentData\"] | undefined;\n  getItemById: (id: string) => G[\"UserComponentData\"] | undefined;\n  getSelectorForId: (id: string) => Required<ItemSelector> | undefined;\n  getParentById: (id: string) => ComponentData | undefined;\n  history: {\n    back: HistorySlice[\"back\"];\n    forward: HistorySlice[\"forward\"];\n    setHistories: HistorySlice[\"setHistories\"];\n    setHistoryIndex: HistorySlice[\"setHistoryIndex\"];\n    histories: HistorySlice[\"histories\"];\n    index: HistorySlice[\"index\"];\n    hasPast: boolean;\n    hasFuture: boolean;\n  };\n};\n\nexport type PuckApi<UserConfig extends Config = Config> =\n  UsePuckData<UserConfig>;\n\ntype UsePuckStore<UserConfig extends Config = Config> = PuckApi<UserConfig>;\n\ntype PickedStore = Pick<\n  AppStore,\n  \"config\" | \"dispatch\" | \"selectedItem\" | \"permissions\" | \"history\" | \"state\"\n>;\n\nexport const generateUsePuck = (\n  store: PickedStore,\n  getState: ReturnType<typeof useAppStoreApi>[\"getState\"]\n): UsePuckStore => {\n  const history: UsePuckStore[\"history\"] = {\n    back: store.history.back,\n    forward: store.history.forward,\n    setHistories: store.history.setHistories,\n    setHistoryIndex: store.history.setHistoryIndex,\n    hasPast: store.history.hasPast(),\n    hasFuture: store.history.hasFuture(),\n    histories: store.history.histories,\n    index: store.history.index,\n  };\n\n  const storeData: PuckApi = {\n    appState: makeStatePublic(store.state),\n    config: store.config,\n    dispatch: store.dispatch,\n    getPermissions: store.permissions.getPermissions,\n    refreshPermissions: store.permissions.refreshPermissions,\n    resolveDataById: (id, trigger) => resolveDataById(id, getState, trigger),\n    resolveDataBySelector: (selector, trigger) =>\n      resolveDataBySelector(selector, getState, trigger),\n    history,\n    selectedItem: store.selectedItem || null,\n    getItemBySelector: (selector) => getItem(selector, store.state),\n    getItemById: (id) => store.state.indexes.nodes[id].data,\n    getSelectorForId: (id) => getSelectorForId(store.state, id),\n    getParentById: (id) => {\n      const node = store.state.indexes.nodes[id];\n      const parentId = node.parentId;\n      if (parentId === null) return;\n      const parentNode = store.state.indexes.nodes[parentId];\n      if (!parentNode) return;\n      return parentNode.data;\n    },\n  };\n\n  (storeData as any).__private = {\n    appState: store.state,\n  };\n\n  return storeData;\n};\n\nexport const UsePuckStoreContext = createContext<StoreApi<UsePuckStore> | null>(\n  null\n);\n\nconst convertToPickedStore = (store: AppStore): PickedStore => {\n  return {\n    state: store.state,\n    config: store.config,\n    dispatch: store.dispatch,\n    permissions: store.permissions,\n    history: store.history,\n    selectedItem: store.selectedItem,\n  };\n};\n\n/**\n * Mirror changes in appStore to usePuckStore\n */\nexport const useRegisterUsePuckStore = (\n  appStore: ReturnType<typeof useAppStoreApi>\n) => {\n  const [usePuckStore] = useState(() =>\n    createStore(() =>\n      generateUsePuck(\n        convertToPickedStore(appStore.getState()),\n        appStore.getState\n      )\n    )\n  );\n\n  useEffect(() => {\n    // Subscribe here isn't doing anything as selection isn't shallow\n    return appStore.subscribe(\n      (store) => convertToPickedStore(store),\n      (pickedStore) => {\n        usePuckStore.setState(generateUsePuck(pickedStore, appStore.getState));\n      }\n    );\n  }, []);\n\n  return usePuckStore;\n};\n\n/**\n * createUsePuck\n *\n * Create a typed usePuck hook, which is necessary because the user may provide a generic type but not\n * a selector type, and TS does not currently support partial inference.\n * Related: https://github.com/microsoft/TypeScript/issues/26242\n *\n * @returns a typed usePuck function\n */\nexport function createUsePuck<UserConfig extends Config = Config>() {\n  return function usePuck<T = PuckApi<UserConfig>>(\n    selector: (state: UsePuckStore<UserConfig>) => T\n  ): T {\n    const usePuckApi = useContext(UsePuckStoreContext);\n\n    if (!usePuckApi) {\n      throw new Error(\"usePuck must be used inside <Puck>.\");\n    }\n\n    const result = useStore(\n      usePuckApi as unknown as StoreApi<UsePuckStore<UserConfig>>,\n      selector ?? ((s) => s as T)\n    );\n\n    return result;\n  };\n}\n\nexport function usePuck<UserConfig extends Config = Config>() {\n  useEffect(() => {\n    console.warn(\n      \"You're using the `usePuck` method without a selector, which may cause unnecessary re-renders. Replace with `createUsePuck` and provide a selector for improved performance.\"\n    );\n  }, []);\n\n  return createUsePuck<UserConfig>()((s) => s);\n}\n\n/**\n * Get the latest state without relying on a render\n *\n * @returns PuckApi\n */\nexport function useGetPuck() {\n  const usePuckApi = useContext(UsePuckStoreContext);\n\n  if (!usePuckApi) {\n    throw new Error(\"usePuckGet must be used inside <Puck>.\");\n  }\n\n  return usePuckApi.getState;\n}\n"
  },
  {
    "path": "packages/core/lib/use-reset-auto-zoom.ts",
    "content": "import { RefObject } from \"react\";\nimport { useAppStoreApi } from \"../store\";\nimport { getZoomConfig } from \"./get-zoom-config\";\nimport { UiState } from \"../types\";\n\ntype ResetAutoZoomOptions = {\n  viewports?: UiState[\"viewports\"];\n};\n\n/**\n * Hook to reset auto zoom functionality\n * This is extracted from Canvas component to be reusable across components\n */\nexport const useResetAutoZoom = (frameRef: RefObject<HTMLElement | null>) => {\n  const appStoreApi = useAppStoreApi();\n\n  const resetAutoZoom = (options?: ResetAutoZoomOptions) => {\n    const { state, zoomConfig, setZoomConfig } = appStoreApi.getState();\n    const { viewports } = state.ui;\n    const newViewports = options?.viewports || viewports;\n\n    if (frameRef.current) {\n      setZoomConfig(\n        getZoomConfig(newViewports?.current, frameRef.current, zoomConfig.zoom)\n      );\n    }\n  };\n\n  return resetAutoZoom;\n};\n"
  },
  {
    "path": "packages/core/lib/use-safe-id.ts",
    "content": "import React, { useState } from \"react\";\nimport { generateId } from \"./generate-id\";\n\nexport const useSafeId = () => {\n  if (typeof React.useId !== \"undefined\") {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    return React.useId();\n  }\n\n  // eslint-disable-next-line react-hooks/rules-of-hooks\n  const [id] = useState(generateId());\n\n  return id;\n};\n"
  },
  {
    "path": "packages/core/lib/use-sidebar-resize.ts",
    "content": "import { useCallback, useEffect, useRef, useState } from \"react\";\nimport { useAppStore } from \"../store\";\nimport { PuckAction } from \"../reducer\";\n\n/**\n * Custom hook for managing sidebar resize functionality\n * @param position The position of the sidebar (\"left\" or \"right\")\n * @param dispatch The dispatch function from the app store\n * @returns Object containing width, setWidth, sidebarRef, and handleResizeEnd\n */\nexport function useSidebarResize(\n  position: \"left\" | \"right\",\n  dispatch: (action: PuckAction) => void\n) {\n  const [width, setWidth] = useState<number | null>(null);\n  const sidebarRef = useRef<HTMLDivElement>(null);\n\n  const storeWidth = useAppStore((s) =>\n    position === \"left\"\n      ? s.state.ui.leftSideBarWidth\n      : s.state.ui.rightSideBarWidth\n  );\n\n  // Load saved widths from localStorage on mount\n  useEffect(() => {\n    if (typeof window !== \"undefined\" && !storeWidth) {\n      try {\n        const savedWidths = localStorage.getItem(\"puck-sidebar-widths\");\n        if (savedWidths) {\n          const widths = JSON.parse(savedWidths);\n          const savedWidth = widths[position];\n          const key =\n            position === \"left\" ? \"leftSideBarWidth\" : \"rightSideBarWidth\";\n\n          if (savedWidth) {\n            dispatch({\n              type: \"setUi\",\n              ui: {\n                [key]: savedWidth,\n              },\n            });\n          }\n        }\n      } catch (error) {\n        console.error(\n          `Failed to load ${position} sidebar width from localStorage`,\n          error\n        );\n      }\n    }\n  }, [dispatch, position, storeWidth]);\n\n  useEffect(() => {\n    if (storeWidth !== undefined) {\n      setWidth(storeWidth);\n    }\n  }, [storeWidth]);\n\n  const handleResizeEnd = useCallback(\n    (width: number) => {\n      // Update store\n      dispatch({\n        type: \"setUi\",\n        ui: {\n          [position === \"left\" ? \"leftSideBarWidth\" : \"rightSideBarWidth\"]:\n            width,\n        },\n      });\n\n      // Save to localStorage\n      let widths = {};\n      try {\n        const savedWidths = localStorage.getItem(\"puck-sidebar-widths\");\n        widths = savedWidths ? JSON.parse(savedWidths) : {};\n      } catch (error) {\n        console.error(\n          `Failed to save ${position} sidebar width to localStorage`,\n          error\n        );\n      } finally {\n        localStorage.setItem(\n          \"puck-sidebar-widths\",\n          JSON.stringify({\n            ...widths,\n            [position]: width,\n          })\n        );\n      }\n\n      // Trigger auto zoom\n      window.dispatchEvent(\n        new CustomEvent(\"viewportchange\", {\n          bubbles: true,\n          cancelable: false,\n        })\n      );\n    },\n    [dispatch, position]\n  );\n\n  return {\n    width,\n    setWidth,\n    sidebarRef,\n    handleResizeEnd,\n  };\n}\n"
  },
  {
    "path": "packages/core/lib/use-slots.tsx",
    "content": "import { ReactNode } from \"react\";\nimport { ComponentData, Config, Content, RootData } from \"../types\";\nimport { DropZoneProps } from \"../components/DropZone/types\";\nimport { useFieldTransforms } from \"./field-transforms/use-field-transforms\";\nimport { getSlotTransform } from \"./field-transforms/default-transforms/slot-transform\";\n\nexport function useSlots<\n  T extends ComponentData | RootData,\n  UserConfig extends Config\n>(\n  config: UserConfig,\n  item: T,\n  renderSlotEdit: (dzProps: DropZoneProps & { content: Content }) => ReactNode,\n  renderSlotRender: (\n    dzProps: DropZoneProps & { content: Content }\n  ) => ReactNode = renderSlotEdit,\n  readOnly?: T[\"readOnly\"],\n  forceReadOnly?: boolean\n): T[\"props\"] {\n  return useFieldTransforms(\n    config,\n    item as ComponentData,\n    getSlotTransform(renderSlotEdit, renderSlotRender),\n    readOnly,\n    forceReadOnly\n  );\n}\n"
  },
  {
    "path": "packages/core/lib/use-why-render.ts",
    "content": "import { useEffect } from \"react\";\n\nexport const useWhyRender = (\n  obj: Record<string, any>,\n  onRender: (key: string, val: any) => void\n) => {\n  Object.keys(obj).map((key) => {\n    // eslint-disable-next-line react-hooks/rules-of-hooks\n    useEffect(() => {\n      onRender(key, obj[key]);\n    }, [obj[key]]);\n  });\n};\n"
  },
  {
    "path": "packages/core/package.json",
    "content": "{\n  \"name\": \"@puckeditor/core\",\n  \"version\": \"0.21.1\",\n  \"description\": \"The open-source visual editor for React\",\n  \"author\": \"Chris Villa <chris@puckeditor.com>\",\n  \"repository\": \"puckeditor/puck\",\n  \"bugs\": \"https://github.com/puckeditor/puck/issues\",\n  \"homepage\": \"https://puckeditor.com\",\n  \"keywords\": [\n    \"puck\",\n    \"visual\",\n    \"editor\",\n    \"react\",\n    \"drag and drop\",\n    \"dnd\",\n    \"page\",\n    \"website\",\n    \"builder\",\n    \"headless\",\n    \"cms\",\n    \"low-code\",\n    \"no-code\",\n    \"javascript\",\n    \"typescript\",\n    \"WYSIWYG\"\n  ],\n  \"private\": false,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"react-server\": {\n        \"types\": \"./dist/rsc.d.ts\",\n        \"import\": \"./dist/rsc.mjs\",\n        \"require\": \"./dist/rsc.js\"\n      },\n      \"default\": {\n        \"types\": \"./dist/index.d.ts\",\n        \"import\": \"./dist/index.mjs\",\n        \"require\": \"./dist/index.js\"\n      }\n    },\n    \"./rsc\": {\n      \"types\": \"./dist/rsc.d.ts\",\n      \"import\": \"./dist/rsc.mjs\",\n      \"require\": \"./dist/rsc.js\"\n    },\n    \"./internal\": {\n      \"types\": \"./dist/internal.d.ts\",\n      \"import\": \"./dist/internal.mjs\",\n      \"require\": \"./dist/internal.js\"\n    },\n    \"./puck.css\": \"./dist/index.css\",\n    \"./no-external.css\": \"./dist/no-external.css\",\n    \"./dist/index.css\": \"./dist/index.css\",\n    \"./package.json\": \"./package.json\"\n  },\n  \"typesVersions\": {\n    \"*\": {\n      \"rsc\": [\n        \"./dist/rsc.js\"\n      ]\n    }\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"lint\": \"eslint \\\"**/*.ts*\\\"\",\n    \"build\": \"rm -rf dist && tsup bundle/index.ts bundle/rsc.tsx bundle/no-external.ts bundle/internal.ts\",\n    \"test\": \"jest\",\n    \"prepare\": \"cp ../../README.md . && yarn build\",\n    \"postpublish\": \"rm README.md\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"devDependencies\": {\n    \"@juggle/resize-observer\": \"^3.4.0\",\n    \"@testing-library/jest-dom\": \"^6.6.3\",\n    \"@testing-library/react\": \"^16.1.0\",\n    \"@types/deep-diff\": \"^1.0.3\",\n    \"@types/flat\": \"^5.0.5\",\n    \"@types/jest\": \"^29.5.14\",\n    \"@types/object-hash\": \"^3.0.6\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"@types/uuid\": \"^10.0.0\",\n    \"css-box-model\": \"^1.2.1\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-custom\": \"*\",\n    \"identity-obj-proxy\": \"^3.0.0\",\n    \"jest\": \"^29.7.0\",\n    \"jest-environment-jsdom\": \"^30.0.0-beta.3\",\n    \"lucide-react\": \"^0.468.0\",\n    \"ts-jest\": \"^29.3.4\",\n    \"tsconfig\": \"*\",\n    \"tsup\": \"^8.2.4\",\n    \"tsup-config\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  },\n  \"dependencies\": {\n    \"@dnd-kit/helpers\": \"0.1.18\",\n    \"@dnd-kit/react\": \"0.1.18\",\n    \"@radix-ui/react-popover\": \"^1.1.15\",\n    \"@tiptap/core\": \"^3.11.1\",\n    \"@tiptap/extension-blockquote\": \"^3.11.1\",\n    \"@tiptap/extension-bold\": \"^3.11.1\",\n    \"@tiptap/extension-code\": \"^3.11.1\",\n    \"@tiptap/extension-code-block\": \"^3.11.1\",\n    \"@tiptap/extension-document\": \"^3.11.1\",\n    \"@tiptap/extension-hard-break\": \"^3.11.1\",\n    \"@tiptap/extension-heading\": \"^3.11.1\",\n    \"@tiptap/extension-horizontal-rule\": \"^3.11.1\",\n    \"@tiptap/extension-italic\": \"^3.11.1\",\n    \"@tiptap/extension-link\": \"^3.11.1\",\n    \"@tiptap/extension-list\": \"^3.11.1\",\n    \"@tiptap/extension-paragraph\": \"^3.11.1\",\n    \"@tiptap/extension-strike\": \"^3.11.1\",\n    \"@tiptap/extension-text\": \"^3.11.1\",\n    \"@tiptap/extension-text-align\": \"^3.11.1\",\n    \"@tiptap/extension-underline\": \"^3.11.1\",\n    \"@tiptap/html\": \"^3.11.1\",\n    \"@tiptap/pm\": \"^3.11.1\",\n    \"@tiptap/react\": \"^3.11.1\",\n    \"deep-diff\": \"^1.0.2\",\n    \"fast-equals\": \"5.2.2\",\n    \"flat\": \"^5.0.2\",\n    \"happy-dom\": \"^20.0.10\",\n    \"object-hash\": \"^3.0.0\",\n    \"react-hotkeys-hook\": \"^4.6.1\",\n    \"use-debounce\": \"^9.0.4\",\n    \"uuid\": \"^9.0.1\",\n    \"zustand\": \"^5.0.3\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^18.0.0 || ^19.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/core/plugins/blocks/index.tsx",
    "content": "import { Hammer } from \"lucide-react\";\nimport { Plugin } from \"../../types\";\nimport { Components } from \"../../components/Puck/components/Components\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"../../lib\";\n\nconst getClassName = getClassNameFactory(\"BlocksPlugin\", styles);\n\nexport const blocksPlugin: () => Plugin = () => ({\n  name: \"blocks\",\n  label: \"Blocks\",\n  render: () => (\n    <div className={getClassName()}>\n      <Components />\n    </div>\n  ),\n  icon: <Hammer />,\n});\n"
  },
  {
    "path": "packages/core/plugins/blocks/styles.module.css",
    "content": ".BlocksPlugin {\n  padding: 16px;\n  height: 100%;\n  overflow-y: auto;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "packages/core/plugins/fields/index.tsx",
    "content": "import { FormInput } from \"lucide-react\";\nimport { useAppStore } from \"../../store\";\nimport { PluginInternal } from \"../../types/Internal\";\nimport { Breadcrumbs } from \"../../components/Breadcrumbs\";\nimport { Fields } from \"../../components/Puck/components/Fields\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"../../lib\";\n\nconst getClassName = getClassNameFactory(\"FieldsPlugin\", styles);\n\nconst CurrentTitle = () => {\n  const label = useAppStore((s) => {\n    const selectedItem = s.selectedItem;\n\n    return selectedItem\n      ? s.config.components[selectedItem.type]?.label ?? selectedItem.type\n      : \"Page\";\n  });\n\n  return label;\n};\n\nexport const fieldsPlugin: (params?: {\n  desktopSideBar?: \"left\" | \"right\";\n}) => PluginInternal = ({ desktopSideBar = \"right\" } = {}) => ({\n  name: \"fields\",\n  label: \"Fields\",\n  render: () => (\n    <div className={getClassName()}>\n      <div className={getClassName(\"header\")}>\n        <Breadcrumbs numParents={2}>\n          <CurrentTitle />\n        </Breadcrumbs>\n      </div>\n      <Fields />\n    </div>\n  ),\n  icon: <FormInput />,\n  mobileOnly: desktopSideBar === \"right\",\n});\n"
  },
  {
    "path": "packages/core/plugins/fields/styles.module.css",
    "content": ".FieldsPlugin {\n  background: white;\n  height: 100%;\n  overflow-y: auto;\n}\n\n.FieldsPlugin-header {\n  border-bottom: 1px solid var(--puck-color-grey-09);\n  font-weight: 600;\n  padding-bottom: 8px;\n  padding-left: 16px;\n  padding-right: 16px;\n  padding-top: 8px;\n}\n\n@media (min-width: 638px) {\n  .FieldsPlugin-header {\n    padding: 16px;\n  }\n}\n"
  },
  {
    "path": "packages/core/plugins/legacy-side-bar/index.tsx",
    "content": "import { Plugin } from \"../../types\";\nimport { Components } from \"../../components/Puck/components/Components\";\nimport { Outline } from \"../../components/Puck/components/Outline\";\nimport { SidebarSection } from \"../../components/SidebarSection\";\n\nexport const legacySideBarPlugin: () => Plugin = () => ({\n  name: \"legacy-side-bar\",\n  render: () => (\n    <div style={{ overflowY: \"auto\" }}>\n      <SidebarSection title=\"Components\" noBorderTop>\n        <Components />\n      </SidebarSection>\n      <SidebarSection title=\"Outline\">\n        <Outline />\n      </SidebarSection>\n    </div>\n  ),\n});\n"
  },
  {
    "path": "packages/core/plugins/outline/index.tsx",
    "content": "import { Layers } from \"lucide-react\";\nimport { Outline } from \"../../components/Puck/components/Outline\";\nimport { Plugin } from \"../../types\";\nimport styles from \"./styles.module.css\";\nimport { getClassNameFactory } from \"../../lib\";\n\nconst getClassName = getClassNameFactory(\"OutlinePlugin\", styles);\n\nexport const outlinePlugin: () => Plugin = () => ({\n  name: \"outline\",\n  label: \"Outline\",\n  render: () => (\n    <div className={getClassName()}>\n      <Outline />\n    </div>\n  ),\n  icon: <Layers />,\n});\n"
  },
  {
    "path": "packages/core/plugins/outline/styles.module.css",
    "content": ".OutlinePlugin {\n  padding: 16px;\n  height: 100%;\n  overflow-y: auto;\n  box-sizing: border-box;\n}\n"
  },
  {
    "path": "packages/core/reducer/actions/__helpers__/index.tsx",
    "content": "import { PuckAction, createReducer } from \"../../../reducer\";\nimport { ComponentData, Config, Data, Slot, UiState } from \"../../../types\";\nimport { generateId } from \"../../../lib/generate-id\";\nimport {\n  createAppStore,\n  defaultAppState as _defaultAppState,\n} from \"../../../store\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport { stripSlots } from \"../../../lib/data/strip-slots\";\nimport { Reducer } from \"react\";\nimport { flattenNode } from \"../../../lib/data/flatten-node\";\n\njest.mock(\"../../../lib/generate-id\");\n\nconst mockedGenerateId = generateId as jest.MockedFunction<typeof generateId>;\n\ntype Props = {\n  Comp: {\n    prop: string;\n    slot: Slot;\n    slotArray: { slot: Slot }[];\n  };\n  CompWithDefaults: {\n    prop: string;\n    slot: Slot;\n    slotArray: { slot: Slot }[];\n  };\n};\n\ntype RootProps = {\n  title: string;\n  slot: Slot;\n};\n\nexport type UserConfig = Config<Props, RootProps>;\nexport type UserData = Data<Props, RootProps>;\n\nexport const dzZoneCompound = \"my-component:zone1\";\n\nexport const defaultData: UserData = {\n  root: { props: { title: \"\", slot: [] } },\n  content: [],\n  zones: { [dzZoneCompound]: [] },\n};\n\nexport const defaultUi: UiState = _defaultAppState.ui;\n\nexport const defaultIndexes: PrivateAppState<UserData>[\"indexes\"] = {\n  nodes: {},\n  zones: {\n    \"root:slot\": { contentIds: [], type: \"slot\" },\n    [dzZoneCompound]: { contentIds: [], type: \"dropzone\" },\n  },\n};\n\nexport const defaultState = {\n  data: defaultData,\n  ui: defaultUi,\n  indexes: defaultIndexes,\n};\n\nexport const appStore = createAppStore();\n\nconst config: UserConfig = {\n  root: {\n    fields: { title: { type: \"text\" }, slot: { type: \"slot\" } },\n  },\n  components: {\n    Comp: {\n      fields: {\n        prop: { type: \"text\" },\n        slot: { type: \"slot\" },\n        slotArray: { type: \"array\", arrayFields: { slot: { type: \"slot\" } } },\n      },\n      defaultProps: { prop: \"example\", slot: [], slotArray: [] },\n      render: () => <div />,\n    },\n    CompWithDefaults: {\n      fields: {\n        prop: { type: \"text\" },\n        slot: { type: \"slot\" },\n        slotArray: { type: \"array\", arrayFields: { slot: { type: \"slot\" } } },\n      },\n      defaultProps: {\n        prop: \"example\",\n        slot: [\n          {\n            type: \"Comp\",\n            props: {\n              prop: \"Defaulted item\",\n              slots: [],\n            },\n          },\n        ],\n        slotArray: [],\n      },\n      render: () => <div />,\n    },\n  },\n};\n\nexport const expectIndexed = (\n  state: PrivateAppState,\n  item: ComponentData | undefined,\n  path: string[],\n  index: number,\n  _config: Config = config\n) => {\n  if (!item) return;\n\n  const zoneCompound = path[path.length - 1];\n\n  expect(state.indexes.zones[zoneCompound].contentIds[index]).toEqual(\n    item.props.id\n  );\n  expect(state.indexes.nodes[item.props.id].data).toEqual(item);\n  expect(state.indexes.nodes[item.props.id].flatData).toEqual(\n    flattenNode(item, _config)\n  );\n  expect(state.indexes.nodes[item.props.id].path).toEqual(path);\n};\n\nexport const executeSequenceFactory =\n  (reducer: Reducer<any, any>) =>\n  <UserData extends Data>(\n    initialState: PrivateAppState<UserData>,\n    actions: ((currentState: PrivateAppState<UserData>) => PuckAction)[]\n  ) => {\n    let currentState: PrivateAppState<UserData> = initialState;\n\n    actions.forEach((actionFn) => {\n      const action = actionFn(currentState);\n\n      currentState = reducer(currentState, action) as PrivateAppState<UserData>;\n    });\n\n    return currentState;\n  };\n\nexport const testSetup = () => {\n  let _reducer = createReducer({ appStore: appStore.getState() });\n\n  const beforeEachFn = () => {\n    const newStore = {\n      ...appStore.getInitialState(),\n      config,\n    };\n\n    appStore.setState(newStore, true);\n\n    _reducer = createReducer({ appStore: newStore });\n\n    let counter = 0;\n\n    mockedGenerateId.mockImplementation(() => `mockId-${counter++}`);\n  };\n\n  beforeEach(beforeEachFn);\n\n  const executeSequence = (\n    initialState: PrivateAppState<UserData>,\n    actions: ((currentState: PrivateAppState<UserData>) => PuckAction)[]\n  ) => {\n    let currentState: PrivateAppState<UserData> = initialState;\n\n    actions.forEach((actionFn) => {\n      const action = actionFn(currentState);\n\n      currentState = _reducer(\n        currentState,\n        action\n      ) as PrivateAppState<UserData>;\n    });\n\n    return currentState;\n  };\n\n  const reducer = (state: PrivateAppState, action: PuckAction) =>\n    _reducer(state, action);\n\n  return { reducer, executeSequence, config };\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/duplicate.spec.ts",
    "content": "import { Content } from \"../../../types\";\nimport { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport {\n  defaultData,\n  defaultState,\n  dzZoneCompound,\n  expectIndexed,\n  testSetup,\n} from \"../__helpers__\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport { walkAppState } from \"../../../lib/data/walk-app-state\";\n\ndescribe(\"Reducer\", () => {\n  const { executeSequence, config, reducer } = testSetup();\n\n  describe(\"duplicate action\", () => {\n    describe(\"with DropZones\", () => {\n      it(\"should duplicate in content\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            id: \"sampleId\",\n          }),\n          (state) => ({\n            type: \"replace\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            data: {\n              ...state.indexes.nodes[\"sampleId\"].data,\n              props: {\n                ...state.indexes.nodes[\"sampleId\"].data.props,\n                prop: \"Some example data\",\n              },\n            },\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: rootDroppableId,\n            sourceIndex: 0,\n          }),\n        ]);\n\n        expect(newState.data.content).toHaveLength(2);\n        expect(newState.data.content[1].props.id).not.toBe(\"sampleId\");\n        expect(newState.data.content[1].props.prop).toBe(\"Some example data\");\n        expectIndexed(\n          newState,\n          newState.data.content[1],\n          [rootDroppableId],\n          1,\n          config\n        );\n      });\n\n      it(\"should duplicate in a different zone\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            id: \"sampleId\",\n          }),\n          (state) => ({\n            type: \"replace\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            data: {\n              ...state.indexes.nodes[\"sampleId\"].data,\n              props: {\n                ...state.indexes.nodes[\"sampleId\"].data.props,\n                prop: \"Some example data\",\n              },\n            },\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: dzZoneCompound,\n            sourceIndex: 0,\n          }),\n        ]);\n\n        const zone = newState.data.zones?.[dzZoneCompound] ?? [];\n\n        expect(zone).toHaveLength(2);\n        expect(zone[1].props.id).not.toBe(\"sampleId\");\n        expect(zone[1].props.prop).toBe(\"Some example data\");\n        expectIndexed(\n          newState,\n          zone[1],\n          [rootDroppableId, dzZoneCompound],\n          1,\n          config\n        );\n      });\n\n      it(\"should recursively duplicate related items and zones\", () => {\n        const state: PrivateAppState = walkAppState(\n          {\n            ...defaultState,\n            data: {\n              ...defaultData,\n              content: [\n                {\n                  type: \"Comp\",\n                  props: { id: \"my-component\", prop: \"Data\" },\n                },\n              ],\n              zones: {\n                \"my-component:zone\": [\n                  {\n                    type: \"Comp\",\n                    props: { id: \"other-component\", prop: \"More example data\" },\n                  },\n                ],\n                \"other-component:zone\": [\n                  {\n                    type: \"Comp\",\n                    props: { id: \"final-id\", prop: \"Even more example data\" },\n                  },\n                ],\n              },\n            },\n          },\n          config\n        );\n\n        const newState = reducer(state, {\n          type: \"duplicate\",\n          sourceIndex: 0,\n          sourceZone: rootDroppableId,\n        });\n\n        const zone1 = newState.data.content ?? [];\n\n        expect(zone1).toHaveLength(2);\n        expect(zone1[1].props.id).not.toBe(\"my-component\");\n        expect(zone1[1].props.prop).toBe(\"Data\");\n        expectIndexed(newState, zone1[1], [rootDroppableId], 1, config);\n\n        const zone2ZoneCompound = `${zone1[1].props.id}:zone`;\n        const zone2 = newState.data.zones?.[zone2ZoneCompound] ?? [];\n\n        expect(zone2).toHaveLength(1);\n        expect(zone2[0].props.id).not.toBe(\"other-component\");\n        expect(zone2[0].props.prop).toBe(\"More example data\");\n        expectIndexed(\n          newState,\n          zone2[0],\n          [rootDroppableId, zone2ZoneCompound],\n          0,\n          config\n        );\n\n        const zone3ZoneCompound = `${zone2[0].props.id}:zone`;\n        const zone3 = newState.data.zones?.[zone3ZoneCompound] ?? [];\n\n        expect(zone3).toHaveLength(1);\n        expect(zone3[0].props.id).not.toBe(\"final-id\");\n        expect(zone3[0].props.prop).toBe(\"Even more example data\");\n        expectIndexed(\n          newState,\n          zone3[0],\n          [rootDroppableId, zone2ZoneCompound, zone3ZoneCompound],\n          0,\n          config\n        );\n      });\n\n      it(\"should select the duplicated item\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            id: \"sampleId\",\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: rootDroppableId,\n            sourceIndex: 0,\n          }),\n        ]);\n\n        expect(newState.ui.itemSelector?.index).toBe(1);\n        expect(newState.ui.itemSelector?.zone).toBe(rootDroppableId);\n      });\n    });\n    describe(\"with slots\", () => {\n      it(\"should duplicate within a slot\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"sampleId\",\n          }),\n          (state) => ({\n            type: \"replace\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            data: {\n              ...state.indexes.nodes[\"sampleId\"].data,\n              props: {\n                ...state.indexes.nodes[\"sampleId\"].data.props,\n                prop: \"Some example data\",\n              },\n            },\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: \"root:slot\",\n            sourceIndex: 0,\n          }),\n        ]);\n\n        const content = newState.data.root.props?.slot ?? [];\n        expect(content).toHaveLength(2);\n        expect(content[1].props.id).not.toBe(\"sampleId\");\n        expect(content[1].props.prop).toBe(\"Some example data\");\n        expectIndexed(newState, content[1], [\"root:slot\"], 1, config);\n      });\n\n      it(\"should duplicate within a slot, within a slot\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"first\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"first:slot\",\n            destinationIndex: 0,\n            id: \"second\",\n          }),\n          (state) => ({\n            type: \"replace\",\n            destinationZone: \"first:slot\",\n            destinationIndex: 0,\n            data: {\n              ...state.indexes.nodes[\"second\"].data,\n              props: {\n                ...state.indexes.nodes[\"second\"].data.props,\n                prop: \"Some example data\",\n              },\n            },\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: \"first:slot\",\n            sourceIndex: 0,\n          }),\n        ]);\n\n        const content = (newState.data.root.props?.slot ?? [])[0].props\n          .slot as Content;\n        expect(content).toHaveLength(2);\n        expect(content[1].props.id).not.toBe(\"second\");\n        expect(content[1].props.prop).toBe(\"Some example data\");\n        expectIndexed(\n          newState,\n          content[1],\n          [\"root:slot\", \"first:slot\"],\n          1,\n          config\n        );\n      });\n\n      it(\"should duplicate within a slot, within a DropZone\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            id: \"first\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"first:slot\",\n            destinationIndex: 0,\n            id: \"second\",\n          }),\n          (state) => ({\n            type: \"replace\",\n            destinationZone: \"first:slot\",\n            destinationIndex: 0,\n            data: {\n              ...state.indexes.nodes[\"second\"].data,\n              props: {\n                ...state.indexes.nodes[\"second\"].data.props,\n                prop: \"Some example data\",\n              },\n            },\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: \"first:slot\",\n            sourceIndex: 0,\n          }),\n        ]);\n\n        const content =\n          newState.data.zones?.[dzZoneCompound][0].props.slot || [];\n\n        expect(content).toHaveLength(2);\n        expect(content[1].props.id).not.toBe(\"second\");\n        expect(content[1].props.prop).toBe(\"Some example data\");\n        expectIndexed(\n          newState,\n          content[1],\n          [rootDroppableId, dzZoneCompound, \"first:slot\"],\n          1,\n          config\n        );\n      });\n\n      it(\"should recursively duplicate related items\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"first\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"first:slot\",\n            destinationIndex: 0,\n            id: \"second\",\n          }),\n          (state) => ({\n            type: \"replace\",\n            destinationZone: \"first:slot\",\n            destinationIndex: 0,\n            data: {\n              ...state.indexes.nodes[\"second\"].data,\n              props: {\n                ...state.indexes.nodes[\"second\"].data.props,\n                prop: \"Some example data\",\n              },\n            },\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: \"root:slot\",\n            sourceIndex: 0,\n          }),\n        ]);\n\n        const zone1 = newState.data.root.props?.slot ?? [];\n        expect(zone1).toHaveLength(2);\n        expect(zone1[1].props.id).not.toBe(\"second\");\n        expectIndexed(newState, zone1[1], [\"root:slot\"], 1, config);\n\n        const zone2ZoneCompound = `${zone1[1].props.id}:slot`;\n        const zone2 = zone1[1].props.slot ?? [];\n\n        expect(zone2).toHaveLength(1);\n        expect(zone2[0].props.id).not.toBe(\"second\");\n        expect(zone2[0].props.prop).toBe(\"Some example data\");\n        expectIndexed(\n          newState,\n          zone2[0],\n          [\"root:slot\", zone2ZoneCompound],\n          0,\n          config\n        );\n      });\n\n      it(\"should select the duplicated item\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"sampleId\",\n          }),\n          () => ({\n            type: \"duplicate\",\n            sourceZone: \"root:slot\",\n            sourceIndex: 0,\n          }),\n        ]);\n\n        expect(newState.ui.itemSelector?.index).toBe(1);\n        expect(newState.ui.itemSelector?.zone).toBe(\"root:slot\");\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/insert.spec.ts",
    "content": "import { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport {\n  defaultState,\n  dzZoneCompound,\n  expectIndexed,\n  testSetup,\n  UserData,\n} from \"../__helpers__\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport { InsertAction } from \"../../actions\";\n\ndescribe(\"Reducer\", () => {\n  const { executeSequence, reducer } = testSetup();\n\n  describe(\"insert action\", () => {\n    describe(\"with DropZones\", () => {\n      it(\"should insert into rootDroppableId\", () => {\n        const action: InsertAction = {\n          type: \"insert\",\n          componentType: \"Comp\",\n          destinationIndex: 0,\n          destinationZone: rootDroppableId,\n        };\n\n        const newState = reducer(defaultState, action);\n        const item = newState.data.content[0];\n\n        expect(item).toHaveProperty(\"type\", \"Comp\");\n        expect(item.props).toHaveProperty(\"prop\", \"example\");\n        expectIndexed(newState, item, [rootDroppableId], 0);\n      });\n\n      it(\"should insert into a different zone\", () => {\n        const action: InsertAction = {\n          type: \"insert\",\n          componentType: \"Comp\",\n          destinationIndex: 0,\n          destinationZone: dzZoneCompound,\n        };\n\n        const state = reducer(defaultState, action);\n\n        const item = state.data.zones?.[dzZoneCompound][0];\n\n        expect(item).toHaveProperty(\"type\", \"Comp\");\n        expect(item?.props).toHaveProperty(\"prop\", \"example\");\n        expectIndexed(state, item, [rootDroppableId, dzZoneCompound], 0);\n      });\n    });\n\n    describe(\"with slots\", () => {\n      it(\"should insert into a root slot\", () => {\n        const state: PrivateAppState<UserData> = defaultState;\n\n        const action: InsertAction = {\n          type: \"insert\",\n          componentType: \"Comp\",\n          destinationIndex: 0,\n          destinationZone: \"root:slot\",\n        };\n\n        const newState = reducer(state, action) as PrivateAppState<UserData>;\n\n        const item = newState.data.root.props?.slot[0];\n\n        expect(item).toHaveProperty(\"type\", \"Comp\");\n        expect(item?.props).toHaveProperty(\"prop\", \"example\");\n        expectIndexed(newState, item, [\"root:slot\"], 0);\n      });\n\n      it(\"should load components defined in defaultProps\", () => {\n        const state: PrivateAppState<UserData> = defaultState;\n\n        const action: InsertAction = {\n          type: \"insert\",\n          componentType: \"CompWithDefaults\",\n          destinationIndex: 0,\n          destinationZone: \"root:slot\",\n          id: \"first\",\n        };\n\n        const newState = reducer(state, action) as PrivateAppState<UserData>;\n\n        const item = newState.data.root.props?.slot[0];\n        const defaultedItem = newState.data.root.props?.slot[0].props.slot[0];\n\n        expect(item).toHaveProperty(\"type\", \"CompWithDefaults\");\n        expect(defaultedItem?.props).toHaveProperty(\"prop\", \"Defaulted item\");\n        expect(defaultedItem?.props.id).toEqual(\"mockId-1\");\n        expectIndexed(newState, defaultedItem, [\"root:slot\", \"first:slot\"], 0);\n      });\n\n      it(\"should insert into a slot within a slot\", () => {\n        const state = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationIndex: 0,\n            destinationZone: \"root:slot\",\n          }),\n          (state) => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationIndex: 0,\n            destinationZone: `${state.data.root.props?.slot[0].props?.id}:slot`,\n          }),\n        ]);\n\n        const item = state.data.root.props?.slot[0]?.props.slot[0];\n\n        expect(item).toHaveProperty(\"type\", \"Comp\");\n        expect(item?.props).toHaveProperty(\"prop\", \"example\");\n        expectIndexed(\n          state,\n          item,\n          [\"root:slot\", `${state.data.root.props?.slot[0].props?.id}:slot`],\n          0\n        );\n      });\n\n      it(\"should insert into a slot within a DropZone\", () => {\n        const state = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationIndex: 0,\n            destinationZone: dzZoneCompound,\n          }),\n          (state) => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationIndex: 0,\n            destinationZone: `${state.data.zones?.[dzZoneCompound][0]?.props.id}:slot`,\n          }),\n        ]);\n\n        const item = state.data.zones?.[dzZoneCompound][0];\n\n        expect(item).toHaveProperty(\"type\", \"Comp\");\n        expect(item?.props).toHaveProperty(\"prop\", \"example\");\n        expectIndexed(state, item, [rootDroppableId, dzZoneCompound], 0);\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/move.spec.ts",
    "content": "import { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport {\n  defaultState,\n  dzZoneCompound,\n  expectIndexed,\n  testSetup,\n} from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { executeSequence } = testSetup();\n\n  describe(\"move action\", () => {\n    describe(\"with DropZones\", () => {\n      it(\"should reorder within rootDroppableId\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 1,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"move\",\n            sourceIndex: 0,\n            sourceZone: rootDroppableId,\n            destinationIndex: 1,\n            destinationZone: rootDroppableId,\n          }),\n        ]);\n\n        expect(newState.data.content[0].props.id).toBe(\"2\");\n        expect(newState.data.content[1].props.id).toBe(\"1\");\n        expectIndexed(newState, newState.data.content[0], [rootDroppableId], 0);\n        expectIndexed(newState, newState.data.content[1], [rootDroppableId], 1);\n      });\n\n      it(\"should move items between zones\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 1,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"move\",\n            sourceIndex: 1,\n            sourceZone: dzZoneCompound,\n            destinationIndex: 0,\n            destinationZone: rootDroppableId,\n          }),\n        ]);\n\n        expect(newState.data.zones?.[dzZoneCompound][0].props.id).toBe(\"1\");\n        expect(newState.data.content[0].props.id).toBe(\"2\");\n        expectIndexed(\n          newState,\n          newState.data.zones?.[dzZoneCompound][0],\n          [rootDroppableId, dzZoneCompound],\n          0\n        );\n        expectIndexed(newState, newState.data.content[0], [rootDroppableId], 0);\n      });\n    });\n\n    describe(\"with slots\", () => {\n      it(\"should reorder within a slot\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 1,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"move\",\n            sourceIndex: 0,\n            sourceZone: \"root:slot\",\n            destinationIndex: 1,\n            destinationZone: \"root:slot\",\n          }),\n        ]);\n\n        expect(newState.data.root.props?.slot[0].props.id).toBe(\"2\");\n        expect(newState.data.root.props?.slot[1].props.id).toBe(\"1\");\n        expectIndexed(\n          newState,\n          newState.data.root.props?.slot[0],\n          [\"root:slot\"],\n          0\n        );\n        expectIndexed(\n          newState,\n          newState.data.root.props?.slot[1],\n          [\"root:slot\"],\n          1\n        );\n      });\n\n      it(\"should move items from a deep slot to a zone\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"1:slot\",\n            destinationIndex: 0,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"2:slot\",\n            destinationIndex: 0,\n            id: \"3\",\n          }),\n          () => ({\n            type: \"move\",\n            sourceIndex: 0,\n            sourceZone: \"1:slot\",\n            destinationIndex: 1,\n            destinationZone: dzZoneCompound,\n          }),\n        ]);\n\n        const item1 = newState.data.root.props?.slot[0];\n        const item2 = newState.data.zones?.[dzZoneCompound][0];\n        const item3 = newState.data.zones?.[dzZoneCompound][0].props?.slot[0];\n\n        expect(item1?.props.id).toBe(\"1\");\n        expectIndexed(newState, item1, [\"root:slot\"], 0);\n\n        expect(item2?.props?.id).toBe(\"2\");\n        expectIndexed(newState, item2, [rootDroppableId, dzZoneCompound], 0);\n\n        expect(item3?.props.id).toBe(\"3\");\n        expectIndexed(\n          newState,\n          item3,\n          [rootDroppableId, dzZoneCompound, \"2:slot\"],\n          0\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/register-zone.spec.ts",
    "content": "import { RegisterZoneAction, UnregisterZoneAction } from \"../..\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport { defaultData, defaultState, testSetup } from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { reducer } = testSetup();\n\n  describe(\"registerZone action\", () => {\n    it(\"should register a zone that's been previously unregistered\", () => {\n      const state: PrivateAppState = {\n        ...defaultState,\n        data: {\n          ...defaultData,\n          zones: { zone1: [{ type: \"Comp\", props: { id: \"1\" } }] },\n        },\n      };\n\n      const unregisterAction: UnregisterZoneAction = {\n        type: \"unregisterZone\",\n        zone: \"zone1\",\n      };\n\n      const registerAction: RegisterZoneAction = {\n        type: \"registerZone\",\n        zone: \"zone1\",\n      };\n\n      const newState = reducer(\n        reducer(state, unregisterAction),\n        registerAction\n      );\n      expect(newState.data.zones?.zone1[0].props.id).toEqual(\"1\");\n    });\n  });\n\n  describe(\"unregisterZone action\", () => {\n    it(\"should unregister a zone\", () => {\n      const state: PrivateAppState = {\n        ...defaultState,\n        data: {\n          ...defaultData,\n          zones: { zone1: [{ type: \"Comp\", props: { id: \"1\" } }] },\n        },\n      };\n\n      const action: UnregisterZoneAction = {\n        type: \"unregisterZone\",\n        zone: \"zone1\",\n      };\n\n      const newState = reducer(state, action);\n      expect(newState.data.zones?.zone1).toBeUndefined();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/remove.spec.ts",
    "content": "import { RemoveAction } from \"../..\";\nimport { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport { walkAppState } from \"../../../lib/data/walk-app-state\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport {\n  defaultData,\n  defaultState,\n  dzZoneCompound,\n  testSetup,\n} from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { executeSequence, config, reducer } = testSetup();\n\n  describe(\"remove action\", () => {\n    describe(\"with DropZones\", () => {\n      it(\"should remove from content\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"remove\",\n            index: 0,\n            zone: rootDroppableId,\n          }),\n        ]);\n\n        expect(newState.data.content).toHaveLength(0);\n        expect(newState.indexes.nodes[\"1\"]).toBeUndefined();\n        expect(newState.indexes.zones[rootDroppableId].contentIds).toEqual([]);\n      });\n\n      it(\"should remove from a zone\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"remove\",\n            index: 0,\n            zone: dzZoneCompound,\n          }),\n        ]);\n\n        expect(newState.data.zones?.[dzZoneCompound]).toHaveLength(0);\n        expect(newState.indexes.nodes[\"1\"]).toBeUndefined();\n        expect(newState.indexes.zones[dzZoneCompound].contentIds).toEqual([]);\n      });\n\n      it(\"should recursively remove items\", () => {\n        const state: PrivateAppState = walkAppState(\n          {\n            ...defaultState,\n            data: {\n              ...defaultData,\n              content: [\n                {\n                  type: \"Comp\",\n                  props: { id: \"my-component\", prop: \"Data\" },\n                },\n              ],\n              zones: {\n                \"my-component:zone\": [\n                  {\n                    type: \"Comp\",\n                    props: { id: \"other-component\", prop: \"More example data\" },\n                  },\n                ],\n                \"other-component:zone\": [\n                  {\n                    type: \"Comp\",\n                    props: { id: \"final-id\", prop: \"Even more example data\" },\n                  },\n                ],\n              },\n            },\n          },\n          config\n        );\n\n        const action: RemoveAction = {\n          type: \"remove\",\n          index: 0,\n          zone: rootDroppableId,\n        };\n\n        const newState = reducer(state, action);\n\n        expect(newState.data).toMatchInlineSnapshot(`\n          {\n            \"content\": [],\n            \"root\": {\n              \"props\": {\n                \"slot\": [],\n                \"title\": \"\",\n              },\n            },\n            \"zones\": {},\n          }\n        `);\n\n        expect(newState.indexes).toMatchInlineSnapshot(`\n          {\n            \"nodes\": {\n              \"root\": {\n                \"data\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": [],\n                    \"title\": \"\",\n                  },\n                  \"type\": \"root\",\n                },\n                \"flatData\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": null,\n                    \"title\": \"\",\n                  },\n                  \"type\": \"root\",\n                },\n                \"parentId\": null,\n                \"path\": [],\n                \"zone\": \"\",\n              },\n            },\n            \"zones\": {\n              \"root:default-zone\": {\n                \"contentIds\": [],\n                \"type\": \"root\",\n              },\n              \"root:slot\": {\n                \"contentIds\": [],\n                \"type\": \"slot\",\n              },\n            },\n          }\n        `);\n      });\n    });\n\n    describe(\"with slots\", () => {\n      it(\"should remove from a slot\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"remove\",\n            index: 0,\n            zone: \"root:slot\",\n          }),\n        ]);\n\n        expect(newState.data.content).toHaveLength(0);\n        expect(newState.indexes.nodes[\"1\"]).toBeUndefined();\n        expect(newState.indexes.zones[\"root:slot\"].contentIds).toEqual([]);\n      });\n\n      it(\"should recursively remove items in a slot\", () => {\n        const state: PrivateAppState = walkAppState(\n          {\n            ...defaultState,\n            data: {\n              ...defaultData,\n              root: {\n                props: {\n                  slot: [\n                    {\n                      type: \"Comp\",\n                      props: {\n                        id: \"my-component\",\n                        prop: \"Data\",\n                        slot: [\n                          {\n                            type: \"Comp\",\n                            props: {\n                              id: \"final-id\",\n                              prop: \"Even more example data\",\n                            },\n                          },\n                        ],\n                      },\n                    },\n                  ],\n                } as any,\n              },\n            },\n          },\n          config\n        );\n\n        const action: RemoveAction = {\n          type: \"remove\",\n          index: 0,\n          zone: \"root:slot\",\n        };\n\n        const newState = reducer(state, action);\n\n        expect(newState.data).toMatchInlineSnapshot(`\n          {\n            \"content\": [],\n            \"root\": {\n              \"props\": {\n                \"slot\": [],\n              },\n            },\n            \"zones\": {},\n          }\n        `);\n\n        expect(newState.indexes).toMatchInlineSnapshot(`\n          {\n            \"nodes\": {\n              \"root\": {\n                \"data\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": [],\n                  },\n                  \"type\": \"root\",\n                },\n                \"flatData\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": null,\n                  },\n                  \"type\": \"root\",\n                },\n                \"parentId\": null,\n                \"path\": [],\n                \"zone\": \"\",\n              },\n            },\n            \"zones\": {\n              \"root:default-zone\": {\n                \"contentIds\": [],\n                \"type\": \"root\",\n              },\n              \"root:slot\": {\n                \"contentIds\": [],\n                \"type\": \"slot\",\n              },\n            },\n          }\n        `);\n      });\n\n      it(\"should remove items deep within a slot\", () => {\n        const state: PrivateAppState = walkAppState(\n          {\n            ...defaultState,\n            data: {\n              ...defaultData,\n              root: {\n                props: {\n                  slot: [\n                    {\n                      type: \"Comp\",\n                      props: {\n                        id: \"my-component\",\n                        prop: \"Data\",\n                        slot: [\n                          {\n                            type: \"Comp\",\n                            props: {\n                              id: \"final-id\",\n                              prop: \"Even more example data\",\n                            },\n                          },\n                        ],\n                      },\n                    },\n                  ],\n                } as any,\n              },\n            },\n          },\n          config\n        );\n\n        const action: RemoveAction = {\n          type: \"remove\",\n          index: 0,\n          zone: \"my-component:slot\",\n        };\n\n        const newState = reducer(state, action);\n\n        expect(newState.data).toMatchInlineSnapshot(`\n          {\n            \"content\": [],\n            \"root\": {\n              \"props\": {\n                \"slot\": [\n                  {\n                    \"props\": {\n                      \"id\": \"my-component\",\n                      \"prop\": \"Data\",\n                      \"slot\": [],\n                    },\n                    \"type\": \"Comp\",\n                  },\n                ],\n              },\n            },\n            \"zones\": {\n              \"my-component:zone1\": [],\n            },\n          }\n        `);\n\n        expect(newState.indexes).toMatchInlineSnapshot(`\n          {\n            \"nodes\": {\n              \"my-component\": {\n                \"data\": {\n                  \"props\": {\n                    \"id\": \"my-component\",\n                    \"prop\": \"Data\",\n                    \"slot\": [],\n                  },\n                  \"type\": \"Comp\",\n                },\n                \"flatData\": {\n                  \"props\": {\n                    \"id\": \"my-component\",\n                    \"prop\": \"Data\",\n                    \"slot\": null,\n                  },\n                  \"type\": \"Comp\",\n                },\n                \"parentId\": \"root\",\n                \"path\": [\n                  \"root:slot\",\n                ],\n                \"zone\": \"slot\",\n              },\n              \"root\": {\n                \"data\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": [\n                      {\n                        \"props\": {\n                          \"id\": \"my-component\",\n                          \"prop\": \"Data\",\n                          \"slot\": [],\n                        },\n                        \"type\": \"Comp\",\n                      },\n                    ],\n                  },\n                  \"type\": \"root\",\n                },\n                \"flatData\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": null,\n                  },\n                  \"type\": \"root\",\n                },\n                \"parentId\": null,\n                \"path\": [],\n                \"zone\": \"\",\n              },\n            },\n            \"zones\": {\n              \"my-component:slot\": {\n                \"contentIds\": [],\n                \"type\": \"slot\",\n              },\n              \"my-component:zone1\": {\n                \"contentIds\": [],\n                \"type\": \"dropzone\",\n              },\n              \"root:default-zone\": {\n                \"contentIds\": [],\n                \"type\": \"root\",\n              },\n              \"root:slot\": {\n                \"contentIds\": [\n                  \"my-component\",\n                ],\n                \"type\": \"slot\",\n              },\n            },\n          }\n        `);\n      });\n\n      it(\"should recursively remove items in a slot within a DropZone\", () => {\n        const state: PrivateAppState = walkAppState(\n          {\n            ...defaultState,\n            data: {\n              ...defaultData,\n              content: [\n                {\n                  type: \"Comp\",\n                  props: {\n                    id: \"my-component\",\n                    prop: \"Data\",\n                    slot: [\n                      {\n                        type: \"Comp\",\n                        props: {\n                          id: \"final-id\",\n                          prop: \"Even more example data\",\n                        },\n                      },\n                    ],\n                  },\n                },\n              ],\n            },\n          },\n          config\n        );\n\n        const action: RemoveAction = {\n          type: \"remove\",\n          index: 0,\n          zone: rootDroppableId,\n        };\n\n        const newState = reducer(state, action);\n\n        expect(newState.data).toMatchInlineSnapshot(`\n          {\n            \"content\": [],\n            \"root\": {\n              \"props\": {\n                \"slot\": [],\n                \"title\": \"\",\n              },\n            },\n            \"zones\": {},\n          }\n        `);\n\n        expect(newState.indexes).toMatchInlineSnapshot(`\n          {\n            \"nodes\": {\n              \"root\": {\n                \"data\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": [],\n                    \"title\": \"\",\n                  },\n                  \"type\": \"root\",\n                },\n                \"flatData\": {\n                  \"props\": {\n                    \"id\": \"root\",\n                    \"slot\": null,\n                    \"title\": \"\",\n                  },\n                  \"type\": \"root\",\n                },\n                \"parentId\": null,\n                \"path\": [],\n                \"zone\": \"\",\n              },\n            },\n            \"zones\": {\n              \"root:default-zone\": {\n                \"contentIds\": [],\n                \"type\": \"root\",\n              },\n              \"root:slot\": {\n                \"contentIds\": [],\n                \"type\": \"slot\",\n              },\n            },\n          }\n        `);\n      });\n    });\n\n    it(\"should deselect the item\", () => {\n      const newState = executeSequence(defaultState, [\n        () => ({\n          type: \"insert\",\n          componentType: \"Comp\",\n          destinationZone: \"root:slot\",\n          destinationIndex: 0,\n          id: \"1\",\n        }),\n        () => ({\n          type: \"remove\",\n          index: 0,\n          zone: \"root:slot\",\n        }),\n      ]);\n\n      expect(newState.ui.itemSelector).toBeNull();\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/reorder.spec.ts",
    "content": "import { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport {\n  defaultState,\n  dzZoneCompound,\n  expectIndexed,\n  testSetup,\n} from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { executeSequence } = testSetup();\n\n  describe(\"reorder action\", () => {\n    describe(\"with DropZones\", () => {\n      it(\"should reorder within rootDroppableId\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 1,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"reorder\",\n            sourceIndex: 0,\n            destinationIndex: 1,\n            destinationZone: rootDroppableId,\n          }),\n        ]);\n\n        expect(newState.data.content[0].props.id).toBe(\"2\");\n        expect(newState.data.content[1].props.id).toBe(\"1\");\n        expectIndexed(newState, newState.data.content[0], [rootDroppableId], 0);\n        expectIndexed(newState, newState.data.content[1], [rootDroppableId], 1);\n      });\n\n      it(\"should reorder within a different zone\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 1,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"reorder\",\n            sourceIndex: 0,\n            destinationIndex: 1,\n            destinationZone: dzZoneCompound,\n          }),\n        ]);\n\n        expect(newState.data.zones?.[dzZoneCompound][0].props.id).toBe(\"2\");\n        expect(newState.data.zones?.[dzZoneCompound][1].props.id).toBe(\"1\");\n        expectIndexed(\n          newState,\n          newState.data.content[0],\n          [rootDroppableId, dzZoneCompound],\n          0\n        );\n        expectIndexed(\n          newState,\n          newState.data.content[1],\n          [rootDroppableId, dzZoneCompound],\n          1\n        );\n      });\n    });\n\n    describe(\"with slots\", () => {\n      it(\"should reorder within a slot\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 1,\n            id: \"2\",\n          }),\n          () => ({\n            type: \"reorder\",\n            sourceIndex: 0,\n            destinationIndex: 1,\n            destinationZone: \"root:slot\",\n          }),\n        ]);\n\n        expect(newState.data.root.props?.slot[0].props.id).toBe(\"2\");\n        expect(newState.data.root.props?.slot[1].props.id).toBe(\"1\");\n        expectIndexed(\n          newState,\n          newState.data.root.props?.slot[0],\n          [\"root:slot\"],\n          0\n        );\n        expectIndexed(\n          newState,\n          newState.data.root.props?.slot[1],\n          [\"root:slot\"],\n          1\n        );\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/replace.spec.ts",
    "content": "import { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport {\n  defaultState,\n  dzZoneCompound,\n  expectIndexed,\n  testSetup,\n} from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { executeSequence } = testSetup();\n\n  describe(\"replace action\", () => {\n    describe(\"with DropZones\", () => {\n      it(\"should replace in content\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"replace\",\n            destinationZone: rootDroppableId,\n            destinationIndex: 0,\n            data: {\n              type: \"Comp\",\n              props: {\n                id: \"1\",\n                prop: \"Changed\",\n              },\n            },\n          }),\n        ]);\n\n        expect(newState.data.content.length).toBe(1);\n        expect(newState.data.content[0].props.prop).toBe(\"Changed\");\n        expectIndexed(newState, newState.data.content[0], [rootDroppableId], 0);\n      });\n\n      it(\"should replace in a zone\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"replace\",\n            destinationZone: dzZoneCompound,\n            destinationIndex: 0,\n            data: {\n              type: \"Comp\",\n              props: {\n                id: \"1\",\n                prop: \"Changed\",\n              },\n            },\n          }),\n        ]);\n\n        expect(newState.data.zones?.[dzZoneCompound].length).toBe(1);\n        expect(newState.data.zones?.[dzZoneCompound][0].props.prop).toBe(\n          \"Changed\"\n        );\n        expectIndexed(newState, newState.data.content[0], [rootDroppableId], 0);\n      });\n    });\n\n    describe(\"with slots\", () => {\n      it(\"should replace in deep slots\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"replace\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            data: {\n              type: \"CompWithDefaults\",\n              props: { id: \"1\", slot: [{ type: \"Comp\", props: {} }] },\n            },\n          }),\n        ]);\n\n        const item = newState.data.root.props?.slot[0];\n\n        expect(newState.data.root.props?.slot.length).toBe(1);\n        expect(item?.props.id).toBe(\"1\");\n        expect(item?.props.slot[0].props.id).toBe(\"mockId-1\");\n        expectIndexed(newState, item, [\"root:slot\"], 0);\n      });\n\n      it(\"should replace when slot removed from array\", () => {\n        const newState = executeSequence(defaultState, [\n          () => ({\n            type: \"insert\",\n            componentType: \"Comp\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            id: \"1\",\n          }),\n          () => ({\n            type: \"replace\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            data: {\n              type: \"CompWithDefaults\",\n              props: {\n                id: \"1\",\n                slot: [],\n                slotArray: [\n                  { slot: [{ type: \"Comp\", props: {} }] },\n                  { slot: [{ type: \"Comp\", props: {} }] },\n                ],\n              },\n            },\n          }),\n        ]);\n\n        const item = newState.data.root.props?.slot[0];\n\n        const slottedItem1 = item?.props.slotArray[0].slot[0];\n        const slottedItem2 = item?.props.slotArray[1].slot[0];\n\n        expectIndexed(\n          newState,\n          slottedItem1,\n          [\"root:slot\", \"1:slotArray[0].slot\"],\n          0\n        );\n        expectIndexed(\n          newState,\n          slottedItem2,\n          [\"root:slot\", \"1:slotArray[1].slot\"],\n          0\n        );\n\n        const removedState = executeSequence(newState, [\n          () => ({\n            type: \"replace\",\n            destinationZone: \"root:slot\",\n            destinationIndex: 0,\n            data: {\n              type: \"CompWithDefaults\",\n              props: {\n                id: \"1\",\n                slot: [],\n                slotArray: [{ slot: [slottedItem1] }],\n              },\n            },\n          }),\n        ]);\n\n        expectIndexed(\n          removedState,\n          slottedItem1,\n          [\"root:slot\", \"1:slotArray[0].slot\"],\n          0\n        );\n\n        expect(\n          removedState.indexes.zones[\"1:slotArray[1].slot\"]\n        ).toBeUndefined();\n      });\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/set-ui.spec.ts",
    "content": "import { SetUiAction } from \"../..\";\nimport { defaultState, testSetup } from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { reducer } = testSetup();\n\n  describe(\"setUi action\", () => {\n    it(\"should insert data into the state\", () => {\n      const action: SetUiAction = {\n        type: \"setUi\",\n        ui: { leftSideBarVisible: false },\n      };\n\n      const newState = reducer(defaultState, action);\n      expect(newState.ui.leftSideBarVisible).toEqual(false);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/__tests__/set.spec.ts",
    "content": "import { SetDataAction } from \"../..\";\nimport { defaultData, defaultState, testSetup } from \"../__helpers__\";\n\ndescribe(\"Reducer\", () => {\n  const { reducer } = testSetup();\n\n  describe(\"set action\", () => {\n    it(\"should set new data\", () => {\n      const newData = {\n        ...defaultData,\n        root: { props: { title: \"Hello, world\", slot: [] } },\n        content: [{ type: \"Comp\", props: { id: \"1\", slot: [] } }],\n      };\n\n      const action: SetDataAction = {\n        type: \"setData\",\n        data: newData,\n      };\n\n      const newState = reducer(defaultState, action);\n      expect(newState.data).toEqual(newData);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/reducer/actions/duplicate.ts",
    "content": "import { Data } from \"../../types\";\nimport { generateId } from \"../../lib/generate-id\";\nimport { DuplicateAction } from \"../actions\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\nimport { getIdsForParent } from \"../../lib/data/get-ids-for-parent\";\nimport { getItem } from \"../../lib/data/get-item\";\nimport { AppStore } from \"../../store\";\nimport { insert } from \"../../lib/data/insert\";\n\nexport function duplicateAction<UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: DuplicateAction,\n  appStore: AppStore\n): PrivateAppState<UserData> {\n  const item = getItem(\n    { index: action.sourceIndex, zone: action.sourceZone },\n    state\n  )!;\n\n  const idsInPath = getIdsForParent(action.sourceZone, state);\n\n  const newItem = {\n    ...item,\n    props: {\n      ...item.props,\n      id: generateId(item.type),\n    },\n  };\n\n  const modified = walkAppState<UserData>(\n    state,\n    appStore.config,\n    (content, zoneCompound) => {\n      if (zoneCompound === action.sourceZone) {\n        return insert(content, action.sourceIndex + 1, item);\n      }\n\n      return content;\n    },\n    (childItem, path, index) => {\n      const zoneCompound = path[path.length - 1];\n\n      const parents = path.map((p) => p.split(\":\")[0]);\n\n      if (parents.indexOf(newItem.props.id) > -1) {\n        return {\n          ...childItem,\n          props: {\n            ...childItem.props,\n            id: generateId(childItem.type),\n          },\n        };\n      }\n\n      if (\n        zoneCompound === action.sourceZone &&\n        index === action.sourceIndex + 1\n      ) {\n        return newItem;\n      }\n\n      const [sourceZoneParent] = action.sourceZone.split(\":\");\n\n      if (\n        sourceZoneParent === childItem.props.id ||\n        idsInPath.indexOf(childItem.props.id) > -1\n      ) {\n        return childItem;\n      }\n\n      return null;\n    }\n  );\n\n  return {\n    ...modified,\n    ui: {\n      ...modified.ui,\n      itemSelector: {\n        index: action.sourceIndex + 1,\n        zone: action.sourceZone,\n      },\n    },\n  };\n}\n"
  },
  {
    "path": "packages/core/reducer/actions/insert.ts",
    "content": "import { Data } from \"../../types\";\nimport { insert } from \"../../lib/data/insert\";\nimport { generateId } from \"../../lib/generate-id\";\nimport { InsertAction } from \"../actions\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\nimport { getIdsForParent } from \"../../lib/data/get-ids-for-parent\";\nimport { AppStore } from \"../../store\";\nimport { populateIds } from \"../../lib/data/populate-ids\";\n\nexport function insertAction<UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: InsertAction,\n  appStore: AppStore\n): PrivateAppState<UserData> {\n  const id = action.id || generateId(action.componentType);\n  const emptyComponentData = populateIds(\n    {\n      type: action.componentType,\n      props: {\n        ...(appStore.config.components[action.componentType].defaultProps ||\n          {}),\n        id,\n      },\n    },\n    appStore.config\n  );\n\n  const [parentId] = action.destinationZone.split(\":\");\n  const idsInPath = getIdsForParent(action.destinationZone, state);\n\n  return walkAppState<UserData>(\n    state,\n    appStore.config,\n    (content, zoneCompound) => {\n      if (zoneCompound === action.destinationZone) {\n        return insert(\n          content || [],\n          action.destinationIndex,\n          emptyComponentData\n        );\n      }\n\n      return content;\n    },\n    (childItem, path) => {\n      if (childItem.props.id === id || childItem.props.id === parentId) {\n        return childItem;\n      } else if (idsInPath.includes(childItem.props.id)) {\n        return childItem;\n      } else if (path.includes(action.destinationZone)) {\n        return childItem;\n      }\n\n      return null;\n    }\n  );\n}\n"
  },
  {
    "path": "packages/core/reducer/actions/move.ts",
    "content": "import { Content, Data } from \"../../types\";\nimport { insert } from \"../../lib/data/insert\";\nimport { remove } from \"../../lib/data/remove\";\nimport { getItem } from \"../../lib/data/get-item\";\nimport { MoveAction } from \"../actions\";\nimport { AppStore } from \"../../store\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\nimport { getIdsForParent } from \"../../lib/data/get-ids-for-parent\";\n\n// Restore unregistered zones when re-registering in same session\nexport const zoneCache: Record<string, Content> = {};\n\nexport const addToZoneCache = (key: string, data: Content) => {\n  zoneCache[key] = data;\n};\n\nexport const moveAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: MoveAction,\n  appStore: AppStore\n): PrivateAppState<UserData> => {\n  if (\n    action.sourceZone === action.destinationZone &&\n    action.sourceIndex === action.destinationIndex\n  ) {\n    return state;\n  }\n\n  const item = getItem(\n    { zone: action.sourceZone, index: action.sourceIndex },\n    state\n  );\n\n  if (!item) return state;\n\n  const idsInSourcePath = getIdsForParent(action.sourceZone, state);\n  const idsInDestinationPath = getIdsForParent(action.destinationZone, state);\n\n  return walkAppState<UserData>(\n    state,\n    appStore.config,\n    (content, zoneCompound) => {\n      if (\n        zoneCompound === action.sourceZone &&\n        zoneCompound === action.destinationZone\n      ) {\n        return insert(\n          remove(content, action.sourceIndex),\n          action.destinationIndex,\n          item\n        );\n      } else if (zoneCompound === action.sourceZone) {\n        return remove(content, action.sourceIndex);\n      } else if (zoneCompound === action.destinationZone) {\n        return insert(content, action.destinationIndex, item);\n      }\n\n      return content;\n    },\n    (childItem, path) => {\n      const [sourceZoneParent] = action.sourceZone.split(\":\");\n      const [destinationZoneParent] = action.destinationZone.split(\":\");\n\n      const childId = childItem.props.id;\n\n      if (\n        sourceZoneParent === childId ||\n        destinationZoneParent === childId ||\n        item.props.id === childId ||\n        idsInSourcePath.indexOf(childId) > -1 ||\n        idsInDestinationPath.indexOf(childId) > -1 ||\n        path.includes(action.destinationZone)\n      ) {\n        return childItem;\n      }\n\n      return null;\n    }\n  );\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/register-zone.ts",
    "content": "import { RegisterZoneAction, UnregisterZoneAction } from \"..\";\nimport { setupZone } from \"../../lib/data/setup-zone\";\nimport { Content, Data } from \"../../types\";\nimport { PrivateAppState } from \"../../types/Internal\";\n\n// Restore unregistered zones when re-registering in same session\nexport const zoneCache: Record<string, Content> = {};\n\nexport const addToZoneCache = (key: string, data: Content) => {\n  zoneCache[key] = data;\n};\n\nexport function registerZoneAction<UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: RegisterZoneAction\n): PrivateAppState<UserData> {\n  if (zoneCache[action.zone]) {\n    return {\n      ...state,\n      data: {\n        ...state.data,\n        zones: {\n          ...state.data.zones,\n          [action.zone]: zoneCache[action.zone],\n        },\n      },\n      indexes: {\n        ...state.indexes,\n        zones: {\n          ...state.indexes.zones,\n          [action.zone]: {\n            ...state.indexes.zones[action.zone],\n            contentIds: zoneCache[action.zone].map((item) => item.props.id),\n            type: \"dropzone\",\n          },\n        },\n      },\n    };\n  }\n\n  return { ...state, data: setupZone(state.data, action.zone) };\n}\n\nexport function unregisterZoneAction<UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: UnregisterZoneAction\n): PrivateAppState<UserData> {\n  const _zones = { ...(state.data.zones || {}) };\n  const zoneIndex = { ...(state.indexes.zones || {}) };\n\n  if (_zones[action.zone]) {\n    zoneCache[action.zone] = _zones[action.zone];\n\n    delete _zones[action.zone];\n  }\n\n  delete zoneIndex[action.zone];\n\n  return {\n    ...state,\n    data: {\n      ...state.data,\n      zones: _zones,\n    },\n    indexes: {\n      ...state.indexes,\n      zones: zoneIndex,\n    },\n  };\n}\n"
  },
  {
    "path": "packages/core/reducer/actions/remove.ts",
    "content": "import { Data } from \"../../types\";\nimport { remove } from \"../../lib/data/remove\";\nimport { getItem } from \"../../lib/data/get-item\";\nimport { RemoveAction } from \"../actions\";\nimport { AppStore } from \"../../store\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\n\nexport const removeAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: RemoveAction,\n  appStore: AppStore\n) => {\n  const item = getItem({ index: action.index, zone: action.zone }, state)!;\n\n  // Gather related\n  const nodesToDelete = Object.entries(state.indexes.nodes).reduce<string[]>(\n    (acc, [nodeId, nodeData]) => {\n      const pathIds = nodeData.path.map((p) => p.split(\":\")[0]);\n      if (pathIds.includes(item.props.id)) {\n        return [...acc, nodeId];\n      }\n\n      return acc;\n    },\n    [item.props.id]\n  );\n\n  const newState = walkAppState<UserData>(\n    state,\n    appStore.config,\n    (content, zoneCompound) => {\n      if (zoneCompound === action.zone) {\n        return remove(content, action.index);\n      }\n\n      return content;\n    }\n  );\n\n  Object.keys(newState.data.zones || {}).forEach((zoneCompound) => {\n    const parentId = zoneCompound.split(\":\")[0];\n\n    if (nodesToDelete.includes(parentId) && newState.data.zones) {\n      delete newState.data.zones[zoneCompound];\n    }\n  });\n\n  Object.keys(newState.indexes.zones).forEach((zoneCompound) => {\n    const parentId = zoneCompound.split(\":\")[0];\n\n    if (nodesToDelete.includes(parentId)) {\n      delete newState.indexes.zones[zoneCompound];\n    }\n  });\n\n  nodesToDelete.forEach((id) => {\n    delete newState.indexes.nodes[id];\n  });\n\n  return newState;\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/reorder.ts",
    "content": "import { Data } from \"../../types\";\nimport { ReorderAction } from \"../actions\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { moveAction } from \"./move\";\nimport { AppStore } from \"../../store\";\n\nexport const reorderAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: ReorderAction,\n  appStore: AppStore\n): PrivateAppState<UserData> => {\n  return moveAction(\n    state,\n    {\n      type: \"move\",\n      sourceIndex: action.sourceIndex,\n      sourceZone: action.destinationZone,\n      destinationIndex: action.destinationIndex,\n      destinationZone: action.destinationZone,\n    },\n    appStore\n  );\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/replace-root.ts",
    "content": "import { Data } from \"../../types\";\nimport { ReplaceRootAction } from \"../actions\";\nimport { AppStore } from \"../../store\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\n\nexport const replaceRootAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: ReplaceRootAction<UserData>,\n  appStore: AppStore\n): PrivateAppState<UserData> => {\n  return walkAppState<UserData>(\n    state,\n    appStore.config,\n    (content) => content,\n    (childItem) => {\n      if (childItem.props.id === \"root\") {\n        return {\n          ...childItem,\n          props: { ...childItem.props, ...action.root.props },\n          readOnly: action.root.readOnly,\n        };\n      }\n\n      // Everything in inside root, so everything needs re-indexing\n      return childItem;\n    }\n  );\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/replace.ts",
    "content": "import { ComponentDataOptionalId, Data } from \"../../types\";\nimport { ReplaceAction } from \"../actions\";\nimport { AppStore } from \"../../store\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\nimport { getIdsForParent } from \"../../lib/data/get-ids-for-parent\";\nimport { walkTree } from \"../../lib/data/walk-tree\";\nimport { generateId } from \"../../lib/generate-id\";\n\nexport const replaceAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: ReplaceAction<UserData>,\n  appStore: AppStore\n): PrivateAppState<UserData> => {\n  const [parentId] = action.destinationZone.split(\":\");\n  const idsInPath = getIdsForParent(action.destinationZone, state);\n\n  const originalId =\n    state.indexes.zones[action.destinationZone].contentIds[\n      action.destinationIndex\n    ];\n\n  const idChanged = originalId !== action.data.props.id;\n\n  if (idChanged) {\n    throw new Error(\n      'Can\\'t change the id during a replace action. Please us \"remove\" and \"insert\" to define a new node.'\n    );\n  }\n\n  const newSlotIds: string[] = [];\n\n  // Populate ids and collect nested slot IDs\n  // We use explicit function here so we don't need to walk tree twice\n  const data = walkTree(action.data, appStore.config, (contents, opts) => {\n    newSlotIds.push(`${opts.parentId}:${opts.propName}`);\n\n    return contents.map((item: ComponentDataOptionalId) => {\n      const id = generateId(item.type);\n\n      return {\n        ...item,\n        props: { id, ...item.props },\n      };\n    });\n  });\n\n  const stateWithDeepSlotsRemoved = {\n    ...state,\n    ui: { ...state.ui, ...action.ui },\n  };\n\n  Object.keys(state.indexes.zones).forEach((zoneCompound) => {\n    const id = zoneCompound.split(\":\")[0];\n\n    if (id === originalId) {\n      if (!newSlotIds.includes(zoneCompound)) {\n        delete stateWithDeepSlotsRemoved.indexes.zones[zoneCompound];\n      }\n    }\n  });\n\n  return walkAppState<UserData>(\n    stateWithDeepSlotsRemoved,\n    appStore.config,\n    (content, zoneCompound) => {\n      const newContent = [...content];\n\n      if (zoneCompound === action.destinationZone) {\n        newContent[action.destinationIndex] = data;\n      }\n\n      return newContent;\n    },\n    (childItem, path) => {\n      const pathIds = path.map((p) => p.split(\":\")[0]);\n\n      if (childItem.props.id === data.props.id) {\n        return data;\n      } else if (childItem.props.id === parentId) {\n        return childItem;\n      } else if (idsInPath.indexOf(childItem.props.id) > -1) {\n        // Node is parent of target\n        return childItem;\n      } else if (pathIds.indexOf(data.props.id) > -1) {\n        // Node is child target\n        return childItem;\n      }\n\n      return null;\n    }\n  );\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/set-data.ts",
    "content": "import { Data } from \"../../types\";\nimport { SetDataAction } from \"../actions\";\nimport { AppStore } from \"../../store\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\n\nexport const setDataAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: SetDataAction,\n  appStore: AppStore\n): PrivateAppState<UserData> => {\n  if (typeof action.data === \"object\") {\n    console.warn(\n      \"`setData` is expensive and may cause unnecessary re-renders. Consider using a more atomic action instead.\"\n    );\n\n    return walkAppState(\n      {\n        ...state,\n        data: {\n          ...state.data,\n          ...action.data,\n        },\n      },\n      appStore.config\n    );\n  }\n\n  return walkAppState(\n    {\n      ...state,\n      data: {\n        ...state.data,\n        ...action.data(state.data),\n      },\n    },\n    appStore.config\n  );\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/set-ui.ts",
    "content": "import { Data } from \"../../types\";\nimport { SetUiAction } from \"../actions\";\nimport { PrivateAppState } from \"../../types/Internal\";\n\nexport const setUiAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: SetUiAction\n): PrivateAppState<UserData> => {\n  if (typeof action.ui === \"object\") {\n    return {\n      ...state,\n      ui: {\n        ...state.ui,\n        ...action.ui,\n      },\n    };\n  }\n\n  return {\n    ...state,\n    ui: {\n      ...state.ui,\n      ...action.ui(state.ui),\n    },\n  };\n};\n"
  },
  {
    "path": "packages/core/reducer/actions/set.ts",
    "content": "import { Data } from \"../../types\";\nimport { SetAction } from \"../actions\";\nimport { AppStore } from \"../../store\";\nimport { PrivateAppState } from \"../../types/Internal\";\nimport { walkAppState } from \"../../lib/data/walk-app-state\";\n\nexport const setAction = <UserData extends Data>(\n  state: PrivateAppState<UserData>,\n  action: SetAction<UserData>,\n  appStore: AppStore\n): PrivateAppState<UserData> => {\n  if (typeof action.state === \"object\") {\n    const newState = {\n      ...state,\n      ...action.state,\n    };\n\n    if (action.state.indexes) {\n      return newState;\n    }\n\n    console.warn(\n      \"`set` is expensive and may cause unnecessary re-renders. Consider using a more atomic action instead.\"\n    );\n\n    return walkAppState(newState, appStore.config);\n  }\n\n  return { ...state, ...action.state(state) };\n};\n"
  },
  {
    "path": "packages/core/reducer/actions.tsx",
    "content": "import { AppState, ComponentData, Data, RootData, UiState } from \"../types\";\nimport { PrivateAppState } from \"../types/Internal\";\n\nexport type InsertAction = {\n  type: \"insert\";\n  componentType: string;\n  destinationIndex: number;\n  destinationZone: string;\n  id?: string;\n};\n\nexport type DuplicateAction = {\n  type: \"duplicate\";\n  sourceIndex: number;\n  sourceZone: string;\n};\n\nexport type ReplaceAction<UserData extends Data = Data> = {\n  type: \"replace\";\n  destinationIndex: number;\n  destinationZone: string;\n  data: ComponentData;\n  ui?: Partial<AppState<UserData>[\"ui\"]>;\n};\n\nexport type ReplaceRootAction<UserData extends Data = Data> = {\n  type: \"replaceRoot\";\n  root: RootData;\n  ui?: Partial<AppState<UserData>[\"ui\"]>;\n};\n\nexport type ReorderAction = {\n  type: \"reorder\";\n  sourceIndex: number;\n  destinationIndex: number;\n  destinationZone: string;\n};\n\nexport type MoveAction = {\n  type: \"move\";\n  sourceIndex: number;\n  sourceZone: string;\n  destinationIndex: number;\n  destinationZone: string;\n};\n\nexport type RemoveAction = {\n  type: \"remove\";\n  index: number;\n  zone: string;\n};\n\nexport type SetUiAction = {\n  type: \"setUi\";\n  ui: Partial<UiState> | ((previous: UiState) => Partial<UiState>);\n};\n\nexport type SetDataAction = {\n  type: \"setData\";\n  data: Partial<Data> | ((previous: Data) => Partial<Data>);\n};\n\nexport type SetAction<UserData extends Data = Data> = {\n  type: \"set\";\n  state:\n    | Partial<PrivateAppState<UserData>>\n    | ((\n        previous: PrivateAppState<UserData>\n      ) => Partial<PrivateAppState<UserData>>);\n};\n\nexport type RegisterZoneAction = {\n  type: \"registerZone\";\n  zone: string;\n};\n\nexport type UnregisterZoneAction = {\n  type: \"unregisterZone\";\n  zone: string;\n};\n\nexport type PuckAction = { recordHistory?: boolean } & (\n  | ReorderAction\n  | InsertAction\n  | MoveAction\n  | ReplaceAction\n  | ReplaceRootAction\n  | RemoveAction\n  | DuplicateAction\n  | SetAction\n  | SetDataAction\n  | SetUiAction\n  | RegisterZoneAction\n  | UnregisterZoneAction\n);\n"
  },
  {
    "path": "packages/core/reducer/index.ts",
    "content": "import { Reducer } from \"react\";\nimport { AppState, Data } from \"../types\";\nimport { PuckAction } from \"./actions\";\nimport type { OnAction } from \"../types\";\nimport { AppStore } from \"../store\";\nimport { PrivateAppState } from \"../types/Internal\";\nimport { setAction } from \"./actions/set\";\nimport { insertAction } from \"./actions/insert\";\nimport { replaceAction } from \"./actions/replace\";\nimport { replaceRootAction } from \"./actions/replace-root\";\nimport { duplicateAction } from \"./actions/duplicate\";\nimport { reorderAction } from \"./actions/reorder\";\nimport { moveAction } from \"./actions/move\";\nimport { removeAction } from \"./actions/remove\";\nimport {\n  registerZoneAction,\n  unregisterZoneAction,\n} from \"./actions/register-zone\";\nimport { setDataAction } from \"./actions/set-data\";\nimport { setUiAction } from \"./actions/set-ui\";\nimport { makeStatePublic } from \"../lib/data/make-state-public\";\n\nexport * from \"./actions\";\n\nexport type ActionType = \"insert\" | \"reorder\";\n\nexport type StateReducer<UserData extends Data = Data> = Reducer<\n  PrivateAppState<UserData>,\n  PuckAction\n>;\n\nfunction storeInterceptor<UserData extends Data = Data>(\n  reducer: StateReducer<UserData>,\n  record?: (appState: AppState<UserData>) => void,\n  onAction?: OnAction<UserData>\n) {\n  return (\n    state: PrivateAppState<UserData>,\n    action: PuckAction\n  ): PrivateAppState<UserData> => {\n    const newAppState = reducer(state, action);\n\n    const isValidType = ![\n      \"registerZone\",\n      \"unregisterZone\",\n      \"setData\",\n      \"setUi\",\n      \"set\",\n    ].includes(action.type);\n\n    if (\n      typeof action.recordHistory !== \"undefined\"\n        ? action.recordHistory\n        : isValidType\n    ) {\n      if (record) record(newAppState);\n    }\n\n    onAction?.(action, makeStatePublic(newAppState), makeStatePublic(state));\n\n    return newAppState;\n  };\n}\n\nexport function createReducer<UserData extends Data>({\n  record,\n  onAction,\n  appStore,\n}: {\n  record?: (appState: AppState<UserData>) => void;\n  onAction?: OnAction<UserData>;\n  appStore: AppStore;\n}): StateReducer<UserData> {\n  return storeInterceptor(\n    (state, action) => {\n      if (action.type === \"set\") {\n        return setAction(state, action, appStore) as PrivateAppState<UserData>;\n      }\n\n      if (action.type === \"insert\") {\n        return insertAction(state, action, appStore);\n      }\n\n      if (action.type === \"replace\") {\n        return replaceAction(state, action, appStore);\n      }\n\n      if (action.type === \"replaceRoot\") {\n        return replaceRootAction(state, action, appStore);\n      }\n\n      if (action.type === \"duplicate\") {\n        return duplicateAction(state, action, appStore);\n      }\n\n      if (action.type === \"reorder\") {\n        return reorderAction(state, action, appStore);\n      }\n\n      if (action.type === \"move\") {\n        return moveAction(state, action, appStore);\n      }\n\n      if (action.type === \"remove\") {\n        return removeAction(state, action, appStore);\n      }\n\n      if (action.type === \"registerZone\") {\n        return registerZoneAction(state, action);\n      }\n\n      if (action.type === \"unregisterZone\") {\n        return unregisterZoneAction(state, action);\n      }\n\n      if (action.type === \"setData\") {\n        return setDataAction(state, action, appStore);\n      }\n\n      if (action.type === \"setUi\") {\n        return setUiAction(state, action);\n      }\n\n      return state;\n    },\n    record,\n    onAction\n  );\n}\n"
  },
  {
    "path": "packages/core/store/default-app-state.ts",
    "content": "import { defaultViewports } from \"../components/ViewportControls/default-viewports\";\nimport { PrivateAppState } from \"../types/Internal\";\n\nexport const defaultAppState: PrivateAppState = {\n  data: { content: [], root: {}, zones: {} },\n  ui: {\n    leftSideBarVisible: true,\n    rightSideBarVisible: true,\n    arrayState: {},\n    itemSelector: null,\n    componentList: {},\n    isDragging: false,\n    previewMode: \"edit\",\n    viewports: {\n      current: {\n        width: defaultViewports[0].width,\n        height: defaultViewports[0].height || \"auto\",\n      },\n      options: [],\n      controlsVisible: true,\n    },\n    field: { focus: null },\n    plugin: { current: null },\n  },\n  indexes: {\n    nodes: {},\n    zones: {},\n  },\n};\n"
  },
  {
    "path": "packages/core/store/index.ts",
    "content": "\"use client\";\n\nimport {\n  Config,\n  IframeConfig,\n  Overrides,\n  AppState,\n  UiState,\n  Plugin,\n  UserGenerics,\n  Field,\n  ComponentConfig,\n  Metadata,\n  ComponentData,\n  RootDataWithProps,\n  ResolveDataTrigger,\n  RichtextField,\n} from \"../types\";\nimport { createReducer, PuckAction } from \"../reducer\";\nimport { getItem } from \"../lib/data/get-item\";\nimport { defaultViewports } from \"../components/ViewportControls/default-viewports\";\nimport { Viewports } from \"../types\";\nimport { create, StoreApi, useStore } from \"zustand\";\nimport { subscribeWithSelector } from \"zustand/middleware\";\nimport { createContext, useContext } from \"react\";\nimport { createHistorySlice, type HistorySlice } from \"./slices/history\";\nimport { createNodesSlice, type NodesSlice } from \"./slices/nodes\";\nimport {\n  createPermissionsSlice,\n  type PermissionsSlice,\n} from \"./slices/permissions\";\nimport { createFieldsSlice, type FieldsSlice } from \"./slices/fields\";\nimport { resolveComponentData } from \"../lib/resolve-component-data\";\nimport { walkAppState } from \"../lib/data/walk-app-state\";\nimport { toRoot } from \"../lib/data/to-root\";\nimport { generateId } from \"../lib/generate-id\";\nimport { defaultAppState } from \"./default-app-state\";\nimport { FieldTransforms } from \"../types/API/FieldTransforms\";\nimport type { Editor } from \"@tiptap/react\";\n\nexport { defaultAppState };\n\nexport type Status = \"LOADING\" | \"MOUNTED\" | \"READY\";\n\ntype ZoomConfig = {\n  autoZoom: number;\n  rootHeight: number;\n  zoom: number;\n};\n\ntype ComponentState = Record<string, { loadingCount: number }>;\n\nexport type AppStore<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n> = {\n  instanceId: string;\n  state: G[\"UserAppState\"];\n  dispatch: (action: PuckAction) => void;\n  config: UserConfig;\n  componentState: ComponentState;\n  setComponentState: (componentState: ComponentState) => void;\n  setComponentLoading: (\n    id: string,\n    loading?: boolean,\n    defer?: number\n  ) => () => void;\n  unsetComponentLoading: (id: string) => void;\n  pendingLoadTimeouts: Record<string, NodeJS.Timeout>;\n  resolveComponentData: <T extends ComponentData | RootDataWithProps>(\n    componentData: T,\n    trigger: ResolveDataTrigger\n  ) => Promise<{ node: T; didChange: boolean }>;\n  resolveAndCommitData: () => void;\n  plugins: Plugin[];\n  overrides: Partial<Overrides>;\n  viewports: Viewports;\n  zoomConfig: ZoomConfig;\n  setZoomConfig: (zoomConfig: ZoomConfig) => void;\n  status: Status;\n  setStatus: (status: Status) => void;\n  iframe: IframeConfig;\n  selectedItem?: G[\"UserData\"][\"content\"][0] | null;\n  getCurrentData: () => G[\"UserData\"][\"content\"][0] | G[\"UserData\"][\"root\"];\n  setUi: (ui: Partial<UiState>, recordHistory?: boolean) => void;\n  getComponentConfig: (type?: string) => ComponentConfig | null | undefined;\n  onAction?: (action: PuckAction, newState: AppState, state: AppState) => void;\n  metadata: Metadata;\n  fields: FieldsSlice;\n  history: HistorySlice;\n  nodes: NodesSlice;\n  permissions: PermissionsSlice;\n  fieldTransforms: FieldTransforms;\n  currentRichText?: {\n    inlineComponentId?: string;\n    inline: boolean;\n    field: RichtextField;\n    editor: Editor;\n    id: string;\n  } | null;\n};\n\nexport type AppStoreApi = StoreApi<AppStore>;\n\nconst defaultPageFields: Record<string, Field> = {\n  title: { type: \"text\" },\n};\n\nexport const createAppStore = (initialAppStore?: Partial<AppStore>) =>\n  create<AppStore>()(\n    subscribeWithSelector((set, get) => ({\n      instanceId: generateId(),\n      state: defaultAppState,\n      config: { components: {} },\n      componentState: {},\n      plugins: [],\n      overrides: {},\n      viewports: defaultViewports,\n      zoomConfig: {\n        autoZoom: 1,\n        rootHeight: 0,\n        zoom: 1,\n      },\n      status: \"LOADING\",\n      iframe: {},\n      metadata: {},\n      fieldTransforms: {},\n      ...initialAppStore,\n      fields: createFieldsSlice(set, get),\n      history: createHistorySlice(set, get),\n      nodes: createNodesSlice(set, get),\n      permissions: createPermissionsSlice(set, get),\n      getCurrentData: () => {\n        const s = get();\n\n        return s.selectedItem ?? s.state.data.root;\n      },\n      getComponentConfig: (type?: string) => {\n        const { config, selectedItem } = get();\n        const rootFields = config.root?.fields || defaultPageFields;\n\n        return type && type !== \"root\"\n          ? config.components[type]\n          : selectedItem\n          ? config.components[selectedItem.type]\n          : ({ ...config.root, fields: rootFields } as ComponentConfig);\n      },\n      selectedItem: initialAppStore?.state?.ui.itemSelector\n        ? getItem(\n            initialAppStore?.state?.ui.itemSelector,\n            initialAppStore.state\n          )\n        : null,\n      dispatch: (action: PuckAction) =>\n        set((s) => {\n          const { record } = get().history;\n\n          const dispatch = createReducer({\n            record,\n            appStore: s,\n          });\n\n          const state = dispatch(s.state, action);\n\n          const selectedItem = state.ui.itemSelector\n            ? getItem(state.ui.itemSelector, state)\n            : null;\n\n          get().onAction?.(action, state, get().state);\n\n          return { ...s, state, selectedItem };\n        }),\n      setZoomConfig: (zoomConfig) => set({ zoomConfig }),\n      setStatus: (status) => set({ status }),\n      setComponentState: (componentState) => set({ componentState }),\n      pendingLoadTimeouts: {},\n      setComponentLoading: (\n        id: string,\n        loading: boolean = true,\n        defer: number = 0\n      ) => {\n        const { setComponentState, pendingLoadTimeouts } = get();\n\n        const loadId = generateId();\n\n        const setLoading = () => {\n          const { componentState } = get();\n\n          setComponentState({\n            ...componentState,\n            [id]: {\n              ...componentState[id],\n              loadingCount: (componentState[id]?.loadingCount || 0) + 1,\n            },\n          });\n        };\n\n        const unsetLoading = () => {\n          const { componentState } = get();\n\n          clearTimeout(timeout);\n\n          delete pendingLoadTimeouts[loadId];\n\n          set({ pendingLoadTimeouts });\n\n          setComponentState({\n            ...componentState,\n            [id]: {\n              ...componentState[id],\n              loadingCount: Math.max(\n                (componentState[id]?.loadingCount || 0) - 1,\n                0\n              ),\n            },\n          });\n        };\n\n        const timeout = setTimeout(() => {\n          if (loading) {\n            setLoading();\n          } else {\n            unsetLoading();\n          }\n\n          delete pendingLoadTimeouts[loadId];\n\n          set({ pendingLoadTimeouts });\n        }, defer);\n\n        set({\n          pendingLoadTimeouts: {\n            ...pendingLoadTimeouts,\n            [id]: timeout,\n          },\n        });\n\n        return unsetLoading;\n      },\n      unsetComponentLoading: (id: string) => {\n        const { setComponentLoading } = get();\n\n        setComponentLoading(id, false);\n      },\n      // Helper\n      setUi: (ui: Partial<UiState>, recordHistory?: boolean) =>\n        set((s) => {\n          const dispatch = createReducer({\n            record: () => {},\n            appStore: s,\n          });\n\n          const state = dispatch(s.state, {\n            type: \"setUi\",\n            ui,\n            recordHistory,\n          });\n\n          const selectedItem = state.ui.itemSelector\n            ? getItem(state.ui.itemSelector, state)\n            : null;\n\n          return { ...s, state, selectedItem };\n        }),\n      resolveComponentData: async (componentData, trigger) => {\n        const { config, metadata, setComponentLoading, permissions, state } =\n          get();\n        const componentId =\n          \"id\" in componentData.props ? componentData.props.id : \"root\";\n        const parentId = state.indexes.nodes[componentId]?.parentId;\n        const parentNode = parentId ? state.indexes.nodes[parentId] : null;\n        const parentData = parentNode?.data ?? null;\n\n        const timeouts: Record<string, () => void> = {};\n\n        return await resolveComponentData(\n          componentData,\n          config,\n          metadata,\n          (item) => {\n            const id = \"id\" in item.props ? item.props.id : \"root\";\n            timeouts[id] = setComponentLoading(id, true, 50);\n          },\n          async (item) => {\n            const id = \"id\" in item.props ? item.props.id : \"root\";\n\n            if (\"type\" in item) {\n              await permissions.refreshPermissions({ item });\n            } else {\n              await permissions.refreshPermissions({ root: true });\n            }\n\n            timeouts[id]();\n          },\n          trigger,\n          parentData\n        );\n      },\n      resolveAndCommitData: async () => {\n        const { config, state, dispatch, resolveComponentData } = get();\n\n        walkAppState(\n          state,\n          config,\n          (content) => content,\n          (childItem) => {\n            resolveComponentData(childItem, \"load\").then((resolved) => {\n              const { state } = get();\n\n              const node = state.indexes.nodes[resolved.node.props.id];\n\n              // Ensure node hasn't been deleted whilst resolution happens\n              if (node && resolved.didChange) {\n                if (resolved.node.props.id === \"root\") {\n                  dispatch({\n                    type: \"replaceRoot\",\n                    root: toRoot(resolved.node),\n                  });\n                } else {\n                  // Use latest position, in case it's moved\n                  const zoneCompound = `${node.parentId}:${node.zone}`;\n                  const parentZone = state.indexes.zones[zoneCompound];\n\n                  const index = parentZone.contentIds.indexOf(\n                    resolved.node.props.id\n                  );\n\n                  dispatch({\n                    type: \"replace\",\n                    data: resolved.node,\n                    destinationIndex: index,\n                    destinationZone: zoneCompound,\n                  });\n                }\n              }\n            });\n\n            return childItem;\n          }\n        );\n      },\n    }))\n  );\n\nexport const appStoreContext = createContext(createAppStore());\n\nexport function useAppStore<T>(selector: (state: AppStore) => T) {\n  const context = useContext(appStoreContext);\n\n  return useStore(context, selector);\n}\n\nexport function useAppStoreApi() {\n  return useContext(appStoreContext);\n}\n"
  },
  {
    "path": "packages/core/store/slices/__tests__/fields.spec.tsx",
    "content": "import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport { useRegisterFieldsSlice } from \"../fields\";\nimport { createAppStore, defaultAppState } from \"../..\";\nimport { Config, ComponentData } from \"../../../types\";\nimport { PrivateAppState } from \"../../../types/Internal\";\nimport { walkAppState } from \"../../../lib/data/walk-app-state\";\nimport { makeStatePublic } from \"../../../lib/data/make-state-public\";\n\nconst baseState: PrivateAppState = {\n  ...defaultAppState,\n  data: {\n    content: [\n      {\n        type: \"Heading\",\n        props: { id: \"heading-1\", title: \"Hello\" },\n      },\n    ],\n    root: {},\n    zones: {},\n  },\n};\n\nconst appStore = createAppStore();\n\nfunction resetStores() {\n  // Reset main app store:\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n      state: walkAppState(baseState, appStore.getInitialState().config),\n    },\n    true\n  );\n}\n\nconst selectFirst = (config: Config) => {\n  appStore.setState({\n    ...appStore.getState(),\n    config,\n    selectedItem: appStore.getState().state.data.content[0],\n    state: walkAppState(\n      {\n        ...appStore.getState().state,\n        ui: {\n          ...appStore.getState().state.ui,\n          itemSelector: {\n            index: 0,\n          },\n        },\n      },\n      config\n    ),\n  });\n};\n\ndescribe(\"fields slice\", () => {\n  beforeEach(() => {\n    resetStores();\n  });\n\n  it(\"returns default fields if no resolver is defined\", async () => {\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n        },\n      },\n    };\n\n    selectFirst(config);\n\n    renderHook(() => useRegisterFieldsSlice(appStore, \"heading-1\"));\n\n    const { fields, loading } = appStore.getState().fields;\n\n    expect(fields).toEqual({ title: { type: \"text\" } });\n    expect(loading).toBe(false);\n  });\n\n  it(\"calls the root resolveFields if defined\", async () => {\n    const mockResolveFields = jest.fn().mockResolvedValue({\n      title: { type: \"textarea\" },\n    });\n\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n        },\n      },\n      root: {\n        resolveFields: mockResolveFields,\n      },\n    };\n\n    appStore.setState({\n      config,\n    });\n\n    renderHook(() => useRegisterFieldsSlice(appStore));\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n    expect(mockResolveFields).toHaveBeenCalledWith(\n      {\n        props: { id: \"root\" },\n        type: \"root\",\n      },\n      {\n        appState: makeStatePublic(appStore.getState().state),\n        changed: {\n          id: true,\n        },\n        fields: { title: { type: \"text\" } },\n        metadata: {},\n        lastData: {},\n        lastFields: { title: { type: \"text\" } },\n        parent: null,\n      }\n    );\n\n    await waitFor(() => {\n      const { fields, loading } = appStore.getState().fields;\n      expect(fields).toEqual({ title: { type: \"textarea\" } });\n      expect(loading).toBe(false);\n    });\n  });\n\n  it(\"calls a component's resolveFields if defined\", async () => {\n    const mockResolveFields = jest.fn().mockResolvedValue({\n      title: { type: \"textarea\" },\n    });\n\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n          resolveFields: mockResolveFields,\n        },\n      },\n    };\n\n    selectFirst(config);\n\n    renderHook(() => useRegisterFieldsSlice(appStore, \"heading-1\"));\n\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n    expect(mockResolveFields).toHaveBeenCalledWith(\n      {\n        props: { id: \"heading-1\", title: \"Hello\" },\n        type: \"Heading\",\n      },\n      {\n        appState: makeStatePublic(appStore.getState().state),\n        changed: {\n          id: true,\n          title: true,\n        },\n        fields: { title: { type: \"text\" } },\n        metadata: {},\n        lastData: null,\n        lastFields: { title: { type: \"text\" } },\n        parent: { props: { id: \"root\" }, type: \"root\" },\n      }\n    );\n\n    // We set a short timeout in the store (50 ms) before loading = true,\n    // so let's wait for that plus the async resolution:\n    await waitFor(() => {\n      const { fields, loading } = appStore.getState().fields;\n      // Once resolved, we expect the store to hold the new fields:\n      expect(fields).toEqual({ title: { type: \"textarea\" } });\n      expect(loading).toBe(false);\n    });\n  });\n\n  it(\"calls a component's resolveFields with correct fields on subsequent calls\", async () => {\n    const mockResolveFields = jest.fn().mockResolvedValue({\n      title: { type: \"textarea\" },\n    });\n\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n          resolveFields: mockResolveFields,\n        },\n      },\n    };\n\n    selectFirst(config);\n\n    renderHook(() => useRegisterFieldsSlice(appStore, \"heading-1\"));\n\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n\n    // We set a short timeout in the store (50 ms) before loading = true,\n    // so let's wait for that plus the async resolution:\n    await waitFor(() => {\n      const { fields } = appStore.getState().fields;\n      expect(fields).toEqual({ title: { type: \"textarea\" } });\n    });\n\n    mockResolveFields.mockReset();\n\n    appStore.getState().dispatch({\n      type: \"replace\",\n      data: {\n        ...appStore.getState().selectedItem!,\n        props: {\n          ...appStore.getState().selectedItem!.props,\n          title: \"Hello, world\",\n        },\n      },\n      destinationIndex: 0,\n      destinationZone: \"root:default-zone\",\n    });\n\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n    expect(mockResolveFields).toHaveBeenCalledWith(\n      {\n        props: { id: \"heading-1\", title: \"Hello, world\" },\n        type: \"Heading\",\n      },\n      {\n        appState: makeStatePublic(appStore.getState().state),\n        changed: {\n          id: false,\n          title: true,\n        },\n        fields: { title: { type: \"text\" } },\n        metadata: {},\n        lastData: {\n          props: { id: \"heading-1\", title: \"Hello\" },\n          type: \"Heading\",\n        },\n        lastFields: { title: { type: \"textarea\" } },\n        parent: { props: { id: \"root\" }, type: \"root\" },\n      }\n    );\n  });\n\n  it(\"calls a component's resolveFields with correct fields on subsequent calls, if item changes\", async () => {\n    const mockResolveFields = jest.fn().mockResolvedValue({\n      title: { type: \"textarea\" },\n    });\n\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n          resolveFields: mockResolveFields,\n        },\n        Block: {\n          fields: { title: { type: \"number\" } },\n          render: () => <div />,\n          resolveFields: mockResolveFields,\n        },\n      },\n    };\n\n    selectFirst(config);\n\n    const { rerender } = renderHook(\n      ({ id }: { id: string }) => useRegisterFieldsSlice(appStore, id),\n      { initialProps: { id: \"heading-1\" } }\n    );\n\n    // We set a short timeout in the store (50 ms) before loading = true,\n    // so let's wait for that plus the async resolution:\n    await waitFor(() => {\n      const { fields } = appStore.getState().fields;\n      expect(fields).toEqual({ title: { type: \"textarea\" } });\n    });\n\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n\n    // Change data and check result\n    act(() => {\n      const newItem: ComponentData = {\n        props: { id: \"block-1\", title: \"1\" },\n        type: \"Block\",\n      };\n\n      appStore.setState({\n        state: walkAppState(\n          {\n            ...appStore.getState().state,\n            data: {\n              ...appStore.getState().state.data,\n              content: [...appStore.getState().state.data.content, newItem],\n            },\n          },\n          config\n        ),\n        selectedItem: newItem,\n      });\n    });\n\n    mockResolveFields.mockReset();\n\n    rerender({ id: \"block-1\" });\n\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n    expect(mockResolveFields).toHaveBeenCalledWith(\n      {\n        props: { id: \"block-1\", title: \"1\" },\n        type: \"Block\",\n      },\n      {\n        appState: makeStatePublic(appStore.getState().state),\n        changed: {\n          id: true,\n          title: true,\n        },\n        fields: { title: { type: \"number\" } },\n        metadata: {},\n        lastData: null,\n        lastFields: { title: { type: \"number\" } },\n        parent: { props: { id: \"root\" }, type: \"root\" },\n      }\n    );\n  });\n\n  it(\"sets loading = true if resolver is slow (deferred loading)\", async () => {\n    // Make a resolver that waits\n    const mockResolveFields = jest\n      .fn()\n      .mockImplementation(async (data, { fields }) => {\n        return new Promise((resolve) => {\n          setTimeout(() => {\n            resolve({\n              ...fields,\n              body: { type: \"textarea\" },\n            });\n          }, 100);\n        });\n      });\n\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n          resolveFields: mockResolveFields,\n        },\n      },\n    };\n\n    selectFirst(config);\n\n    renderHook(() => useRegisterFieldsSlice(appStore, \"heading-1\"));\n\n    // After 50ms, loading should become true:\n    await waitFor(() => {\n      expect(appStore.getState().fields.loading).toBe(true);\n    });\n\n    // After 100ms total, the resolver finishes:\n    await waitFor(() => {\n      const { fields, loading } = appStore.getState().fields;\n      expect(fields).toEqual({\n        title: { type: \"text\" },\n        body: { type: \"textarea\" },\n      });\n      expect(loading).toBe(false);\n      expect(mockResolveFields).toHaveBeenCalledTimes(1);\n    });\n  });\n\n  it(\"updates fields again if node data changes\", async () => {\n    const mockResolveFields = jest.fn().mockResolvedValue({\n      title: { type: \"text\" },\n    });\n\n    const config: Config = {\n      components: {\n        Heading: {\n          fields: { title: { type: \"text\" } },\n          render: () => <div />,\n          resolveFields: mockResolveFields,\n        },\n      },\n    };\n\n    selectFirst(config);\n\n    renderHook(() => useRegisterFieldsSlice(appStore, \"heading-1\"));\n\n    // First call\n    expect(mockResolveFields).toHaveBeenCalledTimes(1);\n\n    // Now let's simulate a data change\n    const selectedItem = appStore.getState().selectedItem;\n    const updatedItem = {\n      ...selectedItem!,\n      props: { ...selectedItem?.props, title: \"Newly changed\" },\n    };\n\n    act(() => {\n      appStore.getState().dispatch({\n        type: \"replace\",\n        data: updatedItem,\n        destinationIndex: 0,\n        destinationZone: \"root:default-zone\",\n      });\n    });\n\n    // The subscription should trigger a second resolve:\n    await waitFor(() => {\n      expect(mockResolveFields).toHaveBeenCalledTimes(2);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/store/slices/__tests__/history.spec.tsx",
    "content": "import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport { useRegisterHistorySlice } from \"../history\";\nimport { defaultAppState, createAppStore } from \"../../\";\n\nconst appStore = createAppStore();\n\nfunction resetStores() {\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n    },\n    true\n  );\n}\n\ndescribe(\"history slice\", () => {\n  beforeEach(() => {\n    resetStores();\n  });\n\n  it(\"initializes with given histories and index\", () => {\n    renderHook(() =>\n      useRegisterHistorySlice(appStore, {\n        histories: [\n          { id: \"initial\", state: { ...defaultAppState, data: \"foo\" } },\n          { id: \"second\", state: { ...defaultAppState, data: \"bar\" } },\n        ],\n        index: 1,\n        initialAppState: defaultAppState,\n      })\n    );\n\n    const { histories, index, hasPast, hasFuture } =\n      appStore.getState().history;\n\n    expect(histories.length).toBe(2);\n    expect(index).toBe(1);\n    expect(hasPast()).toBe(true);\n    expect(hasFuture()).toBe(false);\n  });\n\n  describe(\"record()\", () => {\n    it(\"tracks the history\", () => {\n      jest.spyOn(appStore.getState(), \"dispatch\");\n      // register an initial set of histories\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [{ id: \"initial\", state: defaultAppState }],\n          index: 0,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      act(() => {\n        // call record with some data\n        appStore.getState().history.record({\n          ...defaultAppState,\n          data: { content: [], root: { props: { title: \"Hello, world\" } } },\n        });\n      });\n\n      waitFor(() => {\n        const { histories } = appStore.getState().history;\n        expect(histories.length).toBe(2);\n        expect(histories[1].state.data.root.props?.title).toBe(\"Hello, world\");\n      });\n    });\n  });\n\n  describe(\"back()\", () => {\n    it(\"does nothing if no past\", () => {\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [{ id: \"init\", state: defaultAppState }],\n          index: 0,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.back();\n      });\n\n      expect(appStore.getState().history.index).toBe(0);\n      expect(appStore.getState().dispatch).not.toHaveBeenCalled();\n    });\n\n    it(\"rewinds if hasPast\", () => {\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [\n            { id: \"0\", state: { ...defaultAppState, data: \"A\" } },\n            { id: \"1\", state: { ...defaultAppState, data: \"B\" } },\n          ],\n          index: 1,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.back();\n      });\n\n      expect(appStore.getState().history.index).toBe(0);\n      expect(appStore.getState().history.hasPast()).toBe(false);\n      expect(appStore.getState().history.hasFuture()).toBe(true);\n      expect(appStore.getState().dispatch).toHaveBeenCalledWith({\n        type: \"set\",\n        state: {\n          ...defaultAppState,\n          data: \"A\",\n        },\n      });\n    });\n  });\n\n  describe(\"forward()\", () => {\n    it(\"does nothing if no future\", () => {\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [{ id: \"0\", state: { ...defaultAppState, data: \"A\" } }],\n          index: 0,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.forward();\n      });\n\n      expect(appStore.getState().history.index).toBe(0);\n      expect(appStore.getState().dispatch).not.toHaveBeenCalled();\n    });\n\n    it(\"fast-forwards if hasFuture\", () => {\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [\n            { id: \"0\", state: { ...defaultAppState, data: \"A\" } },\n            { id: \"1\", state: { ...defaultAppState, data: \"B\" } },\n          ],\n          index: 0,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.forward();\n      });\n\n      expect(appStore.getState().history.index).toBe(1);\n      expect(appStore.getState().history.hasPast()).toBe(true);\n      expect(appStore.getState().history.hasFuture()).toBe(false);\n      expect(appStore.getState().dispatch).toHaveBeenCalledWith({\n        type: \"set\",\n        state: {\n          ...defaultAppState,\n          data: \"B\",\n        },\n      });\n    });\n  });\n\n  describe(\"setHistories()\", () => {\n    it(\"updates the state appropriately\", () => {\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [{ id: \"init\", state: defaultAppState }],\n          index: 0,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.setHistories([\n          {\n            id: \"1\",\n            state: { ...defaultAppState, data: \"One\" },\n          },\n          {\n            id: \"2\",\n            state: { ...defaultAppState, data: \"Two\" },\n          },\n        ]);\n      });\n\n      const { histories, index } = appStore.getState().history;\n      expect(histories.length).toBe(2);\n      expect(histories[1].state.data).toBe(\"Two\");\n      expect(index).toBe(1);\n      expect(appStore.getState().dispatch).toHaveBeenCalledWith({\n        type: \"set\",\n        state: { ...defaultAppState, data: \"Two\" },\n      });\n    });\n  });\n\n  describe(\"setHistoryIndex()\", () => {\n    it(\"sets the store index and dispatches that state's data\", () => {\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [\n            { id: \"0\", state: { ...defaultAppState, data: \"A\" } },\n            { id: \"1\", state: { ...defaultAppState, data: \"B\" } },\n          ],\n          index: 1,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.setHistoryIndex(0);\n      });\n\n      expect(appStore.getState().history.index).toBe(0);\n      expect(appStore.getState().dispatch).toHaveBeenCalledWith({\n        type: \"set\",\n        state: { ...defaultAppState, data: \"A\" },\n      });\n    });\n\n    it(\"does nothing if out of bounds\", () => {\n      // By default no histories, or just one:\n      renderHook(() =>\n        useRegisterHistorySlice(appStore, {\n          histories: [{ id: \"0\", state: defaultAppState }],\n          index: 0,\n          initialAppState: defaultAppState,\n        })\n      );\n\n      jest.spyOn(appStore.getState(), \"dispatch\");\n\n      act(() => {\n        appStore.getState().history.setHistoryIndex(5);\n      });\n\n      // The new code always calls dispatch with the \"current\" index's state before setting the new index,\n      // so if the store doesn't check bounds, it might still dispatch once.\n      // If your final code checks bounds, you'd expect no dispatch. Adjust the check as needed:\n      expect(appStore.getState().dispatch).toHaveBeenCalledTimes(1); // or 0 if you disallow out-of-bounds fully\n      expect(appStore.getState().history.index).toBe(5);\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/store/slices/__tests__/nodes.spec.tsx",
    "content": "import { act } from \"@testing-library/react\";\nimport { createAppStore } from \"../../\";\n\nconst appStore = createAppStore();\n\nfunction resetStores() {\n  // Reset the main app store\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n    },\n    true\n  );\n}\n\ndescribe(\"nodes slice\", () => {\n  beforeEach(() => {\n    resetStores();\n  });\n\n  it(\"registerNode stores the node data, merging on subsequent calls\", () => {\n    expect(Object.keys(appStore.getState().nodes.nodes)).toHaveLength(0);\n\n    const syncMethod = jest.fn();\n\n    act(() => {\n      appStore.getState().nodes.registerNode(\"test-1\", {\n        methods: {\n          sync: syncMethod,\n          hideOverlay: () => {},\n          showOverlay: () => {},\n        },\n      });\n    });\n\n    const initialNode = appStore.getState().nodes.nodes[\"test-1\"];\n    expect(initialNode).toBeDefined();\n\n    // Re-register with partial changes\n    act(() => {\n      appStore.getState().nodes.registerNode(\"test-1\", {\n        element: \"stub\" as any,\n      });\n    });\n\n    const updatedNode = appStore.getState().nodes.nodes[\"test-1\"];\n    expect(updatedNode).toBeDefined();\n    expect(updatedNode?.methods.sync).toBe(syncMethod);\n    expect(updatedNode?.element).toBe(\"stub\");\n  });\n\n  it(\"unregisterNode removes from the store\", () => {\n    act(() => {\n      appStore.getState().nodes.registerNode(\"test-2\", {});\n    });\n    expect(appStore.getState().nodes.nodes[\"test-2\"]).toBeDefined();\n\n    act(() => {\n      appStore.getState().nodes.unregisterNode(\"test-2\");\n    });\n    expect(appStore.getState().nodes.nodes[\"test-2\"]).toBeUndefined();\n  });\n});\n"
  },
  {
    "path": "packages/core/store/slices/__tests__/permissions.spec.tsx",
    "content": "import { renderHook, act, waitFor } from \"@testing-library/react\";\nimport { useRegisterPermissionsSlice } from \"../permissions\";\nimport { defaultAppState, createAppStore } from \"../../\";\nimport { rootDroppableId } from \"../../../lib/root-droppable-id\";\nimport { walkAppState } from \"../../../lib/data/walk-app-state\";\nimport { makeStatePublic } from \"../../../lib/data/make-state-public\";\nimport { Config } from \"../../../types\";\n\nconst appStore = createAppStore();\n\nfunction resetStores() {\n  appStore.setState(\n    {\n      ...appStore.getInitialState(),\n    },\n    true\n  );\n}\n\ndescribe(\"permissions slice\", () => {\n  beforeEach(() => {\n    resetStores();\n  });\n\n  it(\"resolves on load\", async () => {\n    const mockResolvePermissions = jest.fn().mockReturnValue({});\n\n    appStore.setState({\n      config: {\n        root: {\n          render: () => <div />,\n          resolvePermissions: mockResolvePermissions,\n        },\n        components: {},\n      },\n      state: {\n        ...defaultAppState,\n        data: {\n          content: [],\n          root: { props: { title: \"Hello, world\" } },\n          zones: {},\n        },\n      },\n    });\n\n    renderHook(() => useRegisterPermissionsSlice(appStore, { foo: true }));\n\n    expect(mockResolvePermissions).toHaveBeenCalledTimes(1);\n  });\n\n  it(\"auto-resolves when appStore data changes\", async () => {\n    const mockResolvePermissions = jest.fn().mockReturnValue({});\n\n    appStore.setState({\n      config: {\n        root: {\n          render: () => <div />,\n          resolvePermissions: mockResolvePermissions,\n        },\n        components: {},\n      },\n      state: {\n        ...defaultAppState,\n        data: {\n          content: [],\n          root: { props: { title: \"Hello, world\" } },\n          zones: {},\n        },\n      },\n    });\n\n    renderHook(() => useRegisterPermissionsSlice(appStore, { foo: true }));\n\n    expect(mockResolvePermissions).toHaveBeenCalledTimes(1);\n\n    appStore.setState({\n      state: {\n        ...defaultAppState,\n        data: {\n          ...appStore.getState().state.data,\n          root: {\n            props: {\n              title: \"Goodbye, world\",\n            },\n          },\n        },\n      },\n    });\n\n    expect(mockResolvePermissions).toHaveBeenCalledTimes(2);\n  });\n\n  it(\"sets loading state if a resolver is defined, and doesn't if none is defined\", async () => {\n    let loadingCalled = false;\n    let unloadingCalled = false;\n\n    // Mock to see if they are called\n    appStore.setState({\n      setComponentLoading: () => {\n        loadingCalled = true;\n        return () => {\n          unloadingCalled = true;\n        };\n      },\n    });\n\n    renderHook(() =>\n      useRegisterPermissionsSlice(appStore, { globalTest: true })\n    );\n\n    await act(async () => {\n      appStore.getState().permissions.resolvePermissions();\n    });\n\n    expect(loadingCalled).toBe(false);\n    expect(unloadingCalled).toBe(false);\n\n    resetStores();\n    loadingCalled = false;\n    unloadingCalled = false;\n\n    appStore.setState({\n      config: {\n        components: {\n          MyComponent: {\n            render: () => <div />,\n            resolvePermissions: async () => ({}),\n          },\n        },\n      },\n      setComponentLoading: () => {\n        loadingCalled = true;\n        return () => {\n          unloadingCalled = true;\n        };\n      },\n\n      state: {\n        ...defaultAppState,\n        data: {\n          ...defaultAppState.data,\n          content: [{ type: \"MyComponent\", props: { id: \"comp-1\" } }],\n        },\n      },\n    });\n\n    renderHook(() =>\n      useRegisterPermissionsSlice(appStore, { globalTest: true })\n    );\n\n    await act(async () => {\n      appStore.getState().permissions.resolvePermissions();\n    });\n\n    // Now we expect calls\n    expect(loadingCalled).toBe(true);\n    expect(unloadingCalled).toBe(true);\n  });\n\n  describe(\"getPermissions()\", () => {\n    it(\"returns global permissions by default\", () => {\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { testGlobal: true })\n      );\n\n      const perms = appStore.getState().permissions.getPermissions();\n      expect(perms.testGlobal).toBe(true);\n      expect(perms.drag).toBe(true); // default\n    });\n\n    it(\"returns merged component permissions if present\", () => {\n      appStore.setState({\n        config: {\n          components: {\n            MyComponent: {\n              render: () => <div />,\n              permissions: { fromComponent: true },\n            },\n          },\n        },\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { fromGlobal: true })\n      );\n\n      const perms = appStore.getState().permissions.getPermissions({\n        item: { type: \"MyComponent\", props: { id: \"component-1\" } },\n      });\n      expect(perms.fromGlobal).toBe(true);\n      expect(perms.fromComponent).toBe(true);\n    });\n\n    it(\"returns merged root permissions if present\", () => {\n      appStore.setState({\n        config: {\n          root: {\n            permissions: { rootPerm: true },\n          },\n          components: {},\n        },\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { fromGlobal: true })\n      );\n\n      const perms = appStore\n        .getState()\n        .permissions.getPermissions({ root: true });\n      expect(perms.fromGlobal).toBe(true);\n      expect(perms.rootPerm).toBe(true);\n    });\n  });\n\n  describe(\"resolvePermissions()\", () => {\n    it(\"calls component.resolvePermissions if defined\", async () => {\n      const resolvePermissions = jest.fn().mockReturnValue({\n        resolved: true,\n      });\n\n      const config = {\n        components: {\n          MyComponent: {\n            render: () => <div />,\n            permissions: { base: true },\n            resolvePermissions,\n          },\n        },\n      };\n\n      appStore.setState({\n        ...appStore.getInitialState(),\n        config,\n        state: walkAppState(\n          {\n            ...defaultAppState,\n            data: {\n              ...defaultAppState.data,\n              content: [{ type: \"MyComponent\", props: { id: \"comp-1\" } }],\n            },\n          },\n          config\n        ),\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { globalTest: true })\n      );\n\n      await act(async () => {\n        await appStore.getState().permissions.resolvePermissions();\n        await appStore.getState().permissions.resolvePermissions(); // Double calls shouldn't run resolvers if data hasn't changed\n      });\n\n      expect(resolvePermissions).toHaveBeenCalledTimes(2);\n      expect(resolvePermissions).toHaveBeenCalledWith(\n        {\n          props: { id: \"comp-1\" },\n          type: \"MyComponent\",\n        },\n        {\n          appState: makeStatePublic(appStore.getState().state),\n          changed: { id: true },\n          lastData: null,\n          lastPermissions: null,\n          permissions: {\n            base: true,\n            delete: true,\n            drag: true,\n            duplicate: true,\n            edit: true,\n            globalTest: true,\n            insert: true,\n          },\n          parent: { type: \"root\", props: { id: \"root\" } },\n        }\n      );\n\n      // Confirm that getPermissions merges in resolved perms\n      const perms = appStore.getState().permissions.getPermissions({\n        item: { type: \"MyComponent\", props: { id: \"comp-1\" } },\n      });\n      expect(perms.resolved).toBe(true);\n      expect(perms.globalTest).toBe(true);\n    });\n\n    it(\"provides correct args to component.resolvePermissions on subsequent calls\", async () => {\n      const resolvePermissions = jest.fn().mockReturnValue({\n        resolved: true,\n      });\n\n      const config = {\n        components: {\n          MyComponent: {\n            render: () => <div />,\n            permissions: { base: true },\n            resolvePermissions,\n          },\n        },\n      };\n\n      appStore.setState({\n        config,\n        state: walkAppState(\n          {\n            ...defaultAppState,\n            data: {\n              ...defaultAppState.data,\n              content: [{ type: \"MyComponent\", props: { id: \"comp-1\" } }],\n            },\n          },\n          config\n        ),\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { globalTest: true })\n      );\n\n      await act(async () => {\n        await appStore.getState().permissions.resolvePermissions();\n\n        const { dispatch } = appStore.getState();\n\n        // Will auto trigger an update\n        dispatch({\n          type: \"replace\",\n          data: {\n            props: { id: \"comp-1\", title: \"changed\" },\n            type: \"MyComponent\",\n          },\n          destinationIndex: 0,\n          destinationZone: rootDroppableId,\n        });\n      });\n\n      expect(resolvePermissions).toHaveBeenCalledTimes(3);\n      expect(resolvePermissions).toHaveBeenCalledWith(\n        {\n          props: { id: \"comp-1\", title: \"changed\" },\n          type: \"MyComponent\",\n        },\n        {\n          appState: makeStatePublic(appStore.getState().state),\n          changed: { id: false, title: true },\n          lastData: {\n            props: { id: \"comp-1\" },\n            type: \"MyComponent\",\n          },\n          lastPermissions: { resolved: true },\n          permissions: {\n            base: true,\n            delete: true,\n            drag: true,\n            duplicate: true,\n            edit: true,\n            globalTest: true,\n            insert: true,\n          },\n          parent: { type: \"root\", props: { id: \"root\" } },\n        }\n      );\n\n      // Confirm that getPermissions merges in resolved perms\n      const perms = appStore.getState().permissions.getPermissions({\n        item: { type: \"MyComponent\", props: { id: \"comp-1\" } },\n      });\n      expect(perms.resolved).toBe(true);\n      expect(perms.globalTest).toBe(true);\n    });\n\n    it(\"updates if item changes or if force = true\", async () => {\n      let resolveCalls = 0;\n      appStore.setState({\n        config: {\n          components: {\n            MyComponent: {\n              render: () => <div />,\n              resolvePermissions: async (item) => {\n                resolveCalls += 1;\n                if (item.props.id === \"changed-1\") {\n                  return { changed: true };\n                }\n                return { notChanged: true };\n              },\n            },\n          },\n        },\n        state: {\n          ...defaultAppState,\n          data: {\n            ...defaultAppState.data,\n            content: [{ type: \"MyComponent\", props: { id: \"comp-1\" } }],\n          },\n        },\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { testGlobal: true })\n      );\n\n      // Initial\n      await act(async () => {\n        await appStore.getState().permissions.resolvePermissions();\n      });\n      expect(resolveCalls).toBe(2);\n\n      // If nothing changed, calling again won't re-resolve\n      await act(async () => {\n        await appStore.getState().permissions.resolvePermissions();\n      });\n      expect(resolveCalls).toBe(2);\n\n      // Force => resolves again\n      await act(async () => {\n        await appStore.getState().permissions.resolvePermissions({}, true);\n      });\n      expect(resolveCalls).toBe(3);\n\n      // Change item => triggers new resolution\n      appStore.setState({\n        state: {\n          ...defaultAppState,\n          data: {\n            content: [{ type: \"MyComponent\", props: { id: \"changed-1\" } }],\n            root: {},\n            zones: {},\n          },\n        },\n      });\n\n      // Watcher will trigger this\n      expect(resolveCalls).toBe(4);\n    });\n\n    it(\"should receive the parent component in params\", async () => {\n      // Given: --------------\n      const parentPermissions = { parent: true };\n      const outsideParentPermissions = { outsideParent: true };\n      const resolvePermissions = jest.fn((_data, params) => {\n        if (params.parent.type === \"Parent\") {\n          return parentPermissions;\n        }\n        return outsideParentPermissions;\n      });\n\n      const config: Config = {\n        components: {\n          Child: {\n            resolvePermissions,\n            render: () => <div />,\n          },\n          Parent: {\n            fields: {\n              items: { type: \"slot\" },\n            },\n            render: ({ items: Content }) => <Content />,\n          },\n        },\n      };\n\n      const childComponent = {\n        type: \"Child\",\n        props: { id: \"child-1\" },\n      };\n      const parentComponent = {\n        type: \"Parent\",\n        props: {\n          id: \"parent-1\",\n          items: [childComponent],\n        },\n      };\n      const initialData = {\n        ...defaultAppState.data,\n        content: [parentComponent],\n      };\n\n      appStore.setState({\n        config,\n        state: walkAppState(\n          {\n            ...defaultAppState,\n            data: initialData,\n          },\n          config\n        ),\n      });\n\n      // When: --------------\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { testGlobal: true })\n      );\n\n      // Wait for the initial resolvePermissions promises to resolve\n      await waitFor(\n        () => Object.keys(appStore.getState().permissions.cache).length > 0\n      );\n\n      // Then: --------------\n      expect(resolvePermissions).toHaveBeenCalledTimes(1);\n      expect(resolvePermissions.mock.calls[0][1].parent).toEqual(\n        parentComponent\n      );\n      expect(resolvePermissions.mock.results[0].value).toEqual(\n        parentPermissions\n      );\n    });\n\n    it(\"updates if parent changes\", async () => {\n      // Given: --------------\n      const parentPermissions = { parent: true };\n      const outsideParentPermissions = { outsideParent: true };\n      const resolvePermissions = jest.fn((_data, params) => {\n        if (params.parent.type === \"Parent\") {\n          return parentPermissions;\n        }\n        return outsideParentPermissions;\n      });\n\n      const config: Config = {\n        components: {\n          Child: {\n            resolvePermissions,\n            render: () => <div />,\n          },\n          Parent: {\n            fields: {\n              items: { type: \"slot\" },\n            },\n            render: ({ items: Content }) => <Content />,\n          },\n        },\n      };\n\n      const childComponent = {\n        type: \"Child\",\n        props: { id: \"child-1\" },\n      };\n      const parentComponent = {\n        type: \"Parent\",\n        props: {\n          id: \"parent-1\",\n          items: [childComponent],\n        },\n      };\n      const initialData = {\n        ...defaultAppState.data,\n        content: [parentComponent],\n      };\n\n      appStore.setState({\n        config,\n        state: walkAppState(\n          {\n            ...defaultAppState,\n            data: initialData,\n          },\n          config\n        ),\n      });\n\n      // When: --------------\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { testGlobal: true })\n      );\n\n      // Wait for the initial resolvePermissions promises to resolve\n      await waitFor(\n        () => Object.keys(appStore.getState().permissions.cache).length > 0\n      );\n\n      await act(async () => {\n        const { dispatch } = appStore.getState();\n\n        // Move the Child component to root\n        dispatch({\n          type: \"move\",\n          sourceZone: \"parent-1:items\",\n          sourceIndex: 0,\n          destinationZone: rootDroppableId,\n          destinationIndex: 0,\n        });\n      });\n\n      // Then: --------------\n      expect(resolvePermissions).toHaveBeenCalledTimes(2);\n      expect(resolvePermissions.mock.calls[0][1].parent).toEqual(\n        parentComponent\n      );\n      expect(resolvePermissions.mock.results[0].value).toEqual(\n        parentPermissions\n      );\n\n      expect(resolvePermissions.mock.calls[1][1].parent).toEqual({\n        type: \"root\",\n        props: { id: \"root\" },\n      });\n      expect(resolvePermissions.mock.results[1].value).toEqual(\n        outsideParentPermissions\n      );\n    });\n\n    it(\"doesn't update if moving on the same parent\", async () => {\n      // Given: --------------\n      const parentPermissions = { parent: true };\n      const outsideParentPermissions = { outsideParent: true };\n      const resolvePermissions = jest.fn((_data, params) => {\n        if (params.parent.type === \"Parent\") {\n          return parentPermissions;\n        }\n        return outsideParentPermissions;\n      });\n\n      const config: Config = {\n        components: {\n          Child: {\n            resolvePermissions,\n            render: () => <div />,\n          },\n          Parent: {\n            fields: {\n              items: { type: \"slot\" },\n            },\n            render: ({ items: Content }) => <Content />,\n          },\n        },\n      };\n\n      const childComponent1 = {\n        type: \"Child\",\n        props: { id: \"child-1\" },\n      };\n      const childComponent2 = {\n        type: \"Child\",\n        props: { id: \"child-2\" },\n      };\n      const parentComponent = {\n        type: \"Parent\",\n        props: {\n          id: \"parent-1\",\n          items: [childComponent1, childComponent2],\n        },\n      };\n      const initialData = {\n        ...defaultAppState.data,\n        content: [parentComponent],\n      };\n\n      appStore.setState({\n        config,\n        state: walkAppState(\n          {\n            ...defaultAppState,\n            data: initialData,\n          },\n          config\n        ),\n      });\n\n      // When: --------------\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { testGlobal: true })\n      );\n\n      // Wait for the initial resolvePermissions promises to resolve\n      await waitFor(\n        () => Object.keys(appStore.getState().permissions.cache).length > 0\n      );\n\n      await act(async () => {\n        const { dispatch } = appStore.getState();\n\n        // Move the first child component to the second position (same parent)\n        dispatch({\n          type: \"move\",\n          sourceZone: \"parent-1:items\",\n          sourceIndex: 0,\n          destinationZone: \"parent-1:items\",\n          destinationIndex: 1,\n        });\n      });\n\n      // Then: --------------\n      expect(resolvePermissions).toHaveBeenCalledTimes(2);\n      // child-1\n      expect(resolvePermissions.mock.calls[0][1].parent).toEqual(\n        parentComponent\n      );\n      expect(resolvePermissions.mock.results[0].value).toEqual(\n        parentPermissions\n      );\n      // child-2\n      expect(resolvePermissions.mock.calls[1][1].parent).toEqual(\n        parentComponent\n      );\n      expect(resolvePermissions.mock.results[1].value).toEqual(\n        parentPermissions\n      );\n    });\n  });\n\n  describe(\"refreshPermissions()\", () => {\n    it(\"force updates if item changes\", async () => {\n      let resolveCalls = 0;\n      appStore.setState({\n        config: {\n          components: {\n            MyComponent: {\n              render: () => <div />,\n              resolvePermissions: async (item) => {\n                resolveCalls += 1;\n                if (item.props.id === \"changed-1\") {\n                  return { changed: true };\n                }\n                return { notChanged: true };\n              },\n            },\n          },\n        },\n        state: {\n          ...defaultAppState,\n          data: {\n            ...defaultAppState.data,\n            content: [{ type: \"MyComponent\", props: { id: \"comp-1\" } }],\n          },\n        },\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { testGlobal: true })\n      );\n\n      // Initial\n      await act(async () => {\n        await appStore.getState().permissions.resolvePermissions();\n      });\n      expect(resolveCalls).toBe(2);\n\n      await act(async () => {\n        await appStore.getState().permissions.refreshPermissions();\n      });\n      expect(resolveCalls).toBe(3);\n    });\n  });\n\n  describe(\"integration with useAppStore subscriptions\", () => {\n    it(\"auto-resolves when appStore config changes\", async () => {\n      let resolveCalled = false;\n      appStore.setState({\n        config: {\n          components: {\n            MyComponent: {\n              render: () => <div />,\n              resolvePermissions: async () => {\n                resolveCalled = true;\n                return { newPerm: true };\n              },\n            },\n          },\n        },\n        state: {\n          ...defaultAppState,\n          data: {\n            content: [{ type: \"MyComponent\", props: { id: \"test-1\" } }],\n            root: {},\n            zones: {},\n          },\n        },\n      });\n\n      renderHook(() =>\n        useRegisterPermissionsSlice(appStore, { fromGlobal: true })\n      );\n\n      // config is already set before hooking, so do a manual \"change\" to trigger subscription\n      await act(async () => {\n        appStore.setState({\n          config: {\n            components: {\n              MyComponent: {\n                render: () => <div />,\n                resolvePermissions: async () => {\n                  resolveCalled = true;\n                  return { changedPerm: true };\n                },\n              },\n            },\n          },\n        });\n      });\n\n      await waitFor(() => expect(resolveCalled).toBe(true));\n    });\n  });\n});\n"
  },
  {
    "path": "packages/core/store/slices/fields.ts",
    "content": "import { ComponentData } from \"../../types\";\nimport type { Fields } from \"../../types\";\nimport { AppStore, useAppStoreApi } from \"../\";\nimport { useCallback, useEffect } from \"react\";\nimport { getChanged } from \"../../lib/get-changed\";\nimport { makeStatePublic } from \"../../lib/data/make-state-public\";\n\ntype ComponentOrRootData = Omit<ComponentData<any>, \"type\">;\n\nexport type FieldsSlice = {\n  fields: Fields | Partial<Fields>;\n  loading: boolean;\n  lastResolvedData: Partial<ComponentOrRootData>;\n  id: string | undefined;\n};\n\nexport const createFieldsSlice = (\n  _set: (newState: Partial<AppStore>) => void,\n  _get: () => AppStore\n): FieldsSlice => {\n  return {\n    fields: {},\n    loading: false,\n    lastResolvedData: {},\n    id: undefined,\n  };\n};\n\nexport const useRegisterFieldsSlice = (\n  appStore: ReturnType<typeof useAppStoreApi>,\n  id?: string\n) => {\n  const resolveFields = useCallback(\n    async (reset?: boolean) => {\n      const { fields, lastResolvedData } = appStore.getState().fields;\n      const metadata = appStore.getState().metadata;\n      const nodes = appStore.getState().state.indexes.nodes;\n      const node = nodes[id || \"root\"];\n      const componentData = node?.data;\n      const parentNode = node?.parentId ? nodes[node.parentId] : null;\n      const parent = parentNode?.data || null;\n\n      const { getComponentConfig, state } = appStore.getState();\n\n      const componentConfig = getComponentConfig(componentData?.type);\n\n      if (!componentData || !componentConfig) return;\n\n      const defaultFields = componentConfig.fields || {};\n      const resolver = componentConfig.resolveFields;\n      let lastFields: Fields | null = fields as Fields;\n\n      if (reset) {\n        appStore.setState((s) => ({\n          fields: { ...s.fields, fields: defaultFields, id },\n        }));\n\n        lastFields = defaultFields;\n      }\n\n      if (resolver) {\n        const timeout = setTimeout(() => {\n          appStore.setState((s) => ({\n            fields: { ...s.fields, loading: true },\n          }));\n        }, 50);\n\n        const lastData =\n          lastResolvedData.props?.id === id ? lastResolvedData : null;\n\n        const changed = getChanged(componentData, lastData);\n\n        const newFields = await resolver(componentData, {\n          changed,\n          fields: defaultFields,\n          lastFields,\n          metadata: { ...metadata, ...componentConfig.metadata },\n          lastData: lastData as ComponentOrRootData,\n          appState: makeStatePublic(state),\n          parent,\n        });\n\n        clearTimeout(timeout);\n\n        // Abort if item has changed during resolution (happens with history)\n        if (appStore.getState().selectedItem?.props.id !== id) {\n          return;\n        }\n\n        appStore.setState({\n          fields: {\n            fields: newFields,\n            loading: false,\n            lastResolvedData: componentData,\n            id,\n          },\n        });\n      } else {\n        appStore.setState((s) => ({\n          fields: { ...s.fields, fields: defaultFields, id },\n        }));\n      }\n    },\n    [id]\n  );\n\n  useEffect(() => {\n    resolveFields(true);\n\n    return appStore.subscribe(\n      (s) => s.state.indexes.nodes[id || \"root\"],\n      () => resolveFields()\n    );\n  }, [id]);\n};\n"
  },
  {
    "path": "packages/core/store/slices/history.ts",
    "content": "import { AppState, History } from \"../../types\";\nimport { generateId } from \"../../lib/generate-id\";\nimport { AppStore, useAppStoreApi } from \"../\";\nimport { useEffect } from \"react\";\nimport { useHotkey } from \"../../lib/use-hotkey\";\n\nexport type HistorySlice<D = any> = {\n  index: number;\n  hasPast: () => boolean;\n  hasFuture: () => boolean;\n  histories: History<D>[];\n  record: (data: D) => void;\n  back: VoidFunction;\n  forward: VoidFunction;\n  currentHistory: () => History;\n  nextHistory: () => History<D> | null;\n  prevHistory: () => History<D> | null;\n  setHistories: (histories: History[]) => void;\n  setHistoryIndex: (index: number) => void;\n  initialAppState: D;\n};\n\nconst EMPTY_HISTORY_INDEX = 0;\n\nfunction debounce(func: Function, timeout = 300) {\n  let timer: NodeJS.Timeout;\n\n  return (...args: any) => {\n    clearTimeout(timer);\n    timer = setTimeout(() => {\n      func(...args);\n    }, timeout);\n  };\n}\n\nexport type PuckHistory = {\n  back: VoidFunction;\n  forward: VoidFunction;\n  setHistories: (histories: History[]) => void;\n  setHistoryIndex: (index: number) => void;\n  HistorySlice: HistorySlice;\n};\n\n// Tidy the state before going back or forward\nconst tidyState = (state: AppState): AppState => {\n  return {\n    ...state,\n    ui: {\n      ...state.ui,\n      field: {\n        ...state.ui.field,\n        focus: null,\n      },\n    },\n  };\n};\n\nexport const createHistorySlice = (\n  set: (newState: Partial<AppStore>) => void,\n  get: () => AppStore\n): HistorySlice => {\n  const record = debounce((state: AppState) => {\n    const { histories, index } = get().history;\n\n    const history: History = {\n      state,\n      id: generateId(\"history\"),\n    };\n\n    // Don't use setHistories due to callback\n    const newHistories = [...histories.slice(0, index + 1), history];\n\n    set({\n      history: {\n        ...get().history,\n        histories: newHistories,\n        index: newHistories.length - 1,\n      },\n    });\n  }, 250);\n\n  return {\n    initialAppState: {} as AppState,\n    index: EMPTY_HISTORY_INDEX,\n    histories: [],\n    hasPast: () => get().history.index > EMPTY_HISTORY_INDEX,\n    hasFuture: () => get().history.index < get().history.histories.length - 1,\n    prevHistory: () => {\n      const { history } = get();\n\n      return history.hasPast() ? history.histories[history.index - 1] : null;\n    },\n    nextHistory: () => {\n      const s = get().history;\n\n      return s.hasFuture() ? s.histories[s.index + 1] : null;\n    },\n    currentHistory: () => get().history.histories[get().history.index],\n    back: () => {\n      const { history, dispatch } = get();\n\n      if (history.hasPast()) {\n        const state = tidyState(\n          history.prevHistory()?.state || history.initialAppState\n        );\n\n        dispatch({\n          type: \"set\",\n          state,\n        });\n\n        set({ history: { ...history, index: history.index - 1 } });\n      }\n    },\n    forward: () => {\n      const { history, dispatch } = get();\n\n      if (history.hasFuture()) {\n        const state = history.nextHistory()?.state;\n\n        dispatch({ type: \"set\", state: state ? tidyState(state) : {} });\n\n        set({ history: { ...history, index: history.index + 1 } });\n      }\n    },\n    setHistories: (histories: History[]) => {\n      const { dispatch, history } = get();\n\n      dispatch({\n        type: \"set\",\n        state:\n          histories[histories.length - 1]?.state || history.initialAppState,\n      });\n\n      set({ history: { ...history, histories, index: histories.length - 1 } });\n    },\n    setHistoryIndex: (index: number) => {\n      const { dispatch, history } = get();\n\n      dispatch({\n        type: \"set\",\n        state: history.histories[index]?.state || history.initialAppState,\n      });\n\n      set({ history: { ...history, index } });\n    },\n    record,\n  };\n};\n\nexport function useRegisterHistorySlice(\n  appStore: ReturnType<typeof useAppStoreApi>,\n  {\n    histories,\n    index,\n    initialAppState,\n  }: {\n    histories: History<any>[];\n    index: number;\n    initialAppState: AppState;\n  }\n) {\n  useEffect(\n    () =>\n      appStore.setState({\n        history: {\n          ...appStore.getState().history,\n          histories,\n          index,\n          initialAppState,\n        },\n      }),\n    [histories, index, initialAppState]\n  );\n\n  const back = () => {\n    appStore.getState().history.back();\n  };\n\n  const forward = () => {\n    appStore.getState().history.forward();\n  };\n\n  useHotkey({ altRight: false, meta: true, z: true }, back);\n  useHotkey({ altRight: false, meta: true, shift: true, z: true }, forward);\n  useHotkey({ altRight: false, meta: true, y: true }, forward);\n\n  useHotkey({ altRight: false, ctrl: true, z: true }, back);\n  useHotkey({ altRight: false, ctrl: true, shift: true, z: true }, forward);\n  useHotkey({ altRight: false, ctrl: true, y: true }, forward);\n}\n"
  },
  {
    "path": "packages/core/store/slices/nodes.ts",
    "content": "import { AppStore } from \"../\";\n\ntype NodeMethods = {\n  sync: () => void;\n  hideOverlay: () => void;\n  showOverlay: () => void;\n};\n\ntype PuckNodeInstance = {\n  id: string;\n  methods: NodeMethods;\n  element: HTMLElement | null;\n};\n\nexport type NodesSlice = {\n  nodes: Record<string, PuckNodeInstance | undefined>;\n  registerNode: (id: string, node: Partial<PuckNodeInstance>) => void;\n  unregisterNode: (id: string, node?: Partial<PuckNodeInstance>) => void;\n};\n\nexport const createNodesSlice = (\n  set: (newState: Partial<AppStore>) => void,\n  get: () => AppStore\n): NodesSlice => ({\n  nodes: {},\n  registerNode: (id: string, node: Partial<PuckNodeInstance>) => {\n    const s = get().nodes;\n\n    const emptyNode: PuckNodeInstance = {\n      id,\n      methods: {\n        sync: () => null,\n        hideOverlay: () => null,\n        showOverlay: () => null,\n      },\n      element: null,\n    };\n\n    const existingNode: PuckNodeInstance | undefined = s.nodes[id];\n\n    set({\n      nodes: {\n        ...s,\n        nodes: {\n          ...s.nodes,\n          [id]: {\n            ...emptyNode,\n            ...existingNode,\n            ...node,\n            id,\n          },\n        },\n      },\n    });\n  },\n  unregisterNode: (id) => {\n    const s = get().nodes;\n    const existingNode: PuckNodeInstance | undefined = s.nodes[id];\n\n    if (existingNode) {\n      const newNodes = { ...s.nodes };\n\n      delete newNodes[id];\n\n      set({\n        nodes: {\n          ...s,\n          nodes: newNodes,\n        },\n      });\n    }\n  },\n});\n"
  },
  {
    "path": "packages/core/store/slices/permissions.ts",
    "content": "import { useEffect } from \"react\";\nimport { flattenData } from \"../../lib/data/flatten-data\";\nimport { ComponentData, Config, Permissions, UserGenerics } from \"../../types\";\nimport { getChanged } from \"../../lib/get-changed\";\nimport { AppStore, useAppStoreApi } from \"../\";\nimport { makeStatePublic } from \"../../lib/data/make-state-public\";\n\ntype PermissionsArgs<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>\n> = {\n  item?: G[\"UserComponentData\"] | null;\n  type?: keyof G[\"UserProps\"];\n  root?: boolean;\n};\n\nexport type GetPermissions<UserConfig extends Config = Config> = (\n  params?: PermissionsArgs<UserConfig>\n) => Permissions;\n\ntype ResolvePermissions<UserConfig extends Config = Config> = (\n  params?: PermissionsArgs<UserConfig>,\n  force?: boolean\n) => void;\n\nexport type RefreshPermissions<UserConfig extends Config = Config> = (\n  params?: PermissionsArgs<UserConfig>,\n  force?: boolean\n) => void;\n\ntype Cache = Record<\n  string,\n  {\n    lastPermissions: Partial<Permissions>;\n    lastData: ComponentData | null;\n    lastParentId: string | null;\n  }\n>;\n\nexport type PermissionsSlice = {\n  cache: Cache;\n  globalPermissions: Permissions;\n  resolvedPermissions: Record<string, Partial<Permissions> | undefined>;\n  getPermissions: GetPermissions<Config>;\n  resolvePermissions: ResolvePermissions<Config>;\n  refreshPermissions: RefreshPermissions<Config>;\n};\n\nexport const createPermissionsSlice = (\n  set: (newState: Partial<AppStore>) => void,\n  get: () => AppStore\n): PermissionsSlice => {\n  const resolvePermissions: ResolvePermissions = async (params = {}, force) => {\n    const { state, permissions, config } = get();\n    const { cache, globalPermissions } = permissions;\n\n    const resolvePermissionsForItem = async (\n      item: ComponentData,\n      force: boolean = false\n    ) => {\n      const { config, state: appState, setComponentLoading } = get();\n      const itemCache: Cache[string] | undefined = cache[item.props.id];\n      const nodes = appState.indexes.nodes;\n      const parentId = nodes[item.props.id]?.parentId;\n      const parentNode = parentId ? nodes[parentId] : null;\n      const parentData = parentNode?.data ?? null;\n\n      const componentConfig =\n        item.type === \"root\" ? config.root : config.components[item.type];\n\n      if (!componentConfig) {\n        return;\n      }\n\n      const initialPermissions = {\n        ...globalPermissions,\n        ...componentConfig.permissions,\n      };\n\n      if (componentConfig.resolvePermissions) {\n        const changed = getChanged(item, itemCache?.lastData);\n        const propsChanged = Object.values(changed).some((el) => el === true);\n        const parentChanged = itemCache?.lastParentId !== parentId;\n\n        if (propsChanged || parentChanged || force) {\n          const clearTimeout = setComponentLoading(item.props.id, true, 50);\n\n          const resolvedPermissions = await componentConfig.resolvePermissions(\n            item,\n            {\n              changed,\n              lastPermissions: itemCache?.lastPermissions || null,\n              permissions: initialPermissions,\n              appState: makeStatePublic(appState),\n              lastData: itemCache?.lastData || null,\n              parent: parentData,\n            }\n          );\n\n          const latest = get().permissions;\n\n          set({\n            permissions: {\n              ...latest,\n              cache: {\n                ...latest.cache,\n                [item.props.id]: {\n                  lastParentId: parentId,\n                  lastData: item,\n                  lastPermissions: resolvedPermissions,\n                },\n              },\n              resolvedPermissions: {\n                ...latest.resolvedPermissions,\n                [item.props.id]: resolvedPermissions,\n              },\n            },\n          });\n\n          clearTimeout();\n        }\n      }\n    };\n\n    const resolvePermissionsForRoot = (force = false) => {\n      const { state: appState } = get();\n\n      resolvePermissionsForItem(\n        // Shim the root data in by conforming to component data shape\n        {\n          type: \"root\",\n          props: { ...appState.data.root.props, id: \"root\" },\n        },\n        force\n      );\n    };\n\n    const { item, type, root } = params;\n\n    if (item) {\n      // Resolve specific item\n      await resolvePermissionsForItem(item, force);\n    } else if (type) {\n      // Resolve specific type\n      flattenData(state, config)\n        .filter((item) => item.type === type)\n        .map(async (item) => {\n          await resolvePermissionsForItem(item, force);\n        });\n    } else if (root) {\n      resolvePermissionsForRoot(force);\n    } else {\n      // Resolve everything\n      flattenData(state, config).map(async (item) => {\n        await resolvePermissionsForItem(item, force);\n      });\n    }\n  };\n\n  const refreshPermissions: RefreshPermissions = (params) =>\n    resolvePermissions(params, true);\n\n  return {\n    cache: {},\n    globalPermissions: {\n      drag: true,\n      edit: true,\n      delete: true,\n      duplicate: true,\n      insert: true,\n    },\n    resolvedPermissions: {},\n    getPermissions: ({ item, type, root } = {}) => {\n      const { config, permissions } = get();\n      const { globalPermissions, resolvedPermissions } = permissions;\n\n      if (item) {\n        const componentConfig = config.components[item.type];\n\n        const initialPermissions = {\n          ...globalPermissions,\n          ...componentConfig?.permissions,\n        };\n\n        const resolvedForItem = resolvedPermissions[item.props.id];\n\n        return (\n          resolvedForItem\n            ? { ...globalPermissions, ...resolvedForItem }\n            : initialPermissions\n        ) as Permissions;\n      } else if (type) {\n        const componentConfig = config.components[type];\n\n        return {\n          ...globalPermissions,\n          ...componentConfig?.permissions,\n        } as Permissions;\n      } else if (root) {\n        const rootConfig = config.root;\n\n        const initialPermissions = {\n          ...globalPermissions,\n          ...rootConfig?.permissions,\n        } as Permissions;\n\n        const resolvedForItem = resolvedPermissions[\"root\"];\n\n        return (\n          resolvedForItem\n            ? { ...globalPermissions, ...resolvedForItem }\n            : initialPermissions\n        ) as Permissions;\n      }\n\n      return globalPermissions;\n    },\n    resolvePermissions,\n    refreshPermissions,\n  };\n};\n\nexport const useRegisterPermissionsSlice = (\n  appStore: ReturnType<typeof useAppStoreApi>,\n  globalPermissions: Partial<Permissions>\n) => {\n  useEffect(() => {\n    const { permissions } = appStore.getState();\n    const { globalPermissions: existingGlobalPermissions } = permissions;\n    appStore.setState({\n      permissions: {\n        ...permissions,\n        globalPermissions: {\n          ...existingGlobalPermissions,\n          ...globalPermissions,\n        } as Permissions,\n      },\n    });\n\n    permissions.resolvePermissions();\n  }, [globalPermissions]);\n\n  useEffect(() => {\n    return appStore.subscribe(\n      (s) => s.state.data,\n      () => {\n        appStore.getState().permissions.resolvePermissions();\n      }\n    );\n  }, []);\n\n  useEffect(() => {\n    return appStore.subscribe(\n      (s) => s.config,\n      () => {\n        appStore.getState().permissions.resolvePermissions();\n      }\n    );\n  }, []);\n};\n"
  },
  {
    "path": "packages/core/styles/color.css",
    "content": ":root {\n  /*\n   * Color palette\n   */\n\n  --puck-color-rose-01: #4a001c;\n  --puck-color-rose-02: #670833;\n  --puck-color-rose-03: #87114c;\n  --puck-color-rose-04: #a81a66;\n  --puck-color-rose-05: #bc5089;\n  --puck-color-rose-06: #cc7ca5;\n  --puck-color-rose-07: #d89aba;\n  --puck-color-rose-08: #e3b8cf;\n  --puck-color-rose-09: #efd6e3;\n  --puck-color-rose-10: #f6eaf1;\n  --puck-color-rose-11: #faf4f8;\n  --puck-color-rose-12: #fef8fc;\n\n  --puck-color-azure-01: #00175d;\n  --puck-color-azure-02: #002c77;\n  --puck-color-azure-03: #014292;\n  --puck-color-azure-04: #0158ad;\n  --puck-color-azure-05: #3479be;\n  --puck-color-azure-06: #6499cf;\n  --puck-color-azure-07: #88b0da;\n  --puck-color-azure-08: #abc7e5;\n  --puck-color-azure-09: #cfdff0;\n  --puck-color-azure-10: #e7eef7;\n  --puck-color-azure-11: #f3f6fb;\n  --puck-color-azure-12: #f7faff;\n\n  --puck-color-green-01: #002000;\n  --puck-color-green-02: #043604;\n  --puck-color-green-03: #084e08;\n  --puck-color-green-04: #0c680c;\n  --puck-color-green-05: #1d882f;\n  --puck-color-green-06: #2faa53;\n  --puck-color-green-07: #56c16f;\n  --puck-color-green-08: #7dd78b;\n  --puck-color-green-09: #b8e8bf;\n  --puck-color-green-10: #ddf3e0;\n  --puck-color-green-11: #eff8f0;\n  --puck-color-green-12: #f3fcf4;\n\n  --puck-color-yellow-01: #211000;\n  --puck-color-yellow-02: #362700;\n  --puck-color-yellow-03: #4c4000;\n  --puck-color-yellow-04: #645a00;\n  --puck-color-yellow-05: #877614;\n  --puck-color-yellow-06: #ab9429;\n  --puck-color-yellow-07: #bfac4e;\n  --puck-color-yellow-08: #d4c474;\n  --puck-color-yellow-09: #e6deb1;\n  --puck-color-yellow-10: #f3efd9;\n  --puck-color-yellow-11: #f9f7ed;\n  --puck-color-yellow-12: #fcfaf0;\n\n  --puck-color-red-01: #4c0000;\n  --puck-color-red-02: #6a0a10;\n  --puck-color-red-03: #8a1422;\n  --puck-color-red-04: #ac1f35;\n  --puck-color-red-05: #bf5366;\n  --puck-color-red-06: #ce7e8e;\n  --puck-color-red-07: #d99ca8;\n  --puck-color-red-08: #e4b9c2;\n  --puck-color-red-09: #efd7db;\n  --puck-color-red-10: #f6eaec;\n  --puck-color-red-11: #faf4f5;\n  --puck-color-red-12: #fff9fa;\n\n  --puck-color-grey-01: #181818;\n  --puck-color-grey-02: #292929;\n  --puck-color-grey-03: #404040;\n  --puck-color-grey-04: #5a5a5a;\n  --puck-color-grey-05: #767676;\n  --puck-color-grey-06: #949494;\n  --puck-color-grey-07: #ababab;\n  --puck-color-grey-08: #c3c3c3;\n  --puck-color-grey-09: #dcdcdc;\n  --puck-color-grey-10: #efefef;\n  --puck-color-grey-11: #f5f5f5;\n  --puck-color-grey-12: #fafafa;\n\n  --puck-color-black: #000000;\n  --puck-color-white: #ffffff;\n}\n"
  },
  {
    "path": "packages/core/styles/typography.css",
    "content": ":root {\n  /**\n     * Modular Scale\n     *\n     * 2:1 - octave - 2.0\n     *\n     * Base: 12px\n     *\n     * Interval: 5\n     *\n     * http://spencermortensen.com/articles/typographic-scale/\n     *\n     * Spacing unit: 16px * 1.5 = 24px\n     *\n     * Root/html font-size is undefined (browser default)\n     *\n     * 1rem = 16px\n     */\n\n  /* Unitless font sizes\n       ======================================================================== */\n\n  --puck-font-size-scale-base-unitless: 12;\n  --puck-font-size-xxxs-unitless: 12;\n  --puck-font-size-xxs-unitless: 14;\n  --puck-font-size-xs-unitless: 16;\n  --puck-font-size-s-unitless: 18;\n  --puck-font-size-m-unitless: 21;\n  --puck-font-size-l-unitless: 24;\n  --puck-font-size-xl-unitless: 28;\n  --puck-font-size-xxl-unitless: 36;\n  --puck-font-size-xxxl-unitless: 48;\n  --puck-font-size-xxxxl-unitless: 56;\n\n  /* Descriptive font sizes\n       ======================================================================== */\n\n  --puck-font-size-xxxs: calc(1rem * var(--puck-font-size-xxxs-unitless) / 16);\n  --puck-font-size-xxs: calc(1rem * var(--puck-font-size-xxs-unitless) / 16);\n  --puck-font-size-xs: calc(1rem * var(--puck-font-size-xs-unitless) / 16);\n  --puck-font-size-s: calc(1rem * var(--puck-font-size-s-unitless) / 16);\n  --puck-font-size-m: calc(1rem * var(--puck-font-size-m-unitless) / 16);\n  --puck-font-size-l: calc(1rem * var(--puck-font-size-l-unitless) / 16);\n  --puck-font-size-xl: calc(1rem * var(--puck-font-size-xl-unitless) / 16);\n  --puck-font-size-xxl: calc(1rem * var(--puck-font-size-xxl-unitless) / 16);\n  --puck-font-size-xxxl: calc(1rem * var(--puck-font-size-xxxl-unitless) / 16);\n  --puck-font-size-xxxxl: calc(\n    1rem * var(--puck-font-size-xxxxl-unitless) / 16\n  );\n\n  /* Functional font sizes\n      ========================================================================= */\n\n  --puck-font-size-base: var(--puck-font-size-xs);\n\n  /* Descriptive line heights\n      ========================================================================= */\n\n  --line-height-reset: 1;\n  --line-height-xs: calc(\n    var(--space-m-unitless) / var(--puck-font-size-m-unitless)\n  );\n  --line-height-s: calc(\n    var(--space-m-unitless) / var(--puck-font-size-s-unitless)\n  );\n  --line-height-m: calc(\n    var(--space-m-unitless) / var(--puck-font-size-xs-unitless)\n  );\n  --line-height-l: calc(\n    var(--space-m-unitless) / var(--puck-font-size-xxs-unitless)\n  );\n  --line-height-xl: calc(\n    var(--space-m-unitless) / var(--puck-font-size-scale-base-unitless)\n  );\n\n  /* Functional line heights\n      ========================================================================= */\n\n  --line-height-base: var(--line-height-m);\n\n  /* Font families\n  ======================================================================== */\n\n  --fallback-font-stack: -apple-system, BlinkMacSystemFont, Segoe UI,\n    Helvetica Neue, sans-serif, Apple Color Emoji, Segoe UI Emoji,\n    Segoe UI Symbol;\n\n  --puck-font-family: Inter, var(--fallback-font-stack);\n  --puck-font-family-monospaced: ui-monospace, \"Cascadia Code\",\n    \"Source Code Pro\", Menlo, Consolas, \"DejaVu Sans Mono\", monospace;\n}\n\n@supports (font-variation-settings: normal) {\n  :root {\n    --puck-font-family: InterVariable, var(--fallback-font-stack);\n  }\n}\n"
  },
  {
    "path": "packages/core/styles.css",
    "content": "@import \"./bundle/index.css\";\n"
  },
  {
    "path": "packages/core/tsconfig.json",
    "content": "{\n  \"extends\": \"../tsconfig/react-library.json\",\n  \"include\": [\".\"],\n  \"exclude\": [\"dist\", \"build\", \"node_modules\"],\n  \"compilerOptions\": {\n    \"types\": [\"node\", \"jest\", \"@testing-library/jest-dom\"]\n  }\n}\n"
  },
  {
    "path": "packages/core/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\";\nimport tsupconfig from \"../tsup-config\";\n\nexport default defineConfig({ ...tsupconfig });\n"
  },
  {
    "path": "packages/core/types/API/DropZone.ts",
    "content": "export type Direction = \"left\" | \"right\" | \"up\" | \"down\" | null;\n\nexport type DragAxis = \"dynamic\" | \"y\" | \"x\";\n"
  },
  {
    "path": "packages/core/types/API/FieldTransforms.ts",
    "content": "import { MapFnParams } from \"../../lib/data/map-fields\";\nimport { Config, ExtractField, Field, UserGenerics } from \"../../types\";\n\nexport type FieldTransformFnParams<T> = Omit<MapFnParams<T>, \"parentId\"> & {\n  isReadOnly: boolean;\n  componentId: string;\n};\n\nexport type FieldTransformFn<T> = (params: FieldTransformFnParams<T>) => any;\n\nexport type FieldTransforms<\n  UserConfig extends Config = Config<{ fields: {} }>, // Setting fields: {} helps TS choose default field types\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>,\n  UserField extends { type: string } = Field | G[\"UserField\"]\n> = Partial<{\n  [Type in UserField[\"type\"]]: FieldTransformFn<ExtractField<UserField, Type>>;\n}>;\n"
  },
  {
    "path": "packages/core/types/API/Overrides.ts",
    "content": "import { ReactNode } from \"react\";\nimport { Field, FieldProps } from \"../Fields\";\nimport { ItemSelector } from \"../../lib/data/get-item\";\nimport { ExtractField, UserGenerics } from \"../Utils\";\nimport { Config } from \"../Config\";\nimport { RenderFunc } from \"../Internal\";\n\n// All direct render methods, excluding fields\nexport const overrideKeys = [\n  \"header\",\n  \"headerActions\",\n  \"fields\",\n  \"fieldLabel\",\n  \"drawer\",\n  \"drawerItem\",\n  \"componentOverlay\",\n  \"outline\",\n  \"puck\",\n  \"preview\",\n] as const;\n\nexport type OverrideKey = (typeof overrideKeys)[number];\n\ntype OverridesGeneric<Shape extends { [key in OverrideKey]: any }> = Shape;\n\nexport type Overrides<UserConfig extends Config = Config> = OverridesGeneric<{\n  fieldTypes: Partial<FieldRenderFunctions<UserConfig>>;\n  header: RenderFunc<{ actions: ReactNode; children: ReactNode }>;\n  actionBar: RenderFunc<{\n    label?: string;\n    children: ReactNode;\n    parentAction: ReactNode;\n  }>;\n  headerActions: RenderFunc<{ children: ReactNode }>;\n  preview: RenderFunc;\n  fields: RenderFunc<{\n    children: ReactNode;\n    isLoading: boolean;\n    itemSelector?: ItemSelector | null;\n  }>;\n  fieldLabel: RenderFunc<{\n    children?: ReactNode;\n    icon?: ReactNode;\n    label: string;\n    el?: \"label\" | \"div\";\n    readOnly?: boolean;\n    className?: string;\n  }>;\n  components: RenderFunc; // DEPRECATED\n  componentItem: RenderFunc<{ children: ReactNode; name: string }>; // DEPRECATED\n  drawer: RenderFunc;\n  drawerItem: RenderFunc<{ children: ReactNode; name: string }>;\n  iframe: RenderFunc<{ children: ReactNode; document?: Document }>;\n  outline: RenderFunc;\n  componentOverlay: RenderFunc<{\n    children: ReactNode;\n    hover: boolean;\n    isSelected: boolean;\n    componentId: string;\n    componentType: string;\n  }>;\n  puck: RenderFunc;\n}>;\n\nexport type FieldRenderFunctions<\n  UserConfig extends Config = Config,\n  G extends UserGenerics<UserConfig> = UserGenerics<UserConfig>,\n  UserField extends { type: string } = Field | G[\"UserField\"]\n> = Omit<\n  {\n    [Type in UserField[\"type\"]]: React.FunctionComponent<\n      FieldProps<ExtractField<UserField, Type>, any> & {\n        children: ReactNode;\n        name: string;\n      }\n    >;\n  },\n  \"custom\"\n>;\n"
  },
  {
    "path": "packages/core/types/API/Viewports.ts",
    "content": "import { ReactNode } from \"react\";\n\ntype iconTypes = \"Smartphone\" | \"Monitor\" | \"Tablet\";\n\nexport type Viewport = {\n  width: number | \"100%\";\n  height?: number | \"auto\";\n  label?: string;\n  icon?: iconTypes | ReactNode;\n};\n\nexport type Viewports = Viewport[];\n"
  },
  {
    "path": "packages/core/types/API/index.ts",
    "content": "import { ReactElement, ReactNode } from \"react\";\nimport { PuckAction } from \"../../reducer\";\nimport { WithDeepSlots } from \"../Internal\";\nimport { DefaultComponentProps } from \"../Props\";\nimport { AppState } from \"./../AppState\";\nimport { ComponentDataOptionalId, Content, Data } from \"./../Data\";\nimport { Overrides } from \"./Overrides\";\nimport { FieldTransforms } from \"./FieldTransforms\";\nimport { Config, DefaultComponents } from \"../Config\";\n\nexport type Permissions = {\n  drag: boolean;\n  duplicate: boolean;\n  delete: boolean;\n  edit: boolean;\n  insert: boolean;\n} & Record<string, boolean>;\n\nexport type IframeConfig = {\n  enabled?: boolean;\n  waitForStyles?: boolean;\n};\n\nexport type OnAction<UserData extends Data = Data> = (\n  action: PuckAction,\n  appState: AppState<UserData>,\n  prevAppState: AppState<UserData>\n) => void;\n\nexport type Plugin<UserConfig extends Config = Config> = {\n  name?: string;\n  label?: string;\n  icon?: ReactNode;\n  render?: () => ReactElement;\n  overrides?: Partial<Overrides<UserConfig>>;\n  fieldTransforms?: FieldTransforms<UserConfig>;\n  mobilePanelHeight?: \"toggle\" | \"min-content\";\n};\n\nexport type History<D = any> = {\n  state: D;\n  id?: string;\n};\n\ntype InitialHistoryAppend<AS = Partial<AppState>> = {\n  histories: History<AS>[];\n  index?: number;\n  appendData?: true;\n};\n\ntype InitialHistoryNoAppend<AS = Partial<AppState>> = {\n  histories: [History<AS>, ...History<AS>[]]; // Array with minimum length of 1\n  index?: number;\n  appendData?: false;\n};\n\nexport type InitialHistory<AS = Partial<AppState>> =\n  | InitialHistoryAppend<AS>\n  | InitialHistoryNoAppend<AS>;\n\nexport type Slot<\n  Props extends { [key: string]: DefaultComponentProps } = {\n    [key: string]: DefaultComponentProps;\n  }\n> = {\n  [K in keyof Props]: ComponentDataOptionalId<\n    Props[K],\n    K extends string ? K : never\n  >;\n}[keyof Props][];\n\nexport type WithSlotProps<\n  Target extends Record<string, any>,\n  Components extends DefaultComponents = DefaultComponents,\n  SlotType extends Content<Components> = Content<Components>\n> = WithDeepSlots<Target, SlotType>;\n\nexport type RichText = string | ReactNode;\n\nexport * from \"./DropZone\";\nexport * from \"./Viewports\";\n\nexport type { Overrides };\n\nexport * from \"./FieldTransforms\";\n"
  },
  {
    "path": "packages/core/types/AppState.tsx",
    "content": "import { ItemSelector } from \"../lib/data/get-item\";\nimport { Viewport } from \"./API\";\nimport { Data } from \"./Data\";\n\nexport type ItemWithId = {\n  _arrayId: string;\n  _originalIndex: number;\n  _currentIndex: number;\n};\n\nexport type ArrayState = { items: ItemWithId[]; openId: string };\n\nexport type UiState = {\n  leftSideBarVisible: boolean;\n  rightSideBarVisible: boolean;\n  leftSideBarWidth?: number | null;\n  rightSideBarWidth?: number | null;\n  mobilePanelExpanded?: boolean;\n  itemSelector: ItemSelector | null;\n  arrayState: Record<string, ArrayState | undefined>;\n  previewMode: \"interactive\" | \"edit\";\n  componentList: Record<\n    string,\n    {\n      components?: string[];\n      title?: string;\n      visible?: boolean;\n      expanded?: boolean;\n    }\n  >;\n  isDragging: boolean;\n  viewports: {\n    current: {\n      width: number | \"100%\";\n      height: number | \"auto\";\n    };\n    controlsVisible: boolean;\n    options: Viewport[];\n  };\n  field: { focus?: string | null; metadata?: Record<string, any> };\n  plugin: {\n    current: string | null;\n  };\n};\n\nexport type AppState<UserData extends Data = Data> = {\n  data: UserData;\n  ui: UiState;\n};\n"
  },
  {
    "path": "packages/core/types/Config.tsx",
    "content": "import type { JSX, ReactNode } from \"react\";\nimport { BaseField, Field, Fields } from \"./Fields\";\nimport { ComponentData, ComponentMetadata, RootData } from \"./Data\";\n\nimport { AsFieldProps, WithChildren, WithId, WithPuckProps } from \"./Utils\";\nimport { AppState } from \"./AppState\";\nimport { DefaultComponentProps, DefaultRootFieldProps } from \"./Props\";\nimport { Permissions } from \"./API\";\nimport { DropZoneProps } from \"../components/DropZone/types\";\nimport {\n  AssertHasValue,\n  FieldsExtension,\n  LeftOrExactRight,\n  WithDeepSlots,\n} from \"./Internal\";\n\nexport type SlotComponent = (props?: Omit<DropZoneProps, \"zone\">) => ReactNode;\n\nexport type PuckComponent<Props> = (\n  props: WithId<\n    WithPuckProps<{\n      [K in keyof Props]: WithDeepSlots<Props[K], SlotComponent>;\n    }>\n  >\n) => JSX.Element;\n\nexport type ResolveDataTrigger =\n  | \"insert\"\n  | \"replace\"\n  | \"load\"\n  | \"force\"\n  | \"move\";\n\ntype WithPartialProps<T, Props extends DefaultComponentProps> = Omit<\n  T,\n  \"props\"\n> & {\n  props?: Partial<Props>;\n};\n\nexport interface ComponentConfigExtensions {}\n\ntype ComponentConfigInternal<\n  RenderProps extends DefaultComponentProps,\n  FieldProps extends DefaultComponentProps,\n  DataShape = Omit<ComponentData<FieldProps>, \"type\">, // NB this doesn't include AllProps, so types will not contain deep slot types. To fix, we require a breaking change.\n  UserField extends BaseField = {}\n> = {\n  render: PuckComponent<RenderProps>;\n  label?: string;\n  defaultProps?: FieldProps;\n  fields?: Fields<FieldProps, UserField>;\n  permissions?: Partial<Permissions>;\n  inline?: boolean;\n  resolveFields?: (\n    data: DataShape,\n    params: {\n      changed: Partial<Record<keyof FieldProps, boolean> & { id: string }>;\n      fields: Fields<FieldProps>;\n      lastFields: Fields<FieldProps>;\n      lastData: DataShape | null;\n      metadata: ComponentMetadata;\n      appState: AppState;\n      parent: ComponentData | null;\n    }\n  ) => Promise<Fields<FieldProps>> | Fields<FieldProps>;\n  resolveData?: (\n    data: DataShape,\n    params: {\n      changed: Partial<Record<keyof FieldProps, boolean> & { id: string }>;\n      lastData: DataShape | null;\n      metadata: ComponentMetadata;\n      trigger: ResolveDataTrigger;\n      parent: ComponentData | null;\n    }\n  ) =>\n    | Promise<WithPartialProps<DataShape, FieldProps>>\n    | WithPartialProps<DataShape, FieldProps>;\n  resolvePermissions?: (\n    data: DataShape,\n    params: {\n      changed: Partial<Record<keyof FieldProps, boolean> & { id: string }>;\n      lastPermissions: Partial<Permissions>;\n      permissions: Partial<Permissions>;\n      appState: AppState;\n      lastData: DataShape | null;\n      parent: ComponentData | null;\n    }\n  ) => Promise<Partial<Permissions>> | Partial<Permissions>;\n  metadata?: ComponentMetadata;\n} & ComponentConfigExtensions;\n\n// DEPRECATED - remove old generics in favour of Params\nexport type ComponentConfig<\n  RenderPropsOrParams extends LeftOrExactRight<\n    RenderPropsOrParams,\n    DefaultComponentProps,\n    ComponentConfigParams\n  > = DefaultComponentProps,\n  FieldProps extends DefaultComponentProps = RenderPropsOrParams extends {\n    props: any;\n  }\n    ? RenderPropsOrParams[\"props\"]\n    : RenderPropsOrParams,\n  DataShape = Omit<ComponentData<FieldProps>, \"type\"> // NB this doesn't include AllProps, so types will not contain deep slot types. To fix, we require a breaking change.\n> = RenderPropsOrParams extends ComponentConfigParams<\n  infer ParamsRenderProps,\n  never\n>\n  ? ComponentConfigInternal<ParamsRenderProps, FieldProps, DataShape, {}>\n  : RenderPropsOrParams extends ComponentConfigParams<\n      infer ParamsRenderProps,\n      infer ParamsFields\n    >\n  ? ComponentConfigInternal<\n      ParamsRenderProps,\n      FieldProps,\n      DataShape,\n      ParamsFields[keyof ParamsFields] & BaseField\n    >\n  : ComponentConfigInternal<RenderPropsOrParams, FieldProps, DataShape>;\n\ntype RootConfigInternal<\n  RootProps extends DefaultComponentProps = DefaultComponentProps,\n  UserField extends BaseField = {}\n> = Partial<\n  ComponentConfigInternal<\n    WithChildren<RootProps>,\n    AsFieldProps<RootProps>,\n    RootData<AsFieldProps<RootProps>>,\n    UserField\n  >\n>;\n\n// DEPRECATED - remove old generics in favour of Params\nexport type RootConfig<\n  RootPropsOrParams extends LeftOrExactRight<\n    RootPropsOrParams,\n    DefaultComponentProps,\n    ComponentConfigParams\n  > = DefaultComponentProps\n> = RootPropsOrParams extends ComponentConfigParams<infer Props, never>\n  ? Partial<RootConfigInternal<WithChildren<Props>, {}>>\n  : RootPropsOrParams extends ComponentConfigParams<\n      infer Props,\n      infer UserFields\n    >\n  ? Partial<\n      RootConfigInternal<\n        WithChildren<Props>,\n        UserFields[keyof UserFields] & BaseField\n      >\n    >\n  : Partial<RootConfigInternal<WithChildren<RootPropsOrParams>>>;\n\ntype Category<ComponentName> = {\n  components?: ComponentName[];\n  title?: string;\n  visible?: boolean;\n  defaultExpanded?: boolean;\n};\n\ntype ConfigInternal<\n  Props extends DefaultComponents = DefaultComponents,\n  RootProps extends DefaultComponentProps = DefaultComponentProps,\n  CategoryName extends string = string,\n  UserField extends {} = {}\n> = {\n  categories?: Record<CategoryName, Category<keyof Props>> & {\n    other?: Category<keyof Props>;\n  };\n  components: {\n    [ComponentName in keyof Props]: Omit<\n      ComponentConfigInternal<\n        Props[ComponentName],\n        Props[ComponentName],\n        Omit<ComponentData<Props[ComponentName]>, \"type\">,\n        UserField\n      >,\n      \"type\"\n    >;\n  };\n  root?: RootConfigInternal<RootProps, UserField>;\n};\n\n// This _deliberately_ casts as any so the user can pass in something that widens the types\nexport type DefaultComponents = Record<string, any>;\n\n// DEPRECATED - remove old generics in favour of Params\nexport type Config<\n  PropsOrParams extends LeftOrExactRight<\n    PropsOrParams,\n    DefaultComponents,\n    ConfigParams\n  > = DefaultComponents | ConfigParams,\n  RootProps extends DefaultComponentProps = any,\n  CategoryName extends string = string\n> = PropsOrParams extends ConfigParams<\n  infer ParamComponents,\n  infer ParamRoot,\n  infer ParamCategoryName,\n  never\n>\n  ? ConfigInternal<ParamComponents, ParamRoot, ParamCategoryName[number]>\n  : PropsOrParams extends ConfigParams<\n      infer ParamComponents,\n      infer ParamRoot,\n      infer ParamCategoryName,\n      infer ParamFields\n    >\n  ? ConfigInternal<\n      ParamComponents,\n      ParamRoot,\n      ParamCategoryName[number],\n      ParamFields[keyof ParamFields] & BaseField\n    >\n  : PropsOrParams extends ConfigParams<\n      infer ParamComponents,\n      infer ParamRoot,\n      infer ParamCategoryName,\n      any\n    >\n  ? ConfigInternal<ParamComponents, ParamRoot, ParamCategoryName[number], {}>\n  : ConfigInternal<PropsOrParams, RootProps, CategoryName>;\n\nexport type ExtractConfigParams<UserConfig extends ConfigInternal> =\n  UserConfig extends ConfigInternal<\n    infer PropsOrParams,\n    infer RootProps,\n    infer CategoryName,\n    infer UserField\n  >\n    ? {\n        props: PropsOrParams;\n        rootProps: RootProps & DefaultRootFieldProps;\n        categoryNames: CategoryName;\n        field: UserField extends { type: string } ? UserField : Field;\n      }\n    : never;\n\nexport type ConfigParams<\n  Components extends DefaultComponents = DefaultComponents,\n  RootProps extends DefaultComponentProps = any,\n  CategoryNames extends string[] = string[],\n  UserFields extends FieldsExtension = FieldsExtension\n> = {\n  components?: Components;\n  root?: RootProps;\n  categories?: CategoryNames;\n  fields?: AssertHasValue<UserFields>;\n};\n\nexport type ComponentConfigParams<\n  Props extends DefaultComponentProps = DefaultComponentProps,\n  UserFields extends FieldsExtension = never\n> = {\n  props: Props;\n  fields?: AssertHasValue<UserFields>;\n};\n"
  },
  {
    "path": "packages/core/types/Data.tsx",
    "content": "import { DefaultComponents } from \"./Config\";\nimport { WithDeepSlots } from \"./Internal\";\nimport { DefaultComponentProps, DefaultRootFieldProps } from \"./Props\";\nimport { AsFieldProps, WithId } from \"./Utils\";\n\nexport type BaseData<\n  Props extends { [key: string]: any } = { [key: string]: any }\n> = {\n  readOnly?: Partial<Record<keyof Props, boolean>>;\n};\n\nexport type RootDataWithProps<\n  Props extends DefaultComponentProps = DefaultRootFieldProps\n> = BaseData<Props> & {\n  props: Props;\n};\n\n// DEPRECATED\nexport type RootDataWithoutProps<\n  Props extends DefaultComponentProps = DefaultRootFieldProps\n> = Props;\n\nexport type RootData<\n  Props extends DefaultComponentProps = DefaultRootFieldProps\n> = Partial<RootDataWithProps<AsFieldProps<Props>>> &\n  Partial<RootDataWithoutProps<Props>>; // DEPRECATED\n\nexport type ComponentData<\n  Props extends DefaultComponentProps = DefaultComponentProps,\n  Name = string,\n  Components extends Record<string, DefaultComponentProps> = Record<\n    string,\n    DefaultComponentProps\n  >\n> = {\n  type: Name;\n  props: WithDeepSlots<WithId<Props>, Content<Components>>;\n} & BaseData<Props>;\n\nexport type ComponentDataOptionalId<\n  Props extends DefaultComponentProps = DefaultComponentProps,\n  Name = string\n> = {\n  type: Name;\n  props: Props & {\n    id?: string;\n  };\n} & BaseData<Props>;\n\n// Backwards compatibility\nexport type MappedItem = ComponentData;\n\nexport type ComponentDataMap<\n  Components extends DefaultComponents = DefaultComponents\n> = {\n  [K in keyof Components]: ComponentData<\n    Components[K],\n    K extends string ? K : never,\n    Components\n  >;\n}[keyof Components];\n\nexport type Content<\n  PropsMap extends { [key: string]: DefaultComponentProps } = {\n    [key: string]: DefaultComponentProps;\n  }\n> = ComponentDataMap<PropsMap>[];\n\nexport type Data<\n  Components extends DefaultComponents = DefaultComponents,\n  RootProps extends DefaultComponentProps = DefaultRootFieldProps\n> = {\n  root: WithDeepSlots<RootData<RootProps>, Content<Components>>;\n  content: Content<Components>;\n  zones?: Record<string, Content<Components>>;\n};\n\nexport type Metadata = { [key: string]: any };\n\nexport interface PuckMetadata extends Metadata {}\n\nexport interface ComponentMetadata extends PuckMetadata {}\n\nexport interface FieldMetadata extends Metadata {}\n"
  },
  {
    "path": "packages/core/types/Fields.ts",
    "content": "import { CSSProperties, ReactElement, ReactNode } from \"react\";\nimport { DefaultComponentProps, FieldMetadata, UiState } from \".\";\nimport type { Editor, Extensions } from \"@tiptap/react\";\nimport {\n  EditorState,\n  RichTextSelector,\n} from \"../components/RichTextEditor/types\";\nimport { PuckRichTextOptions } from \"../components/RichTextEditor/extension\";\n\ntype FieldOption = {\n  label: string;\n  value: string | number | boolean | undefined | null | object;\n};\n\ntype FieldOptions = Array<FieldOption> | ReadonlyArray<FieldOption>;\n\nexport interface BaseField {\n  label?: string;\n  labelIcon?: ReactElement;\n  metadata?: FieldMetadata;\n  visible?: boolean;\n}\n\nexport interface TextField extends BaseField {\n  type: \"text\";\n  placeholder?: string;\n  contentEditable?: boolean;\n}\n\nexport interface NumberField extends BaseField {\n  type: \"number\";\n  placeholder?: string;\n  min?: number;\n  max?: number;\n  step?: number;\n}\n\nexport interface TextareaField extends BaseField {\n  type: \"textarea\";\n  placeholder?: string;\n  contentEditable?: boolean;\n}\n\nexport interface SelectField extends BaseField {\n  type: \"select\";\n  options: FieldOptions;\n}\n\nexport interface RadioField extends BaseField {\n  type: \"radio\";\n  options: FieldOptions;\n}\n\nexport interface RichtextField<\n  UserSelector extends RichTextSelector = RichTextSelector\n> extends BaseField {\n  type: \"richtext\";\n  contentEditable?: boolean;\n  initialHeight?: CSSProperties[\"height\"];\n  options?: Partial<PuckRichTextOptions>;\n  renderMenu?: (props: {\n    children: ReactNode;\n    editor: Editor | null;\n    editorState: EditorState<UserSelector> | null;\n    readOnly: boolean;\n  }) => ReactNode;\n  renderInlineMenu?: (props: {\n    children: ReactNode;\n    editor: Editor | null;\n    editorState: EditorState<UserSelector> | null;\n    readOnly: boolean;\n  }) => ReactNode;\n  tiptap?: {\n    selector?: UserSelector;\n    extensions?: Extensions;\n  };\n}\n\nexport interface ArrayField<\n  Props extends { [key: string]: any }[] = { [key: string]: any }[],\n  UserField extends {} = {}\n> extends BaseField {\n  type: \"array\";\n  arrayFields: {\n    [SubPropName in keyof Props[0]]: UserField extends { type: PropertyKey }\n      ? Field<Props[0][SubPropName], UserField> | UserField\n      : Field<Props[0][SubPropName], UserField>;\n  };\n  defaultItemProps?: Props[0] | ((index: number) => Props[0]);\n  getItemSummary?: (item: Props[0], index?: number) => ReactNode;\n  max?: number;\n  min?: number;\n}\n\nexport interface ObjectField<\n  Props extends any = { [key: string]: any },\n  UserField extends {} = {}\n> extends BaseField {\n  type: \"object\";\n  objectFields: {\n    [SubPropName in keyof Props]: UserField extends { type: PropertyKey }\n      ? Field<Props[SubPropName]> | UserField\n      : Field<Props[SubPropName]>;\n  };\n}\n\n// DEPRECATED\nexport type Adaptor<\n  AdaptorParams = {},\n  TableShape extends Record<string, any> = {},\n  PropShape = TableShape\n> = {\n  name: string;\n  fetchList: (adaptorParams?: AdaptorParams) => Promise<TableShape[] | null>;\n  mapProp?: (value: TableShape) => PropShape;\n};\n\ntype NotUndefined<T> = T extends undefined ? never : T;\n\n// DEPRECATED\nexport type ExternalFieldWithAdaptor<\n  Props extends any = { [key: string]: any }\n> = BaseField & {\n  type: \"external\";\n  placeholder?: string;\n  adaptor: Adaptor<any, any, Props>;\n  adaptorParams?: object;\n  getItemSummary: (item: NotUndefined<Props>, index?: number) => ReactNode;\n};\n\nexport type CacheOpts = {\n  enabled?: boolean;\n};\n\nexport interface ExternalField<Props extends any = { [key: string]: any }>\n  extends BaseField {\n  type: \"external\";\n  cache?: CacheOpts;\n  placeholder?: string;\n  fetchList: (params: {\n    query: string;\n    filters: Record<string, any>;\n  }) => Promise<any[] | null>;\n  mapProp?: (value: any) => Props;\n  mapRow?: (value: any) => Record<string, string | number | ReactElement>;\n  getItemSummary?: (item: NotUndefined<Props>, index?: number) => ReactNode;\n  showSearch?: boolean;\n  renderFooter?: (props: { items: any[] }) => ReactElement;\n  initialQuery?: string;\n  filterFields?: Record<string, Field>;\n  initialFilters?: Record<string, any>;\n}\n\nexport type CustomFieldRender<Value extends any> = (props: {\n  field: CustomField<Value>;\n  name: string;\n  id: string;\n  value: Value;\n  onChange: (value: Value) => void;\n  readOnly?: boolean;\n}) => ReactElement;\n\nexport interface CustomField<Value extends any> extends BaseField {\n  type: \"custom\";\n  render: CustomFieldRender<Value>;\n  contentEditable?: boolean;\n  key?: string;\n}\n\nexport interface SlotField extends BaseField {\n  type: \"slot\";\n  allow?: string[];\n  disallow?: string[];\n}\n\nexport type Field<ValueType = any, UserField extends {} = {}> =\n  | TextField\n  | RichtextField\n  | NumberField\n  | TextareaField\n  | SelectField\n  | RadioField\n  | ArrayField<\n      ValueType extends { [key: string]: any }[] ? ValueType : never,\n      UserField\n    >\n  | ObjectField<ValueType, UserField>\n  | ExternalField<ValueType>\n  | ExternalFieldWithAdaptor<ValueType>\n  | CustomField<ValueType>\n  | SlotField;\n\nexport type Fields<\n  ComponentProps extends DefaultComponentProps = DefaultComponentProps,\n  UserField extends {} = {}\n> = {\n  [PropName in keyof Omit<ComponentProps, \"editMode\">]: UserField extends {\n    type: PropertyKey;\n  }\n    ? Field<ComponentProps[PropName], UserField> | UserField\n    : Field<ComponentProps[PropName]>;\n};\n\nexport type FieldProps<F = Field<any>, ValueType = any> = {\n  field: F;\n  value: ValueType;\n  id?: string;\n  onChange: (value: ValueType, uiState?: Partial<UiState>) => void;\n  readOnly?: boolean;\n};\n"
  },
  {
    "path": "packages/core/types/Internal.tsx",
    "content": "import { ReactElement, ReactNode } from \"react\";\nimport { Plugin, Slot } from \"./API\";\nimport { AppState } from \"./AppState\";\nimport { DefaultComponents } from \"./Config\";\nimport { ComponentData, Data } from \"./Data\";\nimport { DefaultComponentProps } from \"./Props\";\n\nexport type ZoneType = \"root\" | \"dropzone\" | \"slot\";\n\nexport type PuckNodeData = {\n  data: ComponentData;\n  flatData: ComponentData;\n  parentId: string | null;\n  zone: string;\n  path: string[];\n};\n\nexport type PuckZoneData = {\n  contentIds: string[];\n  type: ZoneType;\n};\n\nexport type NodeIndex = Record<string, PuckNodeData>;\nexport type ZoneIndex = Record<string, PuckZoneData>;\n\nexport type PrivateAppState<UserData extends Data = Data> =\n  AppState<UserData> & {\n    indexes: {\n      nodes: NodeIndex;\n      zones: ZoneIndex;\n    };\n  };\n\ntype BuiltinTypes =\n  | Date\n  | RegExp\n  | Error\n  | Function\n  | symbol\n  | null\n  | undefined;\n\n/**\n * Recursively walk T and replace Slots with SlotComponents\n */\nexport type WithDeepSlots<T, SlotType = T> =\n  // ────────────────────────────── leaf conversions ─────────────────────────────\n  T extends Slot\n    ? SlotType\n    : // ────────────────────────── recurse into arrays & tuples ─────────────────\n    T extends (infer U)[]\n    ? Array<WithDeepSlots<U, SlotType>>\n    : T extends (infer U)[]\n    ? WithDeepSlots<U, SlotType>[]\n    : // ────────────────────────── preserve objects like Date ───────────────────\n    T extends BuiltinTypes\n    ? T\n    : // ───────────── recurse into objects while preserving optionality ─────────\n    T extends object\n    ? { [K in keyof T]: WithDeepSlots<T[K], SlotType> }\n    : T;\n\nexport type ConfigParams<\n  Components extends DefaultComponents = DefaultComponents,\n  RootProps extends DefaultComponentProps = any,\n  CategoryNames extends string[] = string[],\n  UserFields extends FieldsExtension = FieldsExtension\n> = {\n  components?: Components;\n  root?: RootProps;\n  categories?: CategoryNames;\n  fields?: AssertHasValue<UserFields>;\n};\n\nexport type FieldsExtension = { [Type in string]: { type: Type } };\n\nexport type ComponentConfigParams<\n  Props extends DefaultComponentProps = DefaultComponentProps,\n  UserFields extends FieldsExtension = never\n> = {\n  props: Props;\n  fields?: AssertHasValue<UserFields>;\n};\n\n// Check the keys of T do not introduce additional ones to Target\nexport type Exact<T, Target> = Record<Exclude<keyof T, keyof Target>, never>;\n\n// Ensures the union either extends the left type, or is exactly the right type\n// This prevents type widening\nexport type LeftOrExactRight<Union, Left, Right> =\n  | (Left & Union extends Right ? Exact<Union, Right> : Left)\n  | (Right & Exact<Union, Right>);\n\nexport type AssertHasValue<T, True = T, False = never> = [keyof T] extends [\n  never\n]\n  ? False\n  : True;\n\n// Plugins can use `usePuck` instead of relying on props\nexport type RenderFunc<\n  Props extends { [key: string]: any } = { children: ReactNode }\n> = (props: Props) => ReactElement;\n\nexport type PluginInternal = Plugin & {\n  mobileOnly?: boolean;\n  desktopOnly?: boolean;\n};\n"
  },
  {
    "path": "packages/core/types/Props.tsx",
    "content": "import { DropZoneProps } from \"../components/DropZone/types\";\nimport { Metadata } from \"./Data\";\nimport { WithChildren, WithPuckProps } from \"./Utils\";\n\nexport type PuckContext = {\n  renderDropZone: (props: DropZoneProps) => React.ReactNode;\n  metadata: Metadata;\n  isEditing: boolean;\n  dragRef: ((element: Element | null) => void) | null;\n};\n\nexport type DefaultRootFieldProps = {\n  title?: string;\n};\n\nexport type DefaultRootRenderProps<\n  Props extends DefaultComponentProps = DefaultRootFieldProps\n> = WithPuckProps<WithChildren<Props>>;\n\nexport type DefaultRootProps = DefaultRootRenderProps; // Deprecated\n\nexport type DefaultComponentProps = { [key: string]: any };\n"
  },
  {
    "path": "packages/core/types/Utils.tsx",
    "content": "import { ReactNode } from \"react\";\nimport { Config, ExtractConfigParams } from \"./Config\";\nimport { DefaultRootFieldProps, PuckContext } from \"./Props\";\nimport { ComponentData, Data } from \"./Data\";\nimport { PrivateAppState } from \"./Internal\";\nimport { AppState } from \"./AppState\";\n\nexport type WithId<Props> = Props & {\n  id: string;\n};\n\nexport type WithPuckProps<Props> = Props & {\n  puck: PuckContext;\n  editMode?: boolean;\n};\nexport type AsFieldProps<Props> = Omit<Props, \"children\" | \"puck\" | \"editMode\">;\n\nexport type WithChildren<Props> = Props & {\n  children: ReactNode;\n};\n\nexport type UserGenerics<\n  UserConfig extends Config = Config,\n  UserParams extends ExtractConfigParams<UserConfig> = ExtractConfigParams<UserConfig>,\n  UserData extends\n    | Data<UserParams[\"props\"], UserParams[\"rootProps\"]>\n    | Data = Data<UserParams[\"props\"], UserParams[\"rootProps\"]>,\n  UserAppState extends PrivateAppState<UserData> = PrivateAppState<UserData>,\n  UserPublicAppState extends AppState<UserData> = AppState<UserData>,\n  UserComponentData extends ComponentData = UserData[\"content\"][0]\n> = {\n  UserConfig: UserConfig;\n  UserParams: UserParams;\n  UserProps: UserParams[\"props\"];\n  UserRootProps: UserParams[\"rootProps\"] & DefaultRootFieldProps;\n  UserData: UserData;\n  UserAppState: UserAppState;\n  UserPublicAppState: UserPublicAppState;\n  UserComponentData: UserComponentData;\n  UserField: UserParams[\"field\"];\n};\n\nexport type ExtractField<\n  UserField extends { type: PropertyKey },\n  T extends UserField[\"type\"]\n> = Extract<UserField, { type: T }>;\n"
  },
  {
    "path": "packages/core/types/__tests__/internal.spec.ts",
    "content": "import { WithDeepSlots } from \"../Internal\";\n\ndescribe(\"WithDeepSlots\", () => {\n  it(\"should preserve types\", () => {\n    const testObject = {\n      someDate: new Date(),\n      someRegExp: /'/g,\n      someError: new Error(\"An error occurred\"),\n      someFunction: () => void 0,\n    } satisfies WithDeepSlots<{}>;\n\n    testObject.someDate.getDate();\n    testObject.someRegExp.test(\"test\");\n    testObject.someError.message;\n    testObject.someFunction();\n\n    expect(\"no compilation error\").toBeTruthy();\n  });\n});\n"
  },
  {
    "path": "packages/core/types/index.ts",
    "content": "export * from \"./API\";\nexport * from \"./API/Overrides\";\nexport * from \"./AppState\";\nexport * from \"./Config\";\nexport * from \"./Data\";\nexport * from \"./Fields\";\nexport * from \"./Props\";\nexport * from \"./Utils\";\n"
  },
  {
    "path": "packages/create-puck-app/README.md",
    "content": "# create-puck-app\n\n`create-puck-app` generates recipes. For a full list of recipes, please see the monorepo README.\n\n## Usage\n\nnpx\n\n```sh\nnpx create-puck-app my-app\n```\n\nyarn\n\n```sh\nyarn create puck-app my-app\n```\n\n## License\n\nMIT © [The Puck Contributors](https://github.com/puckeditor/puck/graphs/contributors)\n"
  },
  {
    "path": "packages/create-puck-app/index.js",
    "content": "#!/usr/bin/env node\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { program } from \"commander\";\nimport inquirer from \"inquirer\";\nimport Handlebars from \"handlebars\";\nimport { glob } from \"glob\";\nimport { execSync } from \"child_process\";\nimport { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = dirname(__filename);\n\nconst packageJson = JSON.parse(\n  fs.readFileSync(path.join(__dirname, \"./package.json\"))\n);\n\nconst ansiColors = {\n  reset: \"\\x1b[0m\",\n  cyan: \"\\x1b[36m\",\n};\n\n// Lifted from https://github.com/vercel/next.js/blob/c2d7bbd1b82c71808b99e9a7944fb16717a581db/packages/create-next-app/helpers/get-pkg-manager.ts\nfunction getPkgManager() {\n  // eslint-disable-next-line turbo/no-undeclared-env-vars\n  const userAgent = process.env.npm_config_user_agent || \"\";\n\n  if (userAgent.startsWith(\"yarn\")) {\n    return \"yarn\";\n  }\n\n  if (userAgent.startsWith(\"pnpm\")) {\n    return \"pnpm\";\n  }\n\n  return \"npm\";\n}\n\nconst handleSigTerm = () => process.exit(0);\n\nprocess.on(\"SIGINT\", handleSigTerm);\nprocess.on(\"SIGTERM\", handleSigTerm);\n\nprogram\n  .command(\"create [app-name]\")\n  .option(\n    \"--use-npm\",\n    `\n\n    Explicitly tell the CLI to bootstrap the application using npm\n  `\n  )\n  .option(\n    \"--use-pnpm\",\n    `\n\n    Explicitly tell the CLI to bootstrap the application using pnpm\n  `\n  )\n  .option(\n    \"--use-yarn\",\n    `\n\n    Explicitly tell the CLI to bootstrap the application using Yarn\n  `\n  )\n  .action(async (_appName, options) => {\n    const beforeQuestions = [];\n\n    if (!_appName) {\n      beforeQuestions.push({\n        type: \"input\",\n        name: \"appName\",\n        message: \"What is the name of your app?\",\n        required: true,\n      });\n    }\n\n    const questions = [\n      ...beforeQuestions,\n      {\n        type: \"list\",\n        name: \"recipe\",\n        message: \"Which recipe would you like to use?\",\n        required: true,\n        default: \"next\",\n        choices: [\n          {\n            name: \"Next.js\",\n            value: \"next\",\n          },\n          {\n            name: \"React Router\",\n            value: \"react-router\",\n          },\n          {\n            name: \"Remix\",\n            value: \"remix\",\n          },\n        ],\n      },\n      {\n        type: \"confirm\",\n        name: \"puckAi\",\n        message: `Puck AI (beta) lets you generate pages using your own components. Learn more: ${ansiColors.cyan}https://puckeditor.com/docs/ai/overview${ansiColors.reset}\n\n  Add Puck AI to the editor? (Requires a Puck Cloud account)`,\n        required: true,\n        default: true,\n      },\n    ];\n\n    const answers = await inquirer.prompt(questions);\n\n    // Check the app name\n    const appNameInput = answers.appName || _appName;\n    const appName = typeof appNameInput === \"string\" ? appNameInput.trim() : \"\";\n\n    if (!appName) {\n      console.error(\"Please provide a name for your app.\");\n      return;\n    }\n\n    const recipe = answers.recipe;\n    const usesPuckAi = answers.puckAi;\n\n    // Copy template files to the new directory\n    const recipeName = `${recipe}${usesPuckAi ? \"-ai\" : \"\"}`;\n    const templatePath = path.join(__dirname, \"./templates\", recipeName);\n    const appPath = path.join(process.cwd(), appName);\n\n    if (!recipe) {\n      console.error(`Please specify a recipe.`);\n      return;\n    }\n\n    if (!fs.existsSync(templatePath)) {\n      console.error(`No recipe named ${recipeName} exists.`);\n      return;\n    }\n\n    if (fs.existsSync(appPath)) {\n      console.error(\n        `A directory called ${appName} already exists. Please use a different name or delete this directory.`\n      );\n      return;\n    }\n\n    fs.mkdirSync(appName);\n\n    const packageManager = !!options.useNpm\n      ? \"npm\"\n      : !!options.usePnpm\n      ? \"pnpm\"\n      : !!options.useYarn\n      ? \"yarn\"\n      : getPkgManager();\n\n    // Compile handlebars templates\n    const templateFiles = glob.sync(`**/*`, {\n      cwd: templatePath,\n      nodir: true,\n      dot: true,\n    });\n\n    for (const templateFile of templateFiles) {\n      const filePath = path.join(templatePath, templateFile);\n      const targetPath = filePath\n        .replace(templatePath, appPath)\n        .replace(\".hbs\", \"\")\n        .replace(\"gitignore\", \".gitignore\"); // Rename gitignore back to .gitignore (.gitignore) gets ignored by npm during publish\n\n      let data;\n\n      if (path.extname(filePath) === \".hbs\") {\n        const templateString = fs.readFileSync(filePath, \"utf-8\");\n\n        const template = Handlebars.compile(templateString);\n        data = template({\n          ...answers,\n          appName,\n          puckVersion: `^${packageJson.version}`,\n        });\n      } else {\n        data = fs.readFileSync(filePath, \"utf-8\");\n      }\n\n      const dir = path.dirname(targetPath);\n\n      fs.mkdirSync(dir, { recursive: true });\n\n      fs.writeFileSync(targetPath, data);\n    }\n\n    if (packageManager === \"yarn\") {\n      execSync(\"yarn install\", { cwd: appPath, stdio: \"inherit\" });\n    } else {\n      execSync(`${packageManager} i`, { cwd: appPath, stdio: \"inherit\" });\n    }\n\n    let inGitRepo = false;\n\n    try {\n      inGitRepo =\n        execSync(\"git status\", { cwd: appPath })\n          .toString()\n          .indexOf(\"fatal:\") !== 0;\n    } catch {}\n\n    // Only commit if this is a new repo\n    if (!inGitRepo) {\n      try {\n        execSync(\"git init\", { cwd: appPath, stdio: \"inherit\" });\n\n        execSync(\"git add .\", { cwd: appPath, stdio: \"inherit\" });\n        execSync('git commit -m \"build(puck): generate app\"', {\n          cwd: appPath,\n          stdio: \"inherit\",\n        });\n      } catch (error) {\n        console.log(\"Failed to commit git changes\");\n      }\n    }\n\n    console.log(\"\\nDone! Now run:\\n\");\n    console.log(`  cd ${appName}`);\n    console.log(`  ${packageManager} run dev\\n`);\n\n    if (usesPuckAi) {\n      console.log(\n        `Configure Puck AI access: ${ansiColors.cyan}https://cloud.puckeditor.com/onboarding/integrate${ansiColors.reset}`\n      );\n    }\n  })\n  .parse(process.argv);\n"
  },
  {
    "path": "packages/create-puck-app/package.json",
    "content": "{\n  \"name\": \"create-puck-app\",\n  \"version\": \"0.21.1\",\n  \"author\": \"Chris Villa <chris@puckeditor.com>\",\n  \"repository\": \"puckeditor/puck\",\n  \"bugs\": \"https://github.com/puckeditor/puck/issues\",\n  \"homepage\": \"https://puckeditor.com\",\n  \"private\": false,\n  \"license\": \"MIT\",\n  \"type\": \"module\",\n  \"bin\": {\n    \"create-puck-app\": \"./index.js\"\n  },\n  \"files\": [\n    \"templates\",\n    \"index.js\"\n  ],\n  \"scripts\": {\n    \"generate\": \"node scripts/generate.js\",\n    \"prepublishOnly\": \"yarn generate\",\n    \"removeGitignore\": \"mv templates/.gitignore templates/gitignore\",\n    \"restoreGitignore\": \"mv templates/gitignore templates/.gitignore\"\n  },\n  \"dependencies\": {\n    \"commander\": \"^10.0.1\",\n    \"glob\": \"^11.1.0\",\n    \"handlebars\": \"^4.7.7\",\n    \"inquirer\": \"^9.2.7\",\n    \"prettier\": \"^2.8.8\"\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/scripts/generate.js",
    "content": "#!/usr/bin/env node\n\nimport fs from \"fs\";\nimport path from \"path\";\nimport { glob } from \"glob\";\nimport { dirname } from \"path\";\nimport { fileURLToPath } from \"url\";\n\nconst verbose = false;\n\nconst run = async () => {\n  const __filename = fileURLToPath(import.meta.url);\n  const __dirname = dirname(__filename);\n\n  // Copy template files to the new directory\n  const recipePath = path.join(__dirname, \"../../../recipes\");\n  const templatePath = path.join(__dirname, \"../templates\");\n\n  if (!fs.existsSync(recipePath)) {\n    console.error(`No recipe directory could be found at ${recipePath}.`);\n    return;\n  }\n\n  if (!fs.existsSync(templatePath)) {\n    console.error(`No template directory could be found at ${templatePath}.`);\n    return;\n  }\n\n  // Copy recipe files\n  const recipeFiles = glob.sync(`**/*`, {\n    cwd: recipePath,\n    nodir: true,\n    dot: true,\n  });\n\n  console.warn(\n    `⚠️   The following files use handlebars templates. Please manually update them:`\n  );\n\n  let counter = 0;\n\n  for (const recipeFile of recipeFiles) {\n    const filePath = path.join(recipePath, recipeFile);\n\n    const targetPath = filePath.replace(recipePath, templatePath);\n\n    // Don't copy file if it's templated by handlebars\n    if (fs.existsSync(`${targetPath}.hbs`)) {\n      console.warn(`- ${recipeFile}`);\n    } else {\n      if (verbose) {\n        console.log(`Copying ${filePath} -> ${targetPath}`);\n      }\n\n      const data = await fs.readFileSync(filePath, \"utf-8\");\n\n      const dir = path.dirname(targetPath);\n\n      await fs.mkdirSync(dir, { recursive: true });\n\n      await fs.writeFileSync(targetPath, data);\n\n      if (targetPath.indexOf(\".gitignore\") > -1) {\n        await fs.copyFileSync(\n          filePath,\n          targetPath.replace(\".gitignore\", \"gitignore\") // rename .gitignore to gitignore so NPM publish doesn't ignore it\n        );\n      }\n\n      counter += 1;\n    }\n  }\n\n  console.log(`Copied ${counter} files into generator!`);\n};\n\nawait run();\n"
  },
  {
    "path": "packages/create-puck-app/templates/next/package.json.hbs",
    "content": "{\n  \"name\": \"{{appName}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"{{puckVersion}}\",\n    \"classnames\": \"^2.3.2\",\n    \"next\": \"^16.0.8\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"typescript\": \"^5.5.4\"\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/next-ai/package.json.hbs",
    "content": "{\n  \"name\": \"{{appName}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev\",\n    \"build\": \"next build\",\n    \"start\": \"next start\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"{{puckVersion}}\",\n    \"@puckeditor/cloud-client\": \"^0.5.0\",\n    \"@puckeditor/plugin-ai\": \"^0.5.0\",\n    \"classnames\": \"^2.3.2\",\n    \"next\": \"^16.0.8\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"typescript\": \"^5.5.4\"\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/react-router/package.json.hbs",
    "content": "{\n  \"name\": \"{{appName}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev\",\n    \"start\": \"react-router-serve ./build/server/index.js\",\n    \"typecheck\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"{{puckVersion}}\",\n    \"@react-router/node\": \"^7.5.3\",\n    \"@react-router/serve\": \"^7.5.3\",\n    \"isbot\": \"^5\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\",\n    \"react-router\": \"^7.5.3\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.5.3\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"typescript\": \"^5.8.3\",\n    \"vite\": \"^6.3.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/react-router/tsconfig.json.hbs",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/react-router-ai/package.json.hbs",
    "content": "{\n  \"name\": \"{{appName}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev\",\n    \"start\": \"react-router-serve ./build/server/index.js\",\n    \"typecheck\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"{{puckVersion}}\",\n    \"@puckeditor/cloud-client\": \"^0.5.0\",\n    \"@puckeditor/plugin-ai\": \"^0.5.0\",\n    \"@react-router/node\": \"^7.5.3\",\n    \"@react-router/serve\": \"^7.5.3\",\n    \"isbot\": \"^5\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\",\n    \"react-router\": \"^7.5.3\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.5.3\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"typescript\": \"^5.8.3\",\n    \"vite\": \"^6.3.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/react-router-ai/tsconfig.json.hbs",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/remix/package.json.hbs",
    "content": "{\n  \"name\": \"{{appName}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"remix build\",\n    \"dev\": \"remix dev --manual\",\n    \"start\": \"remix-serve ./build/index.js\",\n    \"typecheck\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"{{puckVersion}}\",\n    \"@remix-run/css-bundle\": \"^2.2.0\",\n    \"@remix-run/node\": \"^2.2.0\",\n    \"@remix-run/react\": \"^2.2.0\",\n    \"@remix-run/serve\": \"^2.2.0\",\n    \"isbot\": \"^3.6.8\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"tiny-invariant\": \"^1.3.3\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/dev\": \"^2.2.0\",\n    \"@remix-run/eslint-config\": \"^2.2.0\",\n    \"@types/react\": \"^18.2.20\",\n    \"@types/react-dom\": \"^18.2.7\",\n    \"eslint\": \"^8.38.0\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/create-puck-app/templates/remix-ai/package.json.hbs",
    "content": "{\n  \"name\": \"{{appName}}\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"remix build\",\n    \"dev\": \"remix dev --manual\",\n    \"start\": \"remix-serve ./build/index.js\",\n    \"typecheck\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"{{puckVersion}}\",\n    \"@puckeditor/cloud-client\": \"^0.5.0\",\n    \"@puckeditor/plugin-ai\": \"^0.5.0\",\n    \"@remix-run/css-bundle\": \"^2.2.0\",\n    \"@remix-run/node\": \"^2.2.0\",\n    \"@remix-run/react\": \"^2.2.0\",\n    \"@remix-run/serve\": \"^2.2.0\",\n    \"isbot\": \"^3.6.8\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"tiny-invariant\": \"^1.3.3\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/dev\": \"^2.2.0\",\n    \"@remix-run/eslint-config\": \"^2.2.0\",\n    \"@types/react\": \"^18.2.20\",\n    \"@types/react-dom\": \"^18.2.7\",\n    \"eslint\": \"^8.38.0\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/eslint-config-custom/index.js",
    "content": "const turboConfig = require(\"eslint-config-turbo\").default.extends;\n\nmodule.exports = {\n  extends: [\"next\", ...turboConfig, \"prettier\"],\n  rules: {\n    \"@next/next/no-html-link-for-pages\": \"off\",\n  },\n  parserOptions: {\n    babelOptions: {\n      presets: [],\n    },\n  },\n};\n"
  },
  {
    "path": "packages/eslint-config-custom/package.json",
    "content": "{\n  \"name\": \"eslint-config-custom\",\n  \"version\": \"0.0.0\",\n  \"main\": \"index.js\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"eslint-config-next\": \"^13.4.1\",\n    \"eslint-config-prettier\": \"^8.3.0\",\n    \"eslint-plugin-react\": \"7.28.0\",\n    \"eslint-config-turbo\": \"^2.3.3\"\n  },\n  \"publishConfig\": {\n    \"access\": \"public\"\n  }\n}\n"
  },
  {
    "path": "packages/field-contentful/README.md",
    "content": "# field-contentful\n\nSelect [entries](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/entries) from a [Contentful](https://www.contentful.com) space.\n\n## Quick start\n\n```sh\nnpm i @puckeditor/field-contentful\n```\n\n```jsx\nimport createFieldContentful from \"@puckeditor/field-contentful\";\n\nconst config = {\n  components: {\n    Example: {\n      fields: {\n        movie: createFieldContentful(\"movies\", {\n          space: \"my_space\",\n          accessToken: \"abcdefg123456\",\n        }),\n      },\n      render: ({ data }) => {\n        return <p>{data?.fields.title || \"No data selected\"}</p>;\n      },\n    },\n  },\n};\n```\n\n## Args\n\n| Param                         | Example  | Type   | Status   |\n| ----------------------------- | -------- | ------ | -------- |\n| [`contentType`](#contenttype) | `movies` | String | Required |\n| [`options`](#options)         | `{}`     | Object | Required |\n\n### Required args\n\n#### `contentType`\n\nID of the Contentful [Content Type](https://www.contentful.com/help/content-model-and-content-type/) to query.\n\n#### `options`\n\n| Param                                      | Example                                 | Type                                                            | Status                         |\n| ------------------------------------------ | --------------------------------------- | --------------------------------------------------------------- | ------------------------------ |\n| [`accessToken`](#optionsaccesstoken)       | `\"abc123\"`                              | String                                                          | Required (unless using client) |\n| [`space`](#optionsspace)                   | `\"my-space\"`                            | String                                                          | Required (unless using client) |\n| [`client`](#optionsclient)                 | `createClient()`                        | [ContentfulClientApi](https://www.npmjs.com/package/contentful) | -                              |\n| [`filterFields`](#optionsfilterfields)     | `{ \"rating[gte]\": { type: \"number\" } }` | Object                                                          | -                              |\n| [`initialFilters`](#optionsinitialfilters) | `{ \"rating[gte]\": 1 }`                  | Object                                                          | -                              |\n| [`titleField`](#optionstitlefield)         | `\"name\"`                                | String                                                          | -                              |\n\n##### `options.accessToken`\n\nYour Contentful access token.\n\n##### `options.space`\n\nThe id for the Contentful space that contains your content.\n\n##### `options.client`\n\nA Contentful client as created by the [`contentful` Node.js package](https://www.npmjs.com/package/contentful). You can use this instead of `accessToken` and `space` if you want to reuse your client, or customise it more fully.\n\n##### `options.filterFields`\n\nAn object describing which [`filterFields`](https://puckeditor.com/docs/api-reference/fields/external#filterfields) to render and pass the result directly to Contentful as [search parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters).\n\n```jsx\ncreateFieldContentful(\"movies\", {\n  // ...\n  filterFields: {\n    // Filter the \"rating\" field by value greater than the user input\n    \"fields.rating[gte]\": {\n      type: \"number\",\n    },\n  },\n});\n```\n\n##### `options.initialFilters`\n\nThe initial values for the filters defined in [`filterFields`](#optionsfilterfields). This data is passed directly directly to Contentful as [search parameters](https://www.contentful.com/developers/docs/references/content-delivery-api/#/reference/search-parameters).\n\n```jsx\ncreateFieldContentful(\"movies\", {\n  // ...\n  initialFilters: {\n    \"fields.rating[gte]\": 1,\n    select: \"name,rating\", // Can include search parameters not included in filterFields\n  },\n});\n```\n\n##### `options.titleField`\n\nThe field to use as the title for the selected item. Defaults to `\"title\"`.\n\n```jsx\ncreateFieldContentful(\"movies\", {\n  // ...\n  titleField: \"name\",\n});\n```\n\n## Returns\n\nAn [External field](https://puckeditor.com/docs/api-reference/fields/external) type that loads Contentful [entries](https://contentful.github.io/contentful.js/contentful/10.6.16/types/Entry.html).\n\n## TypeScript\n\nYou can use the `Entry` type for data loaded via Contentful:\n\n```tsx\nimport createFieldContentful, { Entry } from \"@/field-contentful\";\n\ntype MyProps = {\n  Example: {\n    movie: Entry<{ title: string; description: string; rating: number }>;\n  };\n};\n\nconst config: Config<MyProps> = {\n  // ...\n};\n```\n\n## License\n\nMIT © [The Puck Contributors](https://github.com/puckeditor/puck/graphs/contributors)\n"
  },
  {
    "path": "packages/field-contentful/index.ts",
    "content": "import { ExternalField } from \"@/core/types\";\n\nimport { BaseEntry, ContentfulClientApi, createClient } from \"contentful\";\n\nexport { createClient };\n\nexport type Entry<Fields extends Record<string, any> = {}> = BaseEntry & {\n  fields: Fields;\n};\n\nexport function createFieldContentful<T extends Entry = Entry>(\n  contentType: string,\n  options: {\n    client?: ContentfulClientApi<undefined>;\n    space?: string;\n    accessToken?: string;\n    titleField?: string;\n    filterFields?: ExternalField[\"filterFields\"];\n    initialFilters?: ExternalField[\"initialFilters\"];\n  } = {}\n) {\n  const {\n    space,\n    accessToken,\n    titleField = \"title\",\n    filterFields,\n    initialFilters,\n  } = options;\n\n  if (!options.client) {\n    if (!space || !accessToken) {\n      throw new Error(\n        'field-contentful: Must either specify \"client\", or \"space\" and \"accessToken\"'\n      );\n    }\n  }\n\n  const client =\n    options.client ||\n    createClient({ space: space!, accessToken: accessToken! });\n\n  const field: ExternalField<T> = {\n    type: \"external\",\n    placeholder: \"Select from Contentful\",\n    showSearch: true,\n    fetchList: async ({ query, filters = {} }) => {\n      const entries = await client.getEntries({\n        ...filters,\n        content_type: contentType,\n        query,\n      });\n\n      return entries.items;\n    },\n    mapRow: ({ fields }) => fields,\n    getItemSummary: (item) =>\n      item.fields[titleField as keyof typeof item.fields],\n    filterFields,\n    initialFilters,\n  };\n\n  return field;\n}\n\nexport default createFieldContentful;\n"
  },
  {
    "path": "packages/field-contentful/package.json",
    "content": "{\n  \"name\": \"@puckeditor/field-contentful\",\n  \"version\": \"0.21.1\",\n  \"author\": \"Chris Villa <chris@puckeditor.com>\",\n  \"repository\": \"puckeditor/puck\",\n  \"bugs\": \"https://github.com/puckeditor/puck/issues\",\n  \"homepage\": \"https://puckeditor.com\",\n  \"private\": false,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \"import\": \"./dist/index.mjs\",\n    \"types\": \"./dist/index.d.ts\"\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"lint\": \"eslint \\\"**/*.ts*\\\"\",\n    \"build\": \"rm -rf dist && tsup index.ts\",\n    \"prepare\": \"yarn build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"devDependencies\": {\n    \"@puckeditor/core\": \"^0.21.1\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"contentful\": \"^10.8.6\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-custom\": \"*\",\n    \"tsconfig\": \"*\",\n    \"tsup-config\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  },\n  \"peerDependencies\": {\n    \"contentful\": \"^10.0.0\",\n    \"react\": \"^17.0.0 || ^18.0.0 || ^19.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/field-contentful/tsconfig.json",
    "content": "{\n  \"extends\": \"tsconfig/react-library.json\",\n  \"include\": [\".\"],\n  \"exclude\": [\"dist\", \"build\", \"node_modules\"],\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/core\": [\"../core\"],\n      \"@/core/*\": [\"../core/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/field-contentful/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\";\nimport tsupconfig from \"../tsup-config\";\n\nexport default defineConfig(tsupconfig);\n"
  },
  {
    "path": "packages/plugin-emotion-cache/README.md",
    "content": "# plugin-emotion-cache\n\nInject [emotion cache](https://emotion.sh/docs/@emotion/cache) into the Puck iframe.\n\n## Quick start\n\n```sh\nnpm i @puckeditor/plugin-emotion-cache\n```\n\n```jsx\nimport { Puck } from \"@puckeditor/core\";\nimport createEmotionCache from \"@puckeditor/plugin-emotion-cache\";\n\n// Create your emotion cache plugin. This example configures it for Chakra.\nconst chakraEmotionCache = createEmotionCache(\"cha\");\n\n// Render Puck\nexport function Page() {\n  return <Puck config={config} data={data} plugins={[chakraEmotionCache]} />;\n}\n```\n\n## Args\n\n| Param         | Example | Type   | Status   |\n| ------------- | ------- | ------ | -------- |\n| [`key`](#key) | `cha`   | String | Required |\n\n### Required args\n\n#### `key`\n\nKey to pass to Emotion's [`createCache` method](https://emotion.sh/docs/@emotion/cache#createcache).\n\n## License\n\nMIT © [The Puck Contributors](https://github.com/puckeditor/puck/graphs/contributors)\n"
  },
  {
    "path": "packages/plugin-emotion-cache/index.tsx",
    "content": "import { Plugin } from \"@/core/types\";\nimport { useEffect, useState } from \"react\";\n\nimport createCache, { EmotionCache } from \"@emotion/cache\";\nimport { CacheProvider } from \"@emotion/react\";\n\nconst createEmotionCachePlugin = (key: string): Plugin => {\n  return {\n    overrides: {\n      iframe: ({ children, document }) => {\n        // eslint-disable-next-line react-hooks/rules-of-hooks\n        const [cache, setCache] = useState<EmotionCache | null>(null);\n\n        // eslint-disable-next-line react-hooks/rules-of-hooks\n        useEffect(() => {\n          // Defer until next render\n          setTimeout(() => {\n            if (document) {\n              setCache(\n                createCache({\n                  key,\n                  container: document.head,\n                })\n              );\n            }\n          }, 0);\n        }, [document, key]);\n\n        if (cache) {\n          return <CacheProvider value={cache}>{children}</CacheProvider>;\n        }\n\n        return <>{children}</>;\n      },\n    },\n  };\n};\n\nexport default createEmotionCachePlugin;\n"
  },
  {
    "path": "packages/plugin-emotion-cache/package.json",
    "content": "{\n  \"name\": \"@puckeditor/plugin-emotion-cache\",\n  \"version\": \"0.21.1\",\n  \"author\": \"Chris Villa <chris@puckeditor.com>\",\n  \"repository\": \"puckeditor/puck\",\n  \"bugs\": \"https://github.com/puckeditor/puck/issues\",\n  \"homepage\": \"https://puckeditor.com\",\n  \"private\": false,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \"import\": \"./dist/index.mjs\",\n    \"require\": \"./dist/index.js\",\n    \"types\": \"./dist/index.d.ts\"\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"lint\": \"eslint \\\"**/*.ts*\\\"\",\n    \"build\": \"rm -rf dist && tsup index.tsx\",\n    \"prepare\": \"yarn build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"devDependencies\": {\n    \"@emotion/react\": \"^11.13.3\",\n    \"@puckeditor/core\": \"^0.21.1\",\n    \"@types/minimatch\": \"3.0.5\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.0.2\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-custom\": \"*\",\n    \"tsconfig\": \"*\",\n    \"tsup\": \"^8.2.4\",\n    \"tsup-config\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  },\n  \"peerDependencies\": {\n    \"@emotion/react\": \"^11.0.0\",\n    \"react\": \"^17.0.0 || ^18.0.0 || ^19.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/plugin-emotion-cache/tsconfig.json",
    "content": "{\n  \"extends\": \"tsconfig/react-library.json\",\n  \"include\": [\".\"],\n  \"exclude\": [\"dist\", \"build\", \"node_modules\"],\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/core\": [\"../core\"],\n      \"@/core/*\": [\"../core/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/plugin-emotion-cache/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\";\nimport tsupconfig from \"../tsup-config\";\n\nexport default defineConfig(tsupconfig);\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/README.md",
    "content": "# plugin-heading-analyzer\n\nVisualise your heading outline structure and identify missing heading levels. Respects WCAG 2.\n\n<img src=\"https://i.imgur.com/POqtgHu.jpg\" alt=\"example\" width=\"156px\" />\n\n## Quick start\n\n```sh\nnpm i @puckeditor/plugin-heading-analyzer\n```\n\n```jsx\nimport { Puck } from \"@puckeditor/core\";\nimport headingAnalyzer from \"@puckeditor/plugin-heading-analyzer\";\nimport \"@puckeditor/plugin-heading-analyzer/dist/index.css\";\n\n...\n\n// Render Puck\nexport function Page() {\n  return <Puck\n    config={config}\n    data={data}\n    plugins={[\n        headingAnalyzer\n    ]}\n  />;\n}\n```\n\n## License\n\nMIT © [The Puck Contributors](https://github.com/puckeditor/puck/graphs/contributors)\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/globals.d.ts",
    "content": "declare module \"*.module.css\";\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/index.ts",
    "content": "export { default } from \"./src/HeadingAnalyzer\";\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/package.json",
    "content": "{\n  \"name\": \"@puckeditor/plugin-heading-analyzer\",\n  \"version\": \"0.21.1\",\n  \"author\": \"Chris Villa <chris@puckeditor.com>\",\n  \"repository\": \"puckeditor/puck\",\n  \"bugs\": \"https://github.com/puckeditor/puck/issues\",\n  \"homepage\": \"https://puckeditor.com\",\n  \"private\": false,\n  \"main\": \"./dist/index.js\",\n  \"types\": \"./dist/index.d.ts\",\n  \"exports\": {\n    \".\": {\n      \"import\": \"./dist/index.mjs\",\n      \"types\": \"./dist/index.d.ts\"\n    },\n    \"./dist/index.css\": \"./dist/index.css\"\n  },\n  \"license\": \"MIT\",\n  \"scripts\": {\n    \"lint\": \"eslint \\\"**/*.ts*\\\"\",\n    \"build\": \"rm -rf dist && tsup index.ts\",\n    \"prepare\": \"yarn build\"\n  },\n  \"files\": [\n    \"dist\"\n  ],\n  \"devDependencies\": {\n    \"@puckeditor/core\": \"^0.21.1\",\n    \"@types/minimatch\": \"3.0.5\",\n    \"@types/react\": \"^19.0.1\",\n    \"@types/react-dom\": \"^19.0.2\",\n    \"eslint\": \"^7.32.0\",\n    \"eslint-config-custom\": \"*\",\n    \"tsconfig\": \"*\",\n    \"tsup\": \"^8.2.4\",\n    \"tsup-config\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  },\n  \"dependencies\": {\n    \"react-from-json\": \"^0.8.0\"\n  },\n  \"peerDependencies\": {\n    \"react\": \"^17.0.0 || ^18.0.0 || ^19.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/src/HeadingAnalyzer.module.css",
    "content": ".HeadingAnalyzer {\n  display: block;\n  padding: 16px;\n}\n\n.HeadingAnalyzer-cssWarning {\n  display: none !important;\n}\n\n.HeadingAnalyzerItem {\n  display: inline;\n}\n\n.HeadingAnalyzerItem--missing {\n  color: var(--puck-color-red-04);\n}\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/src/HeadingAnalyzer.tsx",
    "content": "import { ReactElement, useEffect, useState } from \"react\";\n\nimport styles from \"./HeadingAnalyzer.module.css\";\n\nimport { createUsePuck } from \"@puckeditor/core\";\nimport { Plugin } from \"@/core/types\";\nimport { OutlineList } from \"@/core/components/OutlineList\";\n\nimport { scrollIntoView } from \"@/core/lib/scroll-into-view\";\nimport { getFrame } from \"@/core/lib/get-frame\";\n\nimport { Heading1 } from \"lucide-react\";\n\nimport { getClassNameFactory } from \"@/core/lib\";\n\nconst getClassName = getClassNameFactory(\"HeadingAnalyzer\", styles);\nconst getClassNameItem = getClassNameFactory(\"HeadingAnalyzerItem\", styles);\n\nimport ReactFromJSONModule from \"react-from-json\";\n\n// Synthetic import\nconst ReactFromJSON =\n  (ReactFromJSONModule as unknown as { default: typeof ReactFromJSONModule })\n    .default || ReactFromJSONModule;\n\nconst getOutline = ({ frame }: { frame?: Element | Document } = {}) => {\n  const headings = frame?.querySelectorAll(\"h1,h2,h3,h4,h5,h6\") || [];\n\n  const _outline: {\n    rank: number;\n    text: string;\n    element: HTMLElement;\n  }[] = [];\n\n  headings.forEach((item, i) => {\n    if (item.closest(\"[data-dnd-dragging]\")) {\n      return;\n    }\n\n    _outline.push({\n      rank: parseInt(item.tagName.split(\"H\")[1]),\n      text: item.textContent!,\n      element: item as HTMLElement,\n    });\n  });\n\n  return _outline;\n};\n\ntype Block = {\n  rank: number;\n  text: string;\n  children?: Block[];\n  missing?: boolean;\n  analyzeId?: string;\n  element?: HTMLElement;\n};\n\nfunction buildHierarchy(frame: Element | Document): Block[] {\n  const headings = getOutline({ frame });\n\n  const root = { rank: 0, children: [], text: \"\" }; // Placeholder root node\n  let path: Block[] = [root];\n\n  for (let heading of headings) {\n    const node: Block = {\n      rank: heading.rank,\n      text: heading.text,\n      children: [],\n      element: heading.element,\n    };\n\n    // When encountering an h1, reset the path to only the root\n    if (node.rank === 1) {\n      path = [root];\n    } else {\n      // Go up the path until finding a node where this heading can be a child\n      while (path[path.length - 1].rank >= node.rank) {\n        path.pop();\n      }\n\n      // Add missing nodes if necessary\n      while (path.length < node.rank) {\n        const missingNode: Block = {\n          rank: path.length,\n          missing: true,\n          children: [],\n          text: \"\",\n        };\n        path[path.length - 1].children?.push(missingNode);\n        path.push(missingNode);\n      }\n    }\n\n    // Add this node to its parent in the path and update path\n    path[path.length - 1].children?.push(node);\n    path.push(node);\n  }\n\n  return root.children;\n}\n\nconst usePuck = createUsePuck();\n\nexport const HeadingAnalyzer = () => {\n  const data = usePuck((s) => s.appState.data);\n  const [hierarchy, setHierarchy] = useState<Block[]>([]);\n\n  // Re-render when content changes\n  useEffect(() => {\n    const frame = getFrame();\n\n    let entry = frame?.querySelector(`[data-puck-entry]`);\n\n    const createHierarchy = () => {\n      setHierarchy(buildHierarchy(entry!));\n    };\n    const entryObserver = new MutationObserver(() => {\n      createHierarchy();\n    });\n\n    const frameObserver = new MutationObserver(() => {\n      entry = frame?.querySelector(`[data-puck-entry]`);\n\n      if (entry) {\n        registerEntryObserver();\n        frameObserver.disconnect();\n      }\n    });\n\n    const registerEntryObserver = () => {\n      if (!entry) return;\n      entryObserver.observe(entry, { subtree: true, childList: true });\n    };\n\n    const registerFrameObserver = () => {\n      if (!frame) return;\n      frameObserver.observe(frame, { subtree: true, childList: true });\n    };\n\n    if (entry) {\n      createHierarchy();\n      registerEntryObserver();\n    } else {\n      registerFrameObserver();\n    }\n\n    return () => {\n      entryObserver.disconnect();\n      frameObserver.disconnect();\n    };\n  }, [data]);\n\n  return (\n    <div className={getClassName()}>\n      <small\n        className={getClassName(\"cssWarning\")}\n        style={{\n          color: \"var(--puck-color-red-04)\",\n          display: \"block\",\n          marginBottom: 16,\n        }}\n      >\n        Heading analyzer styles not loaded. Please review the{\" \"}\n        <a href=\"https://github.com/puckeditor/puck/blob/main/packages/plugin-heading-analyzer/README.md\">\n          README\n        </a>\n        .\n      </small>\n\n      {hierarchy.length === 0 && <div>No headings.</div>}\n\n      <OutlineList>\n        <ReactFromJSON<{\n          Root: (props: any) => ReactElement;\n          OutlineListItem: (props: any) => ReactElement;\n        }>\n          mapping={{\n            Root: (props) => <>{props.children}</>,\n            OutlineListItem: (props) => (\n              <OutlineList.Item>\n                <OutlineList.Clickable>\n                  <small\n                    className={getClassNameItem({ missing: props.missing })}\n                    onClick={\n                      typeof props.element == \"undefined\"\n                        ? undefined\n                        : (e) => {\n                            e.stopPropagation();\n\n                            const el = props.element;\n\n                            const oldStyle = { ...el.style };\n\n                            if (el) {\n                              scrollIntoView(el);\n\n                              el.style.outline =\n                                \"4px solid var(--puck-color-rose-06)\";\n                              el.style.outlineOffset = \"4px\";\n\n                              setTimeout(() => {\n                                el.style.outline = oldStyle.outline || \"\";\n                                el.style.outlineOffset =\n                                  oldStyle.outlineOffset || \"\";\n                              }, 2000);\n                            }\n                          }\n                    }\n                  >\n                    {props.missing ? (\n                      <>\n                        <b>H{props.rank}</b>: Missing\n                      </>\n                    ) : (\n                      <>\n                        <b>H{props.rank}</b>: {props.text}\n                      </>\n                    )}\n                  </small>\n                </OutlineList.Clickable>\n                <OutlineList>{props.children}</OutlineList>\n              </OutlineList.Item>\n            ),\n          }}\n          entry={{\n            props: { children: hierarchy },\n            type: \"Root\",\n          }}\n          mapProp={(prop) => {\n            if (prop && prop.rank) {\n              return {\n                type: \"OutlineListItem\",\n                props: prop,\n              };\n            }\n\n            return prop;\n          }}\n        />\n      </OutlineList>\n    </div>\n  );\n};\n\nconst headingAnalyzer: Plugin = {\n  name: \"heading-analyzer\",\n  label: \"Audit\",\n  render: HeadingAnalyzer,\n  icon: <Heading1 />,\n};\n\nexport default headingAnalyzer;\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/tsconfig.json",
    "content": "{\n  \"extends\": \"tsconfig/react-library.json\",\n  \"include\": [\".\"],\n  \"exclude\": [\"dist\", \"build\", \"node_modules\"],\n  \"compilerOptions\": {\n    \"paths\": {\n      \"@/core\": [\"../core\"],\n      \"@/core/*\": [\"../core/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "packages/plugin-heading-analyzer/tsup.config.ts",
    "content": "import { defineConfig } from \"tsup\";\nimport tsupconfig from \"../tsup-config\";\n\nexport default defineConfig(tsupconfig);\n"
  },
  {
    "path": "packages/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Default\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"inlineSources\": false,\n    \"isolatedModules\": true,\n    \"moduleResolution\": \"node\",\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"preserveWatchOutput\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/tsconfig/nextjs.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Next.js\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"allowJs\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"incremental\": true,\n    \"jsx\": \"preserve\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"strict\": false,\n    \"target\": \"es5\"\n  },\n  \"include\": [\"src\", \"next-env.d.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "packages/tsconfig/package.json",
    "content": "{\n  \"name\": \"tsconfig\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"dependencies\": {\n    \"typescript-plugin-css-modules\": \"^5.0.1\"\n  }\n}\n"
  },
  {
    "path": "packages/tsconfig/react-library.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"React Library\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"strict\": true,\n    \"jsx\": \"react-jsx\",\n    \"lib\": [\"ES2015\", \"DOM\"],\n    \"module\": \"ESNext\",\n    \"target\": \"es6\",\n    \"plugins\": [{ \"name\": \"typescript-plugin-css-modules\" }]\n  }\n}\n"
  },
  {
    "path": "packages/tsup-config/index.ts",
    "content": "import fs from \"fs\";\nimport path from \"path\";\nimport postcss from \"postcss\";\nimport postcssModules from \"postcss-modules\";\nimport type { Options } from \"tsup\";\n\nconst config: Options = {\n  dts: true,\n  format: [\"cjs\", \"esm\"],\n  inject: [\"../tsup-config/react-import.js\"],\n  external: [\n    \"react\",\n    \"react-dom\",\n    \"@puckeditor/core\",\n    \"@dnd-kit/react\",\n    \"@dnd-kit/dom\",\n    \"@dnd-kit/abstract\",\n    \"@dnd-kit/state\",\n    \"@dnd-kit/geometry\",\n    \"@dnd-kit/utilities\",\n  ],\n  esbuildPlugins: [\n    {\n      name: \"css-module\",\n      setup(build): void {\n        build.onResolve(\n          { filter: /\\.module\\.css$/, namespace: \"file\" },\n          (args) => ({\n            path: `${path.join(args.resolveDir, args.path)}#css-module`,\n            namespace: \"css-module\",\n            pluginData: {\n              pathDir: path.join(args.resolveDir, args.path),\n            },\n          })\n        );\n        build.onLoad(\n          { filter: /#css-module$/, namespace: \"css-module\" },\n          async (args) => {\n            const { pluginData } = args as {\n              pluginData: { pathDir: string };\n            };\n\n            const source = fs.readFileSync(pluginData.pathDir, \"utf8\");\n\n            let cssModule = {};\n            const result = await postcss([\n              postcssModules({\n                getJSON(_, json) {\n                  cssModule = json;\n                },\n              }),\n            ]).process(source, { from: pluginData.pathDir });\n\n            return {\n              pluginData: { css: result.css },\n              contents: `import \"${\n                pluginData.pathDir\n              }\"; export default ${JSON.stringify(cssModule)}`,\n            };\n          }\n        );\n        build.onResolve(\n          { filter: /\\.module\\.css$/, namespace: \"css-module\" },\n          (args) => ({\n            path: path.join(args.resolveDir, args.path, \"#css-module-data\"),\n            namespace: \"css-module\",\n            pluginData: args.pluginData as { css: string },\n          })\n        );\n        build.onLoad(\n          { filter: /#css-module-data$/, namespace: \"css-module\" },\n          (args) => ({\n            contents: (args.pluginData as { css: string }).css,\n            loader: \"css\",\n          })\n        );\n      },\n    },\n  ],\n};\n\nexport default config;\n"
  },
  {
    "path": "packages/tsup-config/package.json",
    "content": "{\n  \"name\": \"tsup-config\",\n  \"version\": \"0.0.0\",\n  \"private\": true,\n  \"license\": \"MIT\",\n  \"publishConfig\": {\n    \"access\": \"public\"\n  },\n  \"dependencies\": {\n    \"tsconfig\": \"*\",\n    \"tsup\": \"^8.2.4\",\n    \"typescript\": \"^5.5.4\",\n    \"postcss\": \"^8.4.24\",\n    \"postcss-modules\": \"^6.0.0\"\n  }\n}\n"
  },
  {
    "path": "packages/tsup-config/react-import.js",
    "content": "// react-import.js\nimport React from \"react\";\n\nexport { React };\n"
  },
  {
    "path": "recipes/next/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: [\"custom\"],\n};\n"
  },
  {
    "path": "recipes/next/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# vercel\n.vercel\n"
  },
  {
    "path": "recipes/next/README.md",
    "content": "# `next` recipe\n\nThe `next` recipe showcases one of the most powerful ways to implement Puck using to provide an authoring tool for any route in your Next app.\n\n## Demonstrates\n\n- Next.js App Router implementation\n- JSON database implementation with HTTP API\n- Catch-all routes to use puck for any route on the platform\n- Incremental static regeneration (ISR) for all Puck pages\n\n## Usage\n\nRun the generator and enter `next` when prompted\n\n```\nnpx create-puck-app my-app\n```\n\nStart the server\n\n```\nyarn dev\n```\n\nNavigate to the homepage at https://localhost:3000. To edit the homepage, access the Puck editor at https://localhost:3000/edit.\n\nYou can do this for any route on the application, **even if the page doesn't exist**. For example, visit https://localhost:3000/hello/world and you'll receive a 404. You can author and publish a page by visiting https://localhost:3000/hello/world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the example API routes in `/app/puck/api/route.ts` and server component in `/app/puck/[...puckPath]/page.tsx`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the API calls in `/app/puck/api/route.ts`\n- Implement a custom puck configuration in `puck.config.tsx`\n\nBy default, this recipe will generate static pages by setting `dynamic` to [`force-static`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic) in the `/app/[...puckPath]/page.tsx`. This will strip headers and cookies. If you need dynamic pages, you can delete this.\n"
  },
  {
    "path": "recipes/next/app/[...puckPath]/client.tsx",
    "content": "\"use client\";\n\nimport type { Data } from \"@puckeditor/core\";\nimport { Render } from \"@puckeditor/core\";\nimport config from \"../../puck.config\";\n\nexport function Client({ data }: { data: Data }) {\n  return <Render config={config} data={data} />;\n}\n"
  },
  {
    "path": "recipes/next/app/[...puckPath]/page.tsx",
    "content": "/**\n * This file implements a catch-all route that renders the user-facing pages\n * generated by Puck. For any route visited (with exception of other hardcoded\n * pages in /app), it will check your database (via `getPage`) for a Puck page\n * and render it using <Render>.\n *\n * All routes produced by this page are statically rendered using incremental\n * static site generation. After the first visit, the page will be cached as\n * a static file. Subsequent visits will receive the cache. Publishing a page\n * will invalidate the cache as the page is written in /api/puck/route.ts\n */\n\nimport { Client } from \"./client\";\nimport { notFound } from \"next/navigation\";\nimport { Metadata } from \"next\";\nimport { getPage } from \"../../lib/get-page\";\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}): Promise<Metadata> {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n\n  return {\n    title: getPage(path)?.root.props?.title,\n  };\n}\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}) {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n  const data = getPage(path);\n\n  if (!data) {\n    return notFound();\n  }\n\n  return <Client data={data} />;\n}\n\n// Force Next.js to produce static pages: https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic\n// Delete this if you need dynamic rendering, such as access to headers or cookies\nexport const dynamic = \"force-static\";\n"
  },
  {
    "path": "recipes/next/app/layout.tsx",
    "content": "import \"./styles.css\";\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "recipes/next/app/page.tsx",
    "content": "export { default, generateMetadata } from \"./[...puckPath]/page\";\n"
  },
  {
    "path": "recipes/next/app/puck/[...puckPath]/client.tsx",
    "content": "\"use client\";\n\nimport type { Data } from \"@puckeditor/core\";\nimport { Puck } from \"@puckeditor/core\";\nimport config from \"../../../puck.config\";\n\nexport function Client({ path, data }: { path: string; data: Partial<Data> }) {\n  return (\n    <Puck\n      config={config}\n      data={data}\n      onPublish={async (data) => {\n        await fetch(\"/puck/api\", {\n          method: \"post\",\n          body: JSON.stringify({ data, path }),\n        });\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "recipes/next/app/puck/[...puckPath]/page.tsx",
    "content": "/**\n * This file implements a *magic* catch-all route that renders the Puck editor.\n *\n * This route exposes /puck/[...puckPath], but is disabled by middleware.ts. The middleware\n * then rewrites all URL requests ending in `/edit` to this route, allowing you to visit any\n * page in your application and add /edit to the end to spin up a Puck editor.\n *\n * This approach enables public pages to be statically rendered whilst the /puck route can\n * remain dynamic.\n *\n * NB this route is public, and you will need to add authentication\n */\n\nimport \"@puckeditor/core/puck.css\";\nimport { Client } from \"./client\";\nimport { Metadata } from \"next\";\nimport { getPage } from \"../../../lib/get-page\";\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}): Promise<Metadata> {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n\n  return {\n    title: \"Puck: \" + path,\n  };\n}\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}) {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n  const data = getPage(path);\n\n  return <Client path={path} data={data || {}} />;\n}\n\nexport const dynamic = \"force-dynamic\";\n"
  },
  {
    "path": "recipes/next/app/puck/api/route.ts",
    "content": "import { revalidatePath } from \"next/cache\";\nimport { NextResponse } from \"next/server\";\nimport fs from \"fs\";\n\nexport async function POST(request: Request) {\n  const payload = await request.json();\n\n  const existingData = JSON.parse(\n    fs.existsSync(\"database.json\")\n      ? fs.readFileSync(\"database.json\", \"utf-8\")\n      : \"{}\"\n  );\n\n  const updatedData = {\n    ...existingData,\n    [payload.path]: payload.data,\n  };\n\n  fs.writeFileSync(\"database.json\", JSON.stringify(updatedData));\n\n  // Purge Next.js cache\n  revalidatePath(payload.path);\n\n  return NextResponse.json({ status: \"ok\" });\n}\n"
  },
  {
    "path": "recipes/next/app/puck/page.tsx",
    "content": "export { default, generateMetadata } from \"./[...puckPath]/page\";\n\nexport const dynamic = \"force-dynamic\";\n"
  },
  {
    "path": "recipes/next/app/styles.css",
    "content": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "recipes/next/database.json",
    "content": "{\"/\":{\"content\":[{\"type\":\"HeadingBlock\",\"props\":{\"title\":\"Edit this page by adding /edit to the end of the URL\",\"id\":\"HeadingBlock-1694032984497\"}}],\"root\":{\"props\": {\"title\":\"\"}}}}"
  },
  {
    "path": "recipes/next/lib/get-page.ts",
    "content": "import { Data } from \"@puckeditor/core\";\nimport fs from \"fs\";\n\n// Replace with call to your database\nexport const getPage = (path: string) => {\n  const allData: Record<string, Data> | null = fs.existsSync(\"database.json\")\n    ? JSON.parse(fs.readFileSync(\"database.json\", \"utf-8\"))\n    : null;\n\n  return allData ? allData[path] : null;\n};\n"
  },
  {
    "path": "recipes/next/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "recipes/next/next.config.js",
    "content": "module.exports = {\n  reactStrictMode: true,\n};\n"
  },
  {
    "path": "recipes/next/package.json",
    "content": "{\n  \"name\": \"next-recipe\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbo\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"eslint\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"*\",\n    \"classnames\": \"^2.3.2\",\n    \"next\": \"^16.0.8\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"eslint-config-custom\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  }\n}\n"
  },
  {
    "path": "recipes/next/proxy.ts",
    "content": "import { NextResponse } from \"next/server\";\n\nimport type { NextRequest } from \"next/server\";\n\nexport async function proxy(req: NextRequest) {\n  const res = NextResponse.next({ request: req });\n\n  if (req.method === \"GET\") {\n    // Rewrite routes that match \"/[...puckPath]/edit\" to \"/puck/[...puckPath]\"\n    if (req.nextUrl.pathname.endsWith(\"/edit\")) {\n      const pathWithoutEdit = req.nextUrl.pathname.slice(\n        0,\n        req.nextUrl.pathname.length - 5\n      );\n      const pathWithEditPrefix = `/puck${pathWithoutEdit}`;\n\n      return NextResponse.rewrite(new URL(pathWithEditPrefix, req.url));\n    }\n\n    // Disable \"/puck/[...puckPath]\"\n    if (req.nextUrl.pathname.startsWith(\"/puck\")) {\n      return NextResponse.redirect(new URL(\"/\", req.url));\n    }\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "recipes/next/puck.config.tsx",
    "content": "import type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: { title: string };\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => (\n        <div style={{ padding: 64 }}>\n          <h1>{title}</h1>\n        </div>\n      ),\n    },\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "recipes/next/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Default\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"inlineSources\": false,\n    \"isolatedModules\": true,\n    \"moduleResolution\": \"node\",\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"preserveWatchOutput\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "recipes/next/tsconfig/nextjs.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Next.js\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"allowJs\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"incremental\": true,\n    \"jsx\": \"preserve\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"strict\": false,\n    \"target\": \"es5\"\n  },\n  \"include\": [\"src\", \"next-env.d.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "recipes/next/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig/nextjs.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }]\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "recipes/next-ai/.eslintrc.js",
    "content": "module.exports = {\n  root: true,\n  extends: [\"custom\"],\n};\n"
  },
  {
    "path": "recipes/next-ai/.gitignore",
    "content": "# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.\n\n# dependencies\n/node_modules\n/.pnp\n.pnp.js\n\n# testing\n/coverage\n\n# next.js\n/.next/\n/out/\n\n# production\n/build\n\n# misc\n.DS_Store\n*.pem\n\n# debug\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\n\n# local env files\n.env.local\n.env.development.local\n.env.test.local\n.env.production.local\n\n# vercel\n.vercel\n"
  },
  {
    "path": "recipes/next-ai/README.md",
    "content": "# `next-ai` recipe\n\nThe `next-ai` recipe showcases one of the most powerful ways to combine Puck and [Puck AI](https://puckeditor.com/docs/ai/overview): providing an authoring tool with AI page generation capabilities for any route in your Next app.\n\n## Demonstrates\n\n- Puck AI integration for generating pages with AI\n- Next.js App Router implementation\n- JSON database implementation with HTTP API\n- Catch-all routes to use puck for any route on the platform\n- Incremental static regeneration (ISR) for all Puck pages\n\n## Usage\n\nRun the generator and select `Next.js` when prompted\n\n```\nnpx create-puck-app my-app\n\n? Which recipe would you like to use?\n❯ Next.js\n```\n\nConfirm you want to use Puck AI\n\n```\n? Would you like to use Puck AI? (Y/n) Y\n```\n\nStart the server\n\n```\ncd my-app\nyarn dev\n```\n\n### Set up Puck AI\n\nCreate a [Puck account](https://cloud.puckeditor.com) and [obtain an API key](https://cloud.puckeditor.com/api-keys).\n\nCreate a `.env.local` file in the root of your project and add your API key:\n\n```\nPUCK_API_KEY=your-api-key\n```\n\nNavigate to the homepage at https://localhost:3000. To edit the homepage, access the Puck editor at https://localhost:3000/edit, and select the AI button in the left navigation bar to generate content for the page using Puck AI.\n\nYou can do this for any route on the application, **even if the page doesn't exist**. For example, visit https://localhost:3000/hello/world and you'll receive a 404. You can author and publish a page by visiting https://localhost:3000/hello/world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the example API routes in `/app/puck/api/route.ts` and server component in `/app/puck/[...puckPath]/page.tsx`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the API calls in `/app/puck/api/route.ts`\n- Implement a custom puck configuration in `puck.config.tsx`\n- Add business context for the AI generation in `/app/api/puck/[...all]/route.ts`\n\nBy default, this recipe will generate static pages by setting `dynamic` to [`force-static`](https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic) in the `/app/[...puckPath]/page.tsx`. This will strip headers and cookies. If you need dynamic pages, you can delete this.\n"
  },
  {
    "path": "recipes/next-ai/app/[...puckPath]/client.tsx",
    "content": "\"use client\";\n\nimport type { Data } from \"@puckeditor/core\";\nimport { Render } from \"@puckeditor/core\";\nimport config from \"../../puck.config\";\n\nexport function Client({ data }: { data: Data }) {\n  return <Render config={config} data={data} />;\n}\n"
  },
  {
    "path": "recipes/next-ai/app/[...puckPath]/page.tsx",
    "content": "/**\n * This file implements a catch-all route that renders the user-facing pages\n * generated by Puck. For any route visited (with exception of other hardcoded\n * pages in /app), it will check your database (via `getPage`) for a Puck page\n * and render it using <Render>.\n *\n * All routes produced by this page are statically rendered using incremental\n * static site generation. After the first visit, the page will be cached as\n * a static file. Subsequent visits will receive the cache. Publishing a page\n * will invalidate the cache as the page is written in /api/puck/route.ts\n */\n\nimport { Client } from \"./client\";\nimport { notFound } from \"next/navigation\";\nimport { Metadata } from \"next\";\nimport { getPage } from \"../../lib/get-page\";\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}): Promise<Metadata> {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n\n  return {\n    title: getPage(path)?.root.props?.title,\n  };\n}\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}) {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n  const data = getPage(path);\n\n  if (!data) {\n    return notFound();\n  }\n\n  return <Client data={data} />;\n}\n\n// Force Next.js to produce static pages: https://nextjs.org/docs/app/api-reference/file-conventions/route-segment-config#dynamic\n// Delete this if you need dynamic rendering, such as access to headers or cookies\nexport const dynamic = \"force-static\";\n"
  },
  {
    "path": "recipes/next-ai/app/api/pages/route.ts",
    "content": "import { revalidatePath } from \"next/cache\";\nimport { NextResponse } from \"next/server\";\nimport fs from \"fs\";\n\n// Saves page data to a JSON file, replace this with your own database logic\nexport async function POST(request: Request) {\n  const payload = await request.json();\n\n  const existingData = JSON.parse(\n    fs.existsSync(\"database.json\")\n      ? fs.readFileSync(\"database.json\", \"utf-8\")\n      : \"{}\"\n  );\n\n  const updatedData = {\n    ...existingData,\n    [payload.path]: payload.data,\n  };\n\n  fs.writeFileSync(\"database.json\", JSON.stringify(updatedData));\n\n  // Purge Next.js cache\n  revalidatePath(payload.path);\n\n  return NextResponse.json({ status: \"ok\" });\n}\n"
  },
  {
    "path": "recipes/next-ai/app/api/puck/[...all]/route.ts",
    "content": "import { NextRequest } from \"next/server\";\nimport { puckHandler } from \"@puckeditor/cloud-client\";\n\n// Handles all requests for Puck AI\n// Learn more: https://puckeditor.com/docs/ai/getting-started\nexport const POST = (request: NextRequest) => {\n  return puckHandler(request, {\n    ai: {\n      // Replace with your business context\n      context: \"We are Google. You create Google landing pages.\",\n    },\n  });\n};\n"
  },
  {
    "path": "recipes/next-ai/app/layout.tsx",
    "content": "import \"./styles.css\";\n\nexport default function RootLayout({\n  children,\n}: {\n  children: React.ReactNode;\n}) {\n  return (\n    <html lang=\"en\">\n      <body>{children}</body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "recipes/next-ai/app/page.tsx",
    "content": "export { default, generateMetadata } from \"./[...puckPath]/page\";\n"
  },
  {
    "path": "recipes/next-ai/app/puck/[...puckPath]/client.tsx",
    "content": "\"use client\";\n\nimport type { Data } from \"@puckeditor/core\";\nimport { Puck } from \"@puckeditor/core\";\nimport { createAiPlugin } from \"@puckeditor/plugin-ai\";\n\nimport config from \"../../../puck.config\";\n\nconst aiPlugin = createAiPlugin();\n\nexport function Client({ path, data }: { path: string; data: Partial<Data> }) {\n  return (\n    <Puck\n      plugins={[aiPlugin]}\n      config={config}\n      data={data}\n      onPublish={async (data) => {\n        await fetch(\"/api/pages\", {\n          method: \"post\",\n          body: JSON.stringify({ data, path }),\n        });\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "recipes/next-ai/app/puck/[...puckPath]/page.tsx",
    "content": "/**\n * This file implements a *magic* catch-all route that renders the Puck editor.\n *\n * This route exposes /puck/[...puckPath], but is disabled by middleware.ts. The middleware\n * then rewrites all URL requests ending in `/edit` to this route, allowing you to visit any\n * page in your application and add /edit to the end to spin up a Puck editor.\n *\n * This approach enables public pages to be statically rendered whilst the /puck route can\n * remain dynamic.\n *\n * NB this route is public, and you will need to add authentication\n */\n\nimport \"@puckeditor/core/puck.css\";\nimport \"@puckeditor/plugin-ai/styles.css\";\nimport { Client } from \"./client\";\nimport { Metadata } from \"next\";\nimport { getPage } from \"../../../lib/get-page\";\n\nexport async function generateMetadata({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}): Promise<Metadata> {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n\n  return {\n    title: \"Puck: \" + path,\n  };\n}\n\nexport default async function Page({\n  params,\n}: {\n  params: Promise<{ puckPath: string[] }>;\n}) {\n  const { puckPath = [] } = await params;\n  const path = `/${puckPath.join(\"/\")}`;\n  const data = getPage(path);\n\n  return <Client path={path} data={data || {}} />;\n}\n\nexport const dynamic = \"force-dynamic\";\n"
  },
  {
    "path": "recipes/next-ai/app/puck/page.tsx",
    "content": "export { default, generateMetadata } from \"./[...puckPath]/page\";\n\nexport const dynamic = \"force-dynamic\";\n"
  },
  {
    "path": "recipes/next-ai/app/styles.css",
    "content": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "recipes/next-ai/database.json",
    "content": "{\"/\":{\"content\":[{\"type\":\"HeadingBlock\",\"props\":{\"title\":\"Edit this page by adding /edit to the end of the URL\",\"id\":\"HeadingBlock-1694032984497\"}}],\"root\":{\"props\": {\"title\":\"\"}}}}"
  },
  {
    "path": "recipes/next-ai/lib/get-page.ts",
    "content": "import { Data } from \"@puckeditor/core\";\nimport fs from \"fs\";\n\n// Replace with a call to your database\nexport const getPage = (path: string) => {\n  const allData: Record<string, Data> | null = fs.existsSync(\"database.json\")\n    ? JSON.parse(fs.readFileSync(\"database.json\", \"utf-8\"))\n    : null;\n\n  return allData ? allData[path] : null;\n};\n"
  },
  {
    "path": "recipes/next-ai/next-env.d.ts",
    "content": "/// <reference types=\"next\" />\n/// <reference types=\"next/image-types/global\" />\nimport \"./.next/dev/types/routes.d.ts\";\n\n// NOTE: This file should not be edited\n// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.\n"
  },
  {
    "path": "recipes/next-ai/next.config.js",
    "content": "module.exports = {\n  reactStrictMode: true,\n};\n"
  },
  {
    "path": "recipes/next-ai/package.json",
    "content": "{\n  \"name\": \"next-ai-recipe\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"scripts\": {\n    \"dev\": \"next dev --turbo\",\n    \"build\": \"next build\",\n    \"start\": \"next start\",\n    \"lint\": \"eslint\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"*\",\n    \"@puckeditor/cloud-client\": \"^0.5.0\",\n    \"@puckeditor/plugin-ai\": \"^0.5.0\",\n    \"classnames\": \"^2.3.2\",\n    \"next\": \"^16.0.8\",\n    \"react\": \"^19.2.1\",\n    \"react-dom\": \"^19.2.1\"\n  },\n  \"devDependencies\": {\n    \"@types/node\": \"^17.0.12\",\n    \"@types/react\": \"^19.2.7\",\n    \"@types/react-dom\": \"^19.2.3\",\n    \"eslint-config-custom\": \"*\",\n    \"typescript\": \"^5.5.4\"\n  }\n}\n"
  },
  {
    "path": "recipes/next-ai/proxy.ts",
    "content": "import { NextResponse } from \"next/server\";\n\nimport type { NextRequest } from \"next/server\";\n\nexport async function proxy(req: NextRequest) {\n  const res = NextResponse.next({ request: req });\n\n  if (req.method === \"GET\") {\n    // Rewrite routes that match \"/[...puckPath]/edit\" to \"/puck/[...puckPath]\"\n    if (req.nextUrl.pathname.endsWith(\"/edit\")) {\n      const pathWithoutEdit = req.nextUrl.pathname.slice(\n        0,\n        req.nextUrl.pathname.length - 5\n      );\n      const pathWithEditPrefix = `/puck${pathWithoutEdit}`;\n\n      return NextResponse.rewrite(new URL(pathWithEditPrefix, req.url));\n    }\n\n    // Disable \"/puck/[...puckPath]\"\n    if (req.nextUrl.pathname.startsWith(\"/puck\")) {\n      return NextResponse.redirect(new URL(\"/\", req.url));\n    }\n  }\n\n  return res;\n}\n"
  },
  {
    "path": "recipes/next-ai/puck.config.tsx",
    "content": "import type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: { title: string };\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => (\n        <div style={{ padding: 64 }}>\n          <h1>{title}</h1>\n        </div>\n      ),\n    },\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "recipes/next-ai/tsconfig/base.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Default\",\n  \"compilerOptions\": {\n    \"composite\": false,\n    \"declaration\": true,\n    \"declarationMap\": true,\n    \"esModuleInterop\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"inlineSources\": false,\n    \"isolatedModules\": true,\n    \"moduleResolution\": \"node\",\n    \"noUnusedLocals\": false,\n    \"noUnusedParameters\": false,\n    \"preserveWatchOutput\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  },\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "recipes/next-ai/tsconfig/nextjs.json",
    "content": "{\n  \"$schema\": \"https://json.schemastore.org/tsconfig\",\n  \"display\": \"Next.js\",\n  \"extends\": \"./base.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }],\n    \"allowJs\": true,\n    \"declaration\": false,\n    \"declarationMap\": false,\n    \"incremental\": true,\n    \"jsx\": \"preserve\",\n    \"lib\": [\"dom\", \"dom.iterable\", \"esnext\"],\n    \"module\": \"esnext\",\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"strict\": false,\n    \"target\": \"es5\"\n  },\n  \"include\": [\"src\", \"next-env.d.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "recipes/next-ai/tsconfig.json",
    "content": "{\n  \"extends\": \"./tsconfig/nextjs.json\",\n  \"compilerOptions\": {\n    \"plugins\": [{ \"name\": \"next\" }]\n  },\n  \"include\": [\"next-env.d.ts\", \"**/*.ts\", \"**/*.tsx\", \".next/types/**/*.ts\"],\n  \"exclude\": [\"node_modules\"]\n}\n"
  },
  {
    "path": "recipes/react-router/.gitignore",
    "content": ".DS_Store\n/node_modules/\n\n# React Router\n/.react-router/\n/build/\n"
  },
  {
    "path": "recipes/react-router/README.md",
    "content": "# `react-router` recipe\n\nThe `react-router` recipe showcases one of the most powerful ways to implement Puck using to provide an authoring tool for any route in your React Router app.\n\n## Demonstrates\n\n- React Router v7 (framework) implementation\n- JSON database implementation\n- Splat route to use puck for any route on the platform\n\n## Usage\n\nRun the generator and enter `react-router` when prompted\n\n```\nnpx create-puck-app my-app\n```\n\nStart the server\n\n```\nyarn dev\n```\n\nNavigate to the homepage at http://localhost:5173/. To edit the homepage, access the Puck editor at http://localhost:5173/edit.\n\nYou can do this for any **base** route on the application, **even if the page doesn't exist**. For example, visit http://localhost:5173/hello-world and you'll receive a 404. You can author and publish a page by visiting http://localhost:5173/hello-world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe, you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the [route module action](https://reactrouter.com/start/framework/route-module#action) in the splat route `/app/routes/puck-splat.tsx`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the functions in `/lib/pages.server.ts`\n- Implement a custom puck configuration in `/app/puck.config.tsx`\n"
  },
  {
    "path": "recipes/react-router/app/components/puck-render.tsx",
    "content": "import type { Data } from \"@puckeditor/core\";\nimport { Render } from \"@puckeditor/core\";\n\nimport { config } from \"../../puck.config\";\n\nexport function PuckRender({ data }: { data: Data }) {\n  return <Render config={config} data={data} />;\n}\n"
  },
  {
    "path": "recipes/react-router/app/lib/pages.server.ts",
    "content": "import path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport fs from \"fs/promises\";\nimport type { Data } from \"@puckeditor/core\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst databasePath = path.join(__dirname, \"..\", \"..\", \"database.json\");\n\nexport async function getPage(path: string) {\n  const pages = await readDatabase();\n  return pages[path];\n}\n\nexport async function savePage(path: string, data: Data) {\n  const pages = await readDatabase();\n  pages[path] = data;\n  await fs.writeFile(databasePath, JSON.stringify(pages), { encoding: \"utf8\" });\n}\n\nasync function readDatabase() {\n  try {\n    const file = await fs.readFile(databasePath, \"utf8\");\n    return JSON.parse(file) as Record<string, Data>;\n  } catch (error: unknown) {\n    console.error(error);\n    return {};\n  }\n}\n"
  },
  {
    "path": "recipes/react-router/app/lib/resolve-puck-path.server.ts",
    "content": "export function resolvePuckPath(\n  path = \"\",\n  // `base` can be any valid origin, it is required for the URL constructor so\n  // we can return a pathname - you can change this if you want, but it isn't\n  // important\n  base = \"https://placeholder.com/\"\n) {\n  const url = new URL(path, base);\n  const segments = url.pathname.split(\"/\");\n  const isEditorRoute = segments.at(-1) === \"edit\";\n  const pathname = isEditorRoute\n    ? segments.slice(0, -1).join(\"/\")\n    : url.pathname;\n\n  return {\n    isEditorRoute,\n    path: new URL(pathname, base).pathname,\n  };\n}\n"
  },
  {
    "path": "recipes/react-router/app/root.tsx",
    "content": "import {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"react-router\";\n\nimport type { Route } from \"./+types/root\";\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\nexport default function App() {\n  return <Outlet />;\n}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = \"Oops!\";\n  let details = \"An unexpected error occurred.\";\n  let stack: string | undefined;\n\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? \"404\" : \"Error\";\n    details =\n      error.status === 404\n        ? \"The requested page could not be found.\"\n        : error.statusText || details;\n  } else if (\n    import.meta.env.NODE_ENV !== \"production\" &&\n    error &&\n    error instanceof Error\n  ) {\n    details = error.message;\n    stack = error.stack;\n  }\n\n  return (\n    <main>\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre>\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  );\n}\n"
  },
  {
    "path": "recipes/react-router/app/routes/_index.tsx",
    "content": "import type { Route } from \"./+types/_index\";\nimport { PuckRender } from \"~/components/puck-render\";\nimport { resolvePuckPath } from \"~/lib/resolve-puck-path.server\";\nimport { getPage } from \"~/lib/pages.server\";\n\nexport async function loader() {\n  const { isEditorRoute, path } = resolvePuckPath(\"/\");\n  let page = await getPage(path);\n\n  if (!page) {\n    throw new Response(\"Not Found\", { status: 404 });\n  }\n\n  return {\n    isEditorRoute,\n    path,\n    data: page,\n  };\n}\n\nexport function meta({ data: loaderData }: Route.MetaArgs) {\n  return [\n    {\n      title: loaderData.data.root.props?.title ?? \"\",\n    },\n  ];\n}\n\nexport default function PuckSplatRoute({ loaderData }: Route.ComponentProps) {\n  return <PuckRender data={loaderData.data} />;\n}\n"
  },
  {
    "path": "recipes/react-router/app/routes/puck-splat.tsx",
    "content": "import { useFetcher, useLoaderData } from \"react-router\";\nimport type { Data } from \"@puckeditor/core\";\nimport { Puck, Render } from \"@puckeditor/core\";\n\nimport type { Route } from \"./+types/puck-splat\";\nimport { config } from \"../../puck.config\";\nimport { resolvePuckPath } from \"~/lib/resolve-puck-path.server\";\nimport { getPage, savePage } from \"~/lib/pages.server\";\nimport editorStyles from \"@puckeditor/core/puck.css?url\";\n\nexport async function loader({ params }: Route.LoaderArgs) {\n  const pathname = params[\"*\"] ?? \"/\";\n  const { isEditorRoute, path } = resolvePuckPath(pathname);\n  let page = await getPage(path);\n\n  // Throw a 404 if we're not rendering the editor and data for the page does not exist\n  if (!isEditorRoute && !page) {\n    throw new Response(\"Not Found\", { status: 404 });\n  }\n\n  // Empty shell for new pages\n  if (isEditorRoute && !page) {\n    page = {\n      content: [],\n      root: {\n        props: {\n          title: \"\",\n        },\n      },\n    };\n  }\n\n  return {\n    isEditorRoute,\n    path,\n    data: page,\n  };\n}\n\nexport function meta({ data: loaderData }: Route.MetaArgs) {\n  return [\n    {\n      title: loaderData.isEditorRoute\n        ? `Edit: ${loaderData.path}`\n        : loaderData.data.root.props?.title ?? \"\",\n    },\n  ];\n}\n\nexport async function action({ params, request }: Route.ActionArgs) {\n  const pathname = params[\"*\"] ?? \"/\";\n  const { path } = resolvePuckPath(pathname);\n  const body = (await request.json()) as { data: Data };\n\n  await savePage(path, body.data);\n}\n\nfunction Editor() {\n  const loaderData = useLoaderData<typeof loader>();\n  const fetcher = useFetcher<typeof action>();\n\n  return (\n    <>\n      <link rel=\"stylesheet\" href={editorStyles} id=\"puck-css\" />\n      <Puck\n        config={config}\n        data={loaderData.data}\n        onPublish={async (data) => {\n          await fetcher.submit(\n            {\n              data,\n            },\n            {\n              action: \"\",\n              method: \"post\",\n              encType: \"application/json\",\n            }\n          );\n        }}\n      />\n    </>\n  );\n}\n\nexport default function PuckSplatRoute({ loaderData }: Route.ComponentProps) {\n  return (\n    <div>\n      {loaderData.isEditorRoute ? (\n        <Editor />\n      ) : (\n        <Render config={config} data={loaderData.data} />\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "recipes/react-router/app/routes.ts",
    "content": "import type { RouteConfig } from \"@react-router/dev/routes\";\nimport { route, index } from \"@react-router/dev/routes\";\n\nexport default [\n  index(\"routes/_index.tsx\"),\n  route(\"*\", \"routes/puck-splat.tsx\"),\n] satisfies RouteConfig;\n"
  },
  {
    "path": "recipes/react-router/database.json",
    "content": "{\"/\":{\"content\":[{\"type\":\"HeadingBlock\",\"props\":{\"title\":\"Edit this page by adding /edit to the end of the URL\",\"id\":\"HeadingBlock-1694032984497\"}}],\"root\":{\"props\":{\"title\":\"Puck + React Router 7 demo\"}},\"zones\":{}}}"
  },
  {
    "path": "recipes/react-router/package.json",
    "content": "{\n  \"name\": \"react-router-recipe\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev\",\n    \"start\": \"react-router-serve ./build/server/index.js\",\n    \"typecheck\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"*\",\n    \"@react-router/node\": \"^7.5.3\",\n    \"@react-router/serve\": \"^7.5.3\",\n    \"isbot\": \"^5\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-router\": \"^7.5.3\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.5.3\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.0.1\",\n    \"@types/react-dom\": \"^19.0.2\",\n    \"typescript\": \"^5.8.3\",\n    \"vite\": \"^6.3.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "recipes/react-router/puck.config.tsx",
    "content": "import type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: { title: string };\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => (\n        <div style={{ padding: 64 }}>\n          <h1>{title}</h1>\n        </div>\n      ),\n    },\n  },\n};\n"
  },
  {
    "path": "recipes/react-router/react-router.config.ts",
    "content": "import type { Config } from \"@react-router/dev/config\";\n\nexport default {\n  // Config options...\n  // Server-side render by default, to enable SPA mode set this to `false`\n  ssr: true,\n} satisfies Config;\n"
  },
  {
    "path": "recipes/react-router/tsconfig.json",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"],\n      \"@puckeditor/core\": [\"../../packages/core/index.ts\"],\n      \"@puckeditor/core/puck.css\": [\"../../packages/core/bundle/index.css\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "recipes/react-router/vite.config.ts",
    "content": "import { reactRouter } from \"@react-router/dev/vite\";\nimport { defineConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [reactRouter(), tsconfigPaths()],\n});\n"
  },
  {
    "path": "recipes/react-router-ai/.gitignore",
    "content": ".DS_Store\n/node_modules/\n\n# React Router\n/.react-router/\n/build/\n"
  },
  {
    "path": "recipes/react-router-ai/README.md",
    "content": "# `react-router-ai` recipe\n\nThe `react-router-ai` recipe showcases one of the most powerful ways to combine Puck and [Puck AI](https://puckeditor.com/docs/ai/overview): providing an authoring tool with AI page generation capabilities for any route in your React Router app.\n\n## Demonstrates\n\n- Puck AI integration for generating pages with AI\n- React Router v7 (framework) implementation\n- JSON database implementation\n- Splat route to use puck for any route on the platform\n\n## Usage\n\nRun the generator and select `React Router` when prompted\n\n```\nnpx create-puck-app my-app\n\n? Which recipe would you like to use?\n❯ React Router\n```\n\nConfirm you want to use Puck AI\n\n```\n? Would you like to use Puck AI? (Y/n) Y\n```\n\nStart the server\n\n```\ncd my-app\nyarn dev\n```\n\n### Set up Puck AI\n\nCreate a [Puck account](https://cloud.puckeditor.com) and [obtain an API key](https://cloud.puckeditor.com/api-keys).\n\nCreate a `.env.local` file in the root of your project and add your API key:\n\n```\nPUCK_API_KEY=your-api-key\n```\n\nNavigate to the homepage at https://localhost:3000. To edit the homepage, access the Puck editor at https://localhost:3000/edit, and select the AI button in the left navigation bar to generate content for the page using Puck AI.\n\nYou can do this for any route on the application, **even if the page doesn't exist**. For example, visit https://localhost:3000/hello/world and you'll receive a 404. You can author and publish a page by visiting https://localhost:3000/hello/world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe, you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the [route module action](https://reactrouter.com/start/framework/route-module#action) in the splat route `/app/routes/puck-splat.tsx`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the functions in `/lib/pages.server.ts`\n- Implement a custom puck configuration in `/app/puck.config.tsx`\n- Add business context for the AI generation in `/app/routes/api.puck.ts`\n"
  },
  {
    "path": "recipes/react-router-ai/app/components/puck-render.tsx",
    "content": "import type { Data } from \"@puckeditor/core\";\nimport { Render } from \"@puckeditor/core\";\n\nimport { config } from \"../../puck.config\";\n\nexport function PuckRender({ data }: { data: Data }) {\n  return <Render config={config} data={data} />;\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/lib/pages.server.ts",
    "content": "import path from \"path\";\nimport { fileURLToPath } from \"url\";\nimport fs from \"fs/promises\";\nimport type { Data } from \"@puckeditor/core\";\n\nconst __filename = fileURLToPath(import.meta.url);\nconst __dirname = path.dirname(__filename);\nconst databasePath = path.join(__dirname, \"..\", \"..\", \"database.json\");\n\nexport async function getPage(path: string) {\n  const pages = await readDatabase();\n  return pages[path];\n}\n\nexport async function savePage(path: string, data: Data) {\n  const pages = await readDatabase();\n  pages[path] = data;\n  await fs.writeFile(databasePath, JSON.stringify(pages), { encoding: \"utf8\" });\n}\n\nasync function readDatabase() {\n  try {\n    const file = await fs.readFile(databasePath, \"utf8\");\n    return JSON.parse(file) as Record<string, Data>;\n  } catch (error: unknown) {\n    console.error(error);\n    return {};\n  }\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/lib/resolve-puck-path.server.ts",
    "content": "export function resolvePuckPath(\n  path = \"\",\n  // `base` can be any valid origin, it is required for the URL constructor so\n  // we can return a pathname - you can change this if you want, but it isn't\n  // important\n  base = \"https://placeholder.com/\"\n) {\n  const url = new URL(path, base);\n  const segments = url.pathname.split(\"/\");\n  const isEditorRoute = segments.at(-1) === \"edit\";\n  const pathname = isEditorRoute\n    ? segments.slice(0, -1).join(\"/\")\n    : url.pathname;\n\n  return {\n    isEditorRoute,\n    path: new URL(pathname, base).pathname,\n  };\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/root.tsx",
    "content": "import {\n  isRouteErrorResponse,\n  Links,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"react-router\";\n\nimport type { Route } from \"./+types/root\";\n\nexport function Layout({ children }: { children: React.ReactNode }) {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        {children}\n        <ScrollRestoration />\n        <Scripts />\n      </body>\n    </html>\n  );\n}\n\nexport default function App() {\n  return <Outlet />;\n}\n\nexport function ErrorBoundary({ error }: Route.ErrorBoundaryProps) {\n  let message = \"Oops!\";\n  let details = \"An unexpected error occurred.\";\n  let stack: string | undefined;\n\n  if (isRouteErrorResponse(error)) {\n    message = error.status === 404 ? \"404\" : \"Error\";\n    details =\n      error.status === 404\n        ? \"The requested page could not be found.\"\n        : error.statusText || details;\n  } else if (\n    import.meta.env.NODE_ENV !== \"production\" &&\n    error &&\n    error instanceof Error\n  ) {\n    details = error.message;\n    stack = error.stack;\n  }\n\n  return (\n    <main>\n      <h1>{message}</h1>\n      <p>{details}</p>\n      {stack && (\n        <pre>\n          <code>{stack}</code>\n        </pre>\n      )}\n    </main>\n  );\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/routes/_index.tsx",
    "content": "import type { Route } from \"./+types/_index\";\nimport { PuckRender } from \"~/components/puck-render\";\nimport { resolvePuckPath } from \"~/lib/resolve-puck-path.server\";\nimport { getPage } from \"~/lib/pages.server\";\n\nexport async function loader() {\n  const { isEditorRoute, path } = resolvePuckPath(\"/\");\n  let page = await getPage(path);\n\n  if (!page) {\n    throw new Response(\"Not Found\", { status: 404 });\n  }\n\n  return {\n    isEditorRoute,\n    path,\n    data: page,\n  };\n}\n\nexport function meta({ data: loaderData }: Route.MetaArgs) {\n  return [\n    {\n      title: loaderData.data.root.props?.title ?? \"\",\n    },\n  ];\n}\n\nexport default function PuckSplatRoute({ loaderData }: Route.ComponentProps) {\n  return <PuckRender data={loaderData.data} />;\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/routes/api.puck.ts",
    "content": "import type { ActionFunctionArgs } from \"react-router\";\nimport { puckHandler } from \"@puckeditor/cloud-client\";\n\n// Handles all requests for Puck AI\n// Learn more: https://puckeditor.com/docs/ai/getting-started\nexport async function action(args: ActionFunctionArgs) {\n  return puckHandler(args.request, {\n    ai: {\n      // Replace with your business context\n      context: \"We are Google. You create Google landing pages.\",\n    },\n  });\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/routes/puck-splat.tsx",
    "content": "import { useFetcher, useLoaderData } from \"react-router\";\nimport type { Data } from \"@puckeditor/core\";\nimport { Puck, Render } from \"@puckeditor/core\";\nimport { createAiPlugin } from \"@puckeditor/plugin-ai\";\n\nimport type { Route } from \"./+types/puck-splat\";\nimport { config } from \"../../puck.config\";\nimport { resolvePuckPath } from \"~/lib/resolve-puck-path.server\";\nimport { getPage, savePage } from \"~/lib/pages.server\";\nimport editorStyles from \"@puckeditor/core/puck.css?url\";\nimport pluginStyles from \"@puckeditor/plugin-ai/styles.css?url\";\n\nexport async function loader({ params }: Route.LoaderArgs) {\n  const pathname = params[\"*\"] ?? \"/\";\n  const { isEditorRoute, path } = resolvePuckPath(pathname);\n  let page = await getPage(path);\n\n  // Throw a 404 if we're not rendering the editor and data for the page does not exist\n  if (!isEditorRoute && !page) {\n    throw new Response(\"Not Found\", { status: 404 });\n  }\n\n  // Empty shell for new pages\n  if (isEditorRoute && !page) {\n    page = {\n      content: [],\n      root: {\n        props: {\n          title: \"\",\n        },\n      },\n    };\n  }\n\n  return {\n    isEditorRoute,\n    path,\n    data: page,\n  };\n}\n\nexport function meta({ data: loaderData }: Route.MetaArgs) {\n  return [\n    {\n      title: loaderData.isEditorRoute\n        ? `Edit: ${loaderData.path}`\n        : loaderData.data.root.props?.title ?? \"\",\n    },\n  ];\n}\n\nexport async function action({ params, request }: Route.ActionArgs) {\n  const pathname = params[\"*\"] ?? \"/\";\n  const { path } = resolvePuckPath(pathname);\n  const body = (await request.json()) as { data: Data };\n\n  await savePage(path, body.data);\n}\n\nconst aiPlugin = createAiPlugin();\n\nfunction Editor() {\n  const loaderData = useLoaderData<typeof loader>();\n  const fetcher = useFetcher<typeof action>();\n\n  return (\n    <>\n      <link rel=\"stylesheet\" href={editorStyles} id=\"puck-css\" />\n      <link rel=\"stylesheet\" href={pluginStyles} id=\"puck-plugin-ai-css\" />\n      <Puck\n        plugins={[aiPlugin]}\n        config={config}\n        data={loaderData.data}\n        onPublish={async (data) => {\n          await fetcher.submit(\n            {\n              data,\n            },\n            {\n              action: \"\",\n              method: \"post\",\n              encType: \"application/json\",\n            }\n          );\n        }}\n      />\n    </>\n  );\n}\n\nexport default function PuckSplatRoute({ loaderData }: Route.ComponentProps) {\n  return (\n    <div>\n      {loaderData.isEditorRoute ? (\n        <Editor />\n      ) : (\n        <Render config={config} data={loaderData.data} />\n      )}\n    </div>\n  );\n}\n"
  },
  {
    "path": "recipes/react-router-ai/app/routes.ts",
    "content": "import type { RouteConfig } from \"@react-router/dev/routes\";\nimport { route, index } from \"@react-router/dev/routes\";\n\nexport default [\n  index(\"routes/_index.tsx\"),\n  route(\"api/puck/*\", \"routes/api.puck.ts\"),\n  route(\"*\", \"routes/puck-splat.tsx\"),\n] satisfies RouteConfig;\n"
  },
  {
    "path": "recipes/react-router-ai/database.json",
    "content": "{\"/\":{\"content\":[{\"type\":\"HeadingBlock\",\"props\":{\"title\":\"Edit this page by adding /edit to the end of the URL\",\"id\":\"HeadingBlock-1694032984497\"}}],\"root\":{\"props\":{\"title\":\"Puck + React Router 7 demo\"}},\"zones\":{}}}"
  },
  {
    "path": "recipes/react-router-ai/package.json",
    "content": "{\n  \"name\": \"react-router-ai-recipe\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"version\": \"1.0.0\",\n  \"scripts\": {\n    \"build\": \"react-router build\",\n    \"dev\": \"react-router dev\",\n    \"start\": \"react-router-serve ./build/server/index.js\",\n    \"typecheck\": \"react-router typegen && tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"*\",\n    \"@puckeditor/cloud-client\": \"^0.5.0\",\n    \"@puckeditor/plugin-ai\": \"^0.5.0\",\n    \"@react-router/node\": \"^7.5.3\",\n    \"@react-router/serve\": \"^7.5.3\",\n    \"isbot\": \"^5\",\n    \"react\": \"^19.0.0\",\n    \"react-dom\": \"^19.0.0\",\n    \"react-router\": \"^7.5.3\"\n  },\n  \"devDependencies\": {\n    \"@react-router/dev\": \"^7.5.3\",\n    \"@types/node\": \"^20\",\n    \"@types/react\": \"^19.0.1\",\n    \"@types/react-dom\": \"^19.0.2\",\n    \"typescript\": \"^5.8.3\",\n    \"vite\": \"^6.3.3\",\n    \"vite-tsconfig-paths\": \"^5.1.4\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "recipes/react-router-ai/puck.config.tsx",
    "content": "import type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: { title: string };\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => (\n        <div style={{ padding: 64 }}>\n          <h1>{title}</h1>\n        </div>\n      ),\n    },\n  },\n};\n"
  },
  {
    "path": "recipes/react-router-ai/react-router.config.ts",
    "content": "import type { Config } from \"@react-router/dev/config\";\n\nexport default {\n  // Config options...\n  // Server-side render by default, to enable SPA mode set this to `false`\n  ssr: true,\n} satisfies Config;\n"
  },
  {
    "path": "recipes/react-router-ai/tsconfig.json",
    "content": "{\n  \"include\": [\n    \"**/*\",\n    \"**/.server/**/*\",\n    \"**/.client/**/*\",\n    \".react-router/types/**/*\"\n  ],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"types\": [\"node\", \"vite/client\"],\n    \"target\": \"ES2022\",\n    \"module\": \"ES2022\",\n    \"moduleResolution\": \"bundler\",\n    \"jsx\": \"react-jsx\",\n    \"rootDirs\": [\".\", \"./.react-router/types\"],\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"],\n      \"@puckeditor/core\": [\"../../packages/core/index.ts\"],\n      \"@puckeditor/core/puck.css\": [\"../../packages/core/bundle/index.css\"]\n    },\n    \"esModuleInterop\": true,\n    \"verbatimModuleSyntax\": true,\n    \"noEmit\": true,\n    \"resolveJsonModule\": true,\n    \"skipLibCheck\": true,\n    \"strict\": true\n  }\n}\n"
  },
  {
    "path": "recipes/react-router-ai/vite.config.ts",
    "content": "import { reactRouter } from \"@react-router/dev/vite\";\nimport { defineConfig } from \"vite\";\nimport tsconfigPaths from \"vite-tsconfig-paths\";\n\nexport default defineConfig({\n  plugins: [reactRouter(), tsconfigPaths()],\n});\n"
  },
  {
    "path": "recipes/remix/.eslintrc.cjs",
    "content": "/** @type {import('eslint').Linter.Config} */\nmodule.exports = {\n  extends: [\"@remix-run/eslint-config\", \"@remix-run/eslint-config/node\"],\n};\n"
  },
  {
    "path": "recipes/remix/.gitignore",
    "content": "node_modules\n\n/.cache\n/build\n/public/build\n.env\n"
  },
  {
    "path": "recipes/remix/README.md",
    "content": "# `remix` recipe\n\nThe `remix` recipe showcases a Remix Run app with Puck, using it to provide an authoring tool for any root-level route in your Remix app.\n\n## Demonstrates\n\n- Remix Run V2 implementation\n- JSON database implementation with HTTP API\n- Dynamic routes to use puck for any root-level route on the platform\n- Option to disable client-side JavaScript for Puck pages\n\n## Usage\n\nRun the generator and enter `next` when prompted\n\n```\nnpx create-puck-app my-app\n```\n\nStart the server\n\n```\nyarn dev\n```\n\nNavigate to the homepage at https://localhost:3000. To edit the homepage, access the Puck editor at https://localhost:3000/edit.\n\nYou can do this for any **base** route on the application, **even if the page doesn't exist**. For example, visit https://localhost:3000/hello-world and you'll receive a 404. You can author and publish a page by visiting https://localhost:3000/hello-world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the example routes `/app/routes/_index.tsx` and `/app/routes/edit.tsx` or the example model in `/app/models/page.server.ts`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the API calls in `/app/models/page.server.ts`\n- Implement a custom puck configuration in `/app/puck.config.tsx`\n\nBy default, this recipe will have JavaScript enable on all routes - like a usual react app. If you know that your Puck content doesn't need react, then you can disable JS uncommenting the relevant code in `/app/root.tsx` and the example route `/app/routes/_index.tsx`. Check the network tab for no JS downloads, and verify that the page still works.\n\n## Disabling JavaScript\n\nThis recipe can be adapted to disable JavaScript. See the [Remix docs](https://remix.run/docs/en/main/guides/disabling-javascript) for steps on how to do this.\n"
  },
  {
    "path": "recipes/remix/app/entry.client.tsx",
    "content": "/**\n * By default, Remix will handle hydrating your app on the client for you.\n * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨\n * For more information, see https://remix.run/file-conventions/entry.client\n */\n\nimport { RemixBrowser } from \"@remix-run/react\";\nimport { startTransition, StrictMode } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\n\nstartTransition(() => {\n  hydrateRoot(\n    document,\n    <StrictMode>\n      <RemixBrowser />\n    </StrictMode>\n  );\n});\n"
  },
  {
    "path": "recipes/remix/app/entry.server.tsx",
    "content": "/**\n * By default, Remix will handle generating the HTTP Response for you.\n * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨\n * For more information, see https://remix.run/file-conventions/entry.server\n */\n\nimport { PassThrough } from \"node:stream\";\n\nimport type { AppLoadContext, EntryContext } from \"@remix-run/node\";\nimport { createReadableStreamFromReadable } from \"@remix-run/node\";\nimport { RemixServer } from \"@remix-run/react\";\nimport isbot from \"isbot\";\nimport { renderToPipeableStream } from \"react-dom/server\";\n\nconst ABORT_DELAY = 5_000;\n\nexport default function handleRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext,\n  loadContext: AppLoadContext\n) {\n  return isbot(request.headers.get(\"user-agent\"))\n    ? handleBotRequest(\n        request,\n        responseStatusCode,\n        responseHeaders,\n        remixContext\n      )\n    : handleBrowserRequest(\n        request,\n        responseStatusCode,\n        responseHeaders,\n        remixContext\n      );\n}\n\nfunction handleBotRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  return new Promise((resolve, reject) => {\n    let shellRendered = false;\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n        abortDelay={ABORT_DELAY}\n      />,\n      {\n        onAllReady() {\n          shellRendered = true;\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n\n          pipe(body);\n        },\n        onShellError(error: unknown) {\n          reject(error);\n        },\n        onError(error: unknown) {\n          responseStatusCode = 500;\n          // Log streaming rendering errors from inside the shell.  Don't log\n          // errors encountered during initial shell rendering since they'll\n          // reject and get logged in handleDocumentRequest.\n          if (shellRendered) {\n            console.error(error);\n          }\n        },\n      }\n    );\n\n    setTimeout(abort, ABORT_DELAY);\n  });\n}\n\nfunction handleBrowserRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  return new Promise((resolve, reject) => {\n    let shellRendered = false;\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n        abortDelay={ABORT_DELAY}\n      />,\n      {\n        onShellReady() {\n          shellRendered = true;\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n\n          pipe(body);\n        },\n        onShellError(error: unknown) {\n          reject(error);\n        },\n        onError(error: unknown) {\n          responseStatusCode = 500;\n          // Log streaming rendering errors from inside the shell.  Don't log\n          // errors encountered during initial shell rendering since they'll\n          // reject and get logged in handleDocumentRequest.\n          if (shellRendered) {\n            console.error(error);\n          }\n        },\n      }\n    );\n\n    setTimeout(abort, ABORT_DELAY);\n  });\n}\n"
  },
  {
    "path": "recipes/remix/app/models/page.server.ts",
    "content": "import { Data } from \"@puckeditor/core\";\nimport fs from \"fs\";\n\n// Replace with call to your database\nexport const getPage = (path: string) => {\n  const allData: Record<string, Data> | null = fs.existsSync(\"database.json\")\n    ? JSON.parse(fs.readFileSync(\"database.json\", \"utf-8\"))\n    : null;\n\n  return allData ? allData[path] : null;\n};\n\n// Replace with call to your database\nexport const setPage = (path: string, data: Data) => {\n  const existingData = JSON.parse(\n    fs.existsSync(\"database.json\")\n      ? fs.readFileSync(\"database.json\", \"utf-8\")\n      : \"{}\"\n  );\n\n  const updatedData = {\n    ...existingData,\n    [path]: data,\n  };\n\n  fs.writeFileSync(\"database.json\", JSON.stringify(updatedData));\n};\n"
  },
  {
    "path": "recipes/remix/app/puck.config.tsx",
    "content": "import type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: { title: string };\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => (\n        <div style={{ padding: 64 }}>\n          <h1>{title}</h1>\n        </div>\n      ),\n    },\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "recipes/remix/app/root.tsx",
    "content": "import { cssBundleHref } from \"@remix-run/css-bundle\";\nimport type { LinksFunction } from \"@remix-run/node\";\nimport {\n  Links,\n  LiveReload,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"@remix-run/react\";\n\nimport styles from \"~/styles/shared.css\";\n\nexport const links: LinksFunction = () => [\n  ...(cssBundleHref ? [{ rel: \"stylesheet\", href: cssBundleHref }] : []),\n  { rel: \"stylesheet\", href: styles },\n];\n\nexport default function App() {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        <Outlet />\n        <ScrollRestoration />\n        <Scripts />\n        <LiveReload />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "recipes/remix/app/routes/$puckPath.tsx",
    "content": "export { default } from \"./_index\";\nexport * from \"./_index\";\n"
  },
  {
    "path": "recipes/remix/app/routes/$puckPath_.edit.tsx",
    "content": "export { default } from \"./edit\";\n// I think a bug in remix means loader needs to be explicitly exported here\nexport { action, loader } from \"./edit\";\n// For meta and links etc.\nexport * from \"./edit\";\n"
  },
  {
    "path": "recipes/remix/app/routes/_index.tsx",
    "content": "import { Render, type Config } from \"@puckeditor/core\";\nimport type { LoaderFunctionArgs, MetaFunction } from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\n\nimport puckConfig from \"~/puck.config\";\nimport { getPage } from \"~/models/page.server\";\n\nexport const loader = async ({ params }: LoaderFunctionArgs) => {\n  // Get path, and default to slash for root path.\n  const puckPath = params.puckPath || \"/\";\n  // Get puckData for this path, this could be a database call.\n  const puckData = getPage(puckPath);\n  if (!puckData) {\n    throw new Response(null, {\n      status: 404,\n      statusText: \"Not Found\",\n    });\n  }\n  // Return the data.\n  return json({ puckData });\n};\n\nexport const meta: MetaFunction<typeof loader> = ({ data }) => {\n  const title = data?.puckData?.root?.props?.title || \"Page\";\n\n  return [{ title }];\n};\n\nexport default function Page() {\n  const { puckData } = useLoaderData<typeof loader>();\n\n  return <Render config={puckConfig as Config} data={puckData} />;\n}\n"
  },
  {
    "path": "recipes/remix/app/routes/edit.tsx",
    "content": "import { Puck, type Data, type Config } from \"@puckeditor/core\";\nimport styles from \"@puckeditor/core/puck.css\";\nimport type {\n  ActionFunctionArgs,\n  LinksFunction,\n  LoaderFunctionArgs,\n  MetaFunction,\n} from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { useLoaderData, useSubmit } from \"@remix-run/react\";\nimport invariant from \"tiny-invariant\";\n\nimport puckConfig from \"~/puck.config\";\nimport { getPage, setPage } from \"~/models/page.server\";\n\nexport const action = async ({ params, request }: ActionFunctionArgs) => {\n  const puckPath = params.puckPath || \"/\";\n  const formData = await request.formData();\n  const puckData = formData.get(\"puckData\");\n\n  invariant(puckData, \"Missing data\");\n  invariant(typeof puckData === \"string\", \"Invalid data\");\n\n  setPage(puckPath, JSON.parse(puckData));\n\n  return json({ ok: true });\n};\n\nexport const links: LinksFunction = () => [\n  { rel: \"stylesheet\", href: styles, id: \"puck-css\" },\n];\n\nexport const loader = async ({ params }: LoaderFunctionArgs) => {\n  const puckPath = params.puckPath || \"/\";\n  const initialData = getPage(puckPath) || {\n    content: [],\n    root: {},\n  };\n  return json({ puckPath, initialData });\n};\n\nexport const meta: MetaFunction<typeof loader> = ({ data }) => {\n  const title = data?.initialData?.root?.props?.title || \"Untitled page\";\n\n  return [{ title: `Editing: ${title}` }];\n};\n\nexport default function Edit() {\n  const { initialData } = useLoaderData<typeof loader>();\n  const submit = useSubmit();\n\n  return (\n    <Puck\n      config={puckConfig as Config}\n      data={initialData}\n      onPublish={async (data: Data) => {\n        // Use form data here because it's the usual remix way.\n        let formData = new FormData();\n        formData.append(\"puckData\", JSON.stringify(data));\n        submit(formData, { method: \"post\" });\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "recipes/remix/app/styles/shared.css",
    "content": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "recipes/remix/database.json",
    "content": "{\"/\":{\"content\":[{\"type\":\"HeadingBlock\",\"props\":{\"title\":\"Edit this page by adding /edit to the end of the URL\",\"id\":\"HeadingBlock-1694032984497\"}}],\"root\":{\"props\": {\"title\":\"\"}}}}"
  },
  {
    "path": "recipes/remix/package.json",
    "content": "{\n  \"name\": \"remix-recipe\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"remix build\",\n    \"dev\": \"remix dev --manual\",\n    \"start\": \"remix-serve ./build/index.js\",\n    \"typecheck\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"*\",\n    \"@remix-run/css-bundle\": \"^2.2.0\",\n    \"@remix-run/node\": \"^2.2.0\",\n    \"@remix-run/react\": \"^2.2.0\",\n    \"@remix-run/serve\": \"^2.2.0\",\n    \"isbot\": \"^3.6.8\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"tiny-invariant\": \"^1.3.3\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/dev\": \"^2.2.0\",\n    \"@remix-run/eslint-config\": \"^2.2.0\",\n    \"@types/react\": \"^18.2.20\",\n    \"@types/react-dom\": \"^18.2.7\",\n    \"eslint\": \"^8.38.0\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "recipes/remix/remix.config.js",
    "content": "/** @type {import('@remix-run/dev').AppConfig} */\nexport default {\n  ignoredRouteFiles: [\"**/.*\"],\n  // appDirectory: \"app\",\n  // assetsBuildDirectory: \"public/build\",\n  // publicPath: \"/build/\",\n  // serverBuildPath: \"build/index.js\",\n  /**\n   * @see https://github.com/puckeditor/puck/issues/112\n   */\n  browserNodeBuiltinsPolyfill: { modules: { crypto: true } },\n};\n"
  },
  {
    "path": "recipes/remix/remix.env.d.ts",
    "content": "/// <reference types=\"@remix-run/dev\" />\n/// <reference types=\"@remix-run/node\" />\n"
  },
  {
    "path": "recipes/remix/tsconfig.json",
    "content": "{\n  \"include\": [\"remix.env.d.ts\", \"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react-jsx\",\n    \"moduleResolution\": \"Bundler\",\n    \"resolveJsonModule\": true,\n    \"target\": \"ES2022\",\n    \"strict\": true,\n    \"allowJs\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n\n    // Remix takes care of building everything in `remix build`.\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "recipes/remix-ai/.eslintrc.cjs",
    "content": "/** @type {import('eslint').Linter.Config} */\nmodule.exports = {\n  extends: [\"@remix-run/eslint-config\", \"@remix-run/eslint-config/node\"],\n};\n"
  },
  {
    "path": "recipes/remix-ai/.gitignore",
    "content": "node_modules\n\n/.cache\n/build\n/public/build\n.env\n"
  },
  {
    "path": "recipes/remix-ai/README.md",
    "content": "# `remix-ai` recipe\n\nThe `remix-ai` recipe showcases one of the most powerful ways to combine Puck and [Puck AI](https://puckeditor.com/docs/ai/overview): providing an authoring tool with AI page generation capabilities for any route in your Remix app.\n\n## Demonstrates\n\n- Puck AI integration for generating pages with AI\n- Remix Run V2 implementation\n- JSON database implementation with HTTP API\n- Dynamic routes to use puck for any root-level route on the platform\n- Option to disable client-side JavaScript for Puck pages\n\n## Usage\n\nRun the generator and select `Remix` when prompted\n\n```\nnpx create-puck-app my-app\n\n? Which recipe would you like to use?\n❯ Remix\n```\n\nConfirm you want to use Puck AI\n\n```\n? Would you like to use Puck AI? (Y/n) Y\n```\n\nStart the server\n\n```\ncd my-app\nyarn dev\n```\n\n### Set up Puck AI\n\nCreate a [Puck account](https://cloud.puckeditor.com) and [obtain an API key](https://cloud.puckeditor.com/api-keys).\n\nCreate a `.env` file in the root of your project and add your API key:\n\n```\nPUCK_API_KEY=your-api-key\n```\n\nNavigate to the homepage at https://localhost:3000. To edit the homepage, access the Puck editor at https://localhost:3000/edit, and select the AI button in the left navigation bar to generate content for the page using Puck AI.\n\nYou can do this for any route on the application, **even if the page doesn't exist**. For example, visit https://localhost:3000/hello/world and you'll receive a 404. You can author and publish a page by visiting https://localhost:3000/hello/world/edit. After publishing, go back to the original URL to see your page.\n\n## Using this recipe\n\nTo adopt this recipe you will need to:\n\n- **IMPORTANT** Add authentication to `/edit` routes. This can be done by modifying the example routes `/app/routes/_index.tsx` and `/app/routes/edit.tsx` or the example model in `/app/models/page.server.ts`. **If you don't do this, Puck will be completely public.**\n- Integrate your database into the API calls in `/app/models/page.server.ts`\n- Implement a custom puck configuration in `/app/puck.config.tsx`\n- Add business context for the AI generation in `/app/routes/api.puck.$.tsx`\n\nBy default, this recipe will have JavaScript enable on all routes - like a usual react app. If you know that your Puck content doesn't need react, then you can disable JS uncommenting the relevant code in `/app/root.tsx` and the example route `/app/routes/_index.tsx`. Check the network tab for no JS downloads, and verify that the page still works.\n\n## Disabling JavaScript\n\nThis recipe can be adapted to disable JavaScript. See the [Remix docs](https://remix.run/docs/en/main/guides/disabling-javascript) for steps on how to do this.\n"
  },
  {
    "path": "recipes/remix-ai/app/entry.client.tsx",
    "content": "/**\n * By default, Remix will handle hydrating your app on the client for you.\n * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨\n * For more information, see https://remix.run/file-conventions/entry.client\n */\n\nimport { RemixBrowser } from \"@remix-run/react\";\nimport { startTransition, StrictMode } from \"react\";\nimport { hydrateRoot } from \"react-dom/client\";\n\nstartTransition(() => {\n  hydrateRoot(\n    document,\n    <StrictMode>\n      <RemixBrowser />\n    </StrictMode>\n  );\n});\n"
  },
  {
    "path": "recipes/remix-ai/app/entry.server.tsx",
    "content": "/**\n * By default, Remix will handle generating the HTTP Response for you.\n * You are free to delete this file if you'd like to, but if you ever want it revealed again, you can run `npx remix reveal` ✨\n * For more information, see https://remix.run/file-conventions/entry.server\n */\n\nimport { PassThrough } from \"node:stream\";\n\nimport type { AppLoadContext, EntryContext } from \"@remix-run/node\";\nimport { createReadableStreamFromReadable } from \"@remix-run/node\";\nimport { RemixServer } from \"@remix-run/react\";\nimport isbot from \"isbot\";\nimport { renderToPipeableStream } from \"react-dom/server\";\n\nconst ABORT_DELAY = 5_000;\n\nexport default function handleRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext,\n  loadContext: AppLoadContext\n) {\n  return isbot(request.headers.get(\"user-agent\"))\n    ? handleBotRequest(\n        request,\n        responseStatusCode,\n        responseHeaders,\n        remixContext\n      )\n    : handleBrowserRequest(\n        request,\n        responseStatusCode,\n        responseHeaders,\n        remixContext\n      );\n}\n\nfunction handleBotRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  return new Promise((resolve, reject) => {\n    let shellRendered = false;\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n        abortDelay={ABORT_DELAY}\n      />,\n      {\n        onAllReady() {\n          shellRendered = true;\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n\n          pipe(body);\n        },\n        onShellError(error: unknown) {\n          reject(error);\n        },\n        onError(error: unknown) {\n          responseStatusCode = 500;\n          // Log streaming rendering errors from inside the shell.  Don't log\n          // errors encountered during initial shell rendering since they'll\n          // reject and get logged in handleDocumentRequest.\n          if (shellRendered) {\n            console.error(error);\n          }\n        },\n      }\n    );\n\n    setTimeout(abort, ABORT_DELAY);\n  });\n}\n\nfunction handleBrowserRequest(\n  request: Request,\n  responseStatusCode: number,\n  responseHeaders: Headers,\n  remixContext: EntryContext\n) {\n  return new Promise((resolve, reject) => {\n    let shellRendered = false;\n    const { pipe, abort } = renderToPipeableStream(\n      <RemixServer\n        context={remixContext}\n        url={request.url}\n        abortDelay={ABORT_DELAY}\n      />,\n      {\n        onShellReady() {\n          shellRendered = true;\n          const body = new PassThrough();\n          const stream = createReadableStreamFromReadable(body);\n\n          responseHeaders.set(\"Content-Type\", \"text/html\");\n\n          resolve(\n            new Response(stream, {\n              headers: responseHeaders,\n              status: responseStatusCode,\n            })\n          );\n\n          pipe(body);\n        },\n        onShellError(error: unknown) {\n          reject(error);\n        },\n        onError(error: unknown) {\n          responseStatusCode = 500;\n          // Log streaming rendering errors from inside the shell.  Don't log\n          // errors encountered during initial shell rendering since they'll\n          // reject and get logged in handleDocumentRequest.\n          if (shellRendered) {\n            console.error(error);\n          }\n        },\n      }\n    );\n\n    setTimeout(abort, ABORT_DELAY);\n  });\n}\n"
  },
  {
    "path": "recipes/remix-ai/app/models/page.server.ts",
    "content": "import { Data } from \"@puckeditor/core\";\nimport fs from \"fs\";\n\n// Replace with call to your database\nexport const getPage = (path: string) => {\n  const allData: Record<string, Data> | null = fs.existsSync(\"database.json\")\n    ? JSON.parse(fs.readFileSync(\"database.json\", \"utf-8\"))\n    : null;\n\n  return allData ? allData[path] : null;\n};\n\n// Replace with call to your database\nexport const setPage = (path: string, data: Data) => {\n  const existingData = JSON.parse(\n    fs.existsSync(\"database.json\")\n      ? fs.readFileSync(\"database.json\", \"utf-8\")\n      : \"{}\"\n  );\n\n  const updatedData = {\n    ...existingData,\n    [path]: data,\n  };\n\n  fs.writeFileSync(\"database.json\", JSON.stringify(updatedData));\n};\n"
  },
  {
    "path": "recipes/remix-ai/app/puck.config.tsx",
    "content": "import type { Config } from \"@puckeditor/core\";\n\ntype Props = {\n  HeadingBlock: { title: string };\n};\n\nexport const config: Config<Props> = {\n  components: {\n    HeadingBlock: {\n      fields: {\n        title: { type: \"text\" },\n      },\n      defaultProps: {\n        title: \"Heading\",\n      },\n      render: ({ title }) => (\n        <div style={{ padding: 64 }}>\n          <h1>{title}</h1>\n        </div>\n      ),\n    },\n  },\n};\n\nexport default config;\n"
  },
  {
    "path": "recipes/remix-ai/app/root.tsx",
    "content": "import { cssBundleHref } from \"@remix-run/css-bundle\";\nimport type { LinksFunction } from \"@remix-run/node\";\nimport {\n  Links,\n  LiveReload,\n  Meta,\n  Outlet,\n  Scripts,\n  ScrollRestoration,\n} from \"@remix-run/react\";\n\nimport styles from \"~/styles/shared.css\";\n\nexport const links: LinksFunction = () => [\n  ...(cssBundleHref ? [{ rel: \"stylesheet\", href: cssBundleHref }] : []),\n  { rel: \"stylesheet\", href: styles },\n];\n\nexport default function App() {\n  return (\n    <html lang=\"en\">\n      <head>\n        <meta charSet=\"utf-8\" />\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\n        <Meta />\n        <Links />\n      </head>\n      <body>\n        <Outlet />\n        <ScrollRestoration />\n        <Scripts />\n        <LiveReload />\n      </body>\n    </html>\n  );\n}\n"
  },
  {
    "path": "recipes/remix-ai/app/routes/$puckPath.tsx",
    "content": "export { default, loader, meta } from \"./_index\";\nexport * from \"./_index\";\n"
  },
  {
    "path": "recipes/remix-ai/app/routes/$puckPath_.edit.tsx",
    "content": "export { default, action, loader, links } from \"./edit\";\nexport * from \"./edit\";\n"
  },
  {
    "path": "recipes/remix-ai/app/routes/_index.tsx",
    "content": "import { Render, type Config } from \"@puckeditor/core\";\nimport type { LoaderFunctionArgs, MetaFunction } from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { useLoaderData } from \"@remix-run/react\";\n\nimport puckConfig from \"~/puck.config\";\nimport { getPage } from \"~/models/page.server\";\n\nexport const loader = async ({ params }: LoaderFunctionArgs) => {\n  // Get path, and default to slash for root path.\n  const puckPath = params.puckPath || \"/\";\n  // Get puckData for this path, this could be a database call.\n  const puckData = getPage(puckPath);\n  if (!puckData) {\n    throw new Response(null, {\n      status: 404,\n      statusText: \"Not Found\",\n    });\n  }\n  // Return the data.\n  return json({ puckData });\n};\n\nexport const meta: MetaFunction<typeof loader> = ({ data }) => {\n  const title = data?.puckData?.root?.props?.title || \"Page\";\n\n  return [{ title }];\n};\n\nexport default function Page() {\n  const { puckData } = useLoaderData<typeof loader>();\n\n  return <Render config={puckConfig as Config} data={puckData} />;\n}\n"
  },
  {
    "path": "recipes/remix-ai/app/routes/api.puck.$.tsx",
    "content": "import type { ActionFunctionArgs } from \"@remix-run/node\";\nimport { puckHandler } from \"@puckeditor/cloud-client\";\n\n// Handles all requests for Puck AI\n// Learn more: https://puckeditor.com/docs/ai/getting-started\nexport async function action(args: ActionFunctionArgs) {\n  return puckHandler(args.request, {\n    ai: {\n      // Replace with your business context\n      context: \"We are Google. You create Google landing pages.\",\n    },\n  });\n}\n"
  },
  {
    "path": "recipes/remix-ai/app/routes/edit.tsx",
    "content": "import { Puck, type Data, type Config } from \"@puckeditor/core\";\nimport { createAiPlugin } from \"@puckeditor/plugin-ai\";\nimport styles from \"@puckeditor/core/puck.css\";\nimport pluginStyles from \"@puckeditor/plugin-ai/styles.css\";\nimport type {\n  ActionFunctionArgs,\n  LinksFunction,\n  LoaderFunctionArgs,\n  MetaFunction,\n} from \"@remix-run/node\";\nimport { json } from \"@remix-run/node\";\nimport { useLoaderData, useSubmit } from \"@remix-run/react\";\nimport invariant from \"tiny-invariant\";\n\nimport puckConfig from \"~/puck.config\";\nimport { getPage, setPage } from \"~/models/page.server\";\n\nexport const action = async ({ params, request }: ActionFunctionArgs) => {\n  const puckPath = params.puckPath || \"/\";\n  const formData = await request.formData();\n  const puckData = formData.get(\"puckData\");\n\n  invariant(puckData, \"Missing data\");\n  invariant(typeof puckData === \"string\", \"Invalid data\");\n\n  setPage(puckPath, JSON.parse(puckData));\n\n  return json({ ok: true });\n};\n\nexport const links: LinksFunction = () => [\n  { rel: \"stylesheet\", href: styles, id: \"puck-css\" },\n  { rel: \"stylesheet\", href: pluginStyles, id: \"puck-plugin-ai-css\" },\n];\n\nexport const loader = async ({ params }: LoaderFunctionArgs) => {\n  const puckPath = params.puckPath || \"/\";\n  const initialData = getPage(puckPath) || {\n    content: [],\n    root: {},\n  };\n  return json({ puckPath, initialData });\n};\n\nexport const meta: MetaFunction<typeof loader> = ({ data }) => {\n  const title = data?.initialData?.root?.props?.title || \"Untitled page\";\n\n  return [{ title: `Editing: ${title}` }];\n};\n\nconst aiPlugin = createAiPlugin();\n\nexport default function Edit() {\n  const { initialData } = useLoaderData<typeof loader>();\n  const submit = useSubmit();\n\n  return (\n    <Puck\n      plugins={[aiPlugin]}\n      config={puckConfig as Config}\n      data={initialData}\n      onPublish={async (data: Data) => {\n        // Use form data here because it's the usual remix way.\n        let formData = new FormData();\n        formData.append(\"puckData\", JSON.stringify(data));\n        submit(formData, { method: \"post\" });\n      }}\n    />\n  );\n}\n"
  },
  {
    "path": "recipes/remix-ai/app/styles/shared.css",
    "content": "body {\n  margin: 0;\n}\n"
  },
  {
    "path": "recipes/remix-ai/database.json",
    "content": "{\"/\":{\"content\":[{\"type\":\"HeadingBlock\",\"props\":{\"title\":\"Edit this page by adding /edit to the end of the URL\",\"id\":\"HeadingBlock-1694032984497\"}}],\"root\":{\"props\": {\"title\":\"\"}}}}"
  },
  {
    "path": "recipes/remix-ai/package.json",
    "content": "{\n  \"name\": \"remix-ai-recipe\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"sideEffects\": false,\n  \"type\": \"module\",\n  \"scripts\": {\n    \"build\": \"remix build\",\n    \"dev\": \"remix dev --manual\",\n    \"start\": \"remix-serve ./build/index.js\",\n    \"typecheck\": \"tsc\"\n  },\n  \"dependencies\": {\n    \"@puckeditor/core\": \"*\",\n    \"@puckeditor/cloud-client\": \"^0.5.0\",\n    \"@puckeditor/plugin-ai\": \"^0.5.0\",\n    \"@remix-run/css-bundle\": \"^2.2.0\",\n    \"@remix-run/node\": \"^2.2.0\",\n    \"@remix-run/react\": \"^2.2.0\",\n    \"@remix-run/serve\": \"^2.2.0\",\n    \"isbot\": \"^3.6.8\",\n    \"react\": \"^18.2.0\",\n    \"react-dom\": \"^18.2.0\",\n    \"tiny-invariant\": \"^1.3.3\"\n  },\n  \"devDependencies\": {\n    \"@remix-run/dev\": \"^2.2.0\",\n    \"@remix-run/eslint-config\": \"^2.2.0\",\n    \"@types/react\": \"^18.2.20\",\n    \"@types/react-dom\": \"^18.2.7\",\n    \"eslint\": \"^8.38.0\",\n    \"typescript\": \"^5.1.6\"\n  },\n  \"engines\": {\n    \"node\": \">=20.0.0\"\n  }\n}\n"
  },
  {
    "path": "recipes/remix-ai/remix.config.js",
    "content": "/** @type {import('@remix-run/dev').AppConfig} */\nexport default {\n  ignoredRouteFiles: [\"**/.*\"],\n  // appDirectory: \"app\",\n  // assetsBuildDirectory: \"public/build\",\n  // publicPath: \"/build/\",\n  // serverBuildPath: \"build/index.js\",\n  /**\n   * @see https://github.com/puckeditor/puck/issues/112\n   */\n  browserNodeBuiltinsPolyfill: { modules: { crypto: true } },\n};\n"
  },
  {
    "path": "recipes/remix-ai/remix.env.d.ts",
    "content": "/// <reference types=\"@remix-run/dev\" />\n/// <reference types=\"@remix-run/node\" />\n"
  },
  {
    "path": "recipes/remix-ai/tsconfig.json",
    "content": "{\n  \"include\": [\"remix.env.d.ts\", \"**/*.ts\", \"**/*.tsx\"],\n  \"compilerOptions\": {\n    \"lib\": [\"DOM\", \"DOM.Iterable\", \"ES2022\"],\n    \"isolatedModules\": true,\n    \"esModuleInterop\": true,\n    \"jsx\": \"react-jsx\",\n    \"moduleResolution\": \"Bundler\",\n    \"resolveJsonModule\": true,\n    \"target\": \"ES2022\",\n    \"strict\": true,\n    \"allowJs\": true,\n    \"forceConsistentCasingInFileNames\": true,\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"~/*\": [\"./app/*\"]\n    },\n\n    // Remix takes care of building everything in `remix build`.\n    \"noEmit\": true\n  }\n}\n"
  },
  {
    "path": "scripts/create-changelog.js",
    "content": "const standardChangelog = require(\"standard-changelog\");\nconst path = require(\"path\");\nconst fs = require(\"fs\");\n\nconst releasePattern = /^#\\s(\\[?(([0-9]|\\.)+)\\]?.*)((.|\\n)*)/gm;\n\nconst transform = (body) =>\n  body.replace(releasePattern, (_, header, _2, _3, content) => {\n    return `## ${header}${content}`;\n  });\n\nconst changelogPath = path.join(__dirname, \"../CHANGELOG.md\");\nconst changelog = fs.readFileSync(changelogPath, \"utf8\");\n\nlet changes = \"\";\n\nconst standardStream = standardChangelog();\n\nstandardStream.on(\n  \"data\",\n  (chunk) => (changes = `${changes}${chunk.toString()}`)\n);\n\nstandardStream.on(\"end\", () => {\n  // Indent headers and add download section\n  const transformed = transform(changes);\n\n  // Inject into changelog\n  const updatedChangelog = changelog.replace(\n    \"<!--__CHANGELOG_ENTRY__-->\\n\",\n    `<!--__CHANGELOG_ENTRY__-->\\n\\n${transformed}`\n  );\n\n  fs.writeFileSync(changelogPath, updatedChangelog);\n});\n"
  },
  {
    "path": "scripts/e2e/smoke-framework.mjs",
    "content": "import asciichart from \"asciichart\";\nimport { pause } from \"./utils/pause.mjs\";\nimport { setup } from \"./utils/setup.mjs\";\n\nconst CHART = false;\nconst CHART_HEIGHT = 15;\nconst DURATION = 60;\nconst THRESHOLD = 300;\nconst MB = 1024 * 1024;\n\nconst execute = async (test) => {\n  const {\n    duration = DURATION,\n    threshold = THRESHOLD,\n    chart = CHART,\n    chartHeight = CHART_HEIGHT,\n    puppeteerOptions,\n  } = test;\n\n  const { browser, page } = await setup(test.url, puppeteerOptions);\n\n  if (chart) {\n    console.log(\n      \"\\x1b[36m%s\\x1b[0m\",\n      ` ╭ ${test.label} (duration: ${duration}s, threshold: ${threshold} MB)`\n    );\n  }\n\n  const timeout = duration * 1000;\n  const memoryThreshold = threshold * MB;\n\n  let memoryUsage;\n\n  const memoryData = [50];\n\n  const start = Date.now();\n\n  const clearLines = (n) => {\n    for (let i = 0; i < n; i++) {\n      const y = i === 0 ? null : -1;\n      process.stdout.moveCursor(0, y);\n      process.stdout.clearLine(1);\n    }\n    process.stdout.cursorTo(0);\n  };\n\n  let plotted = false;\n  let timeElapsedHuman = 0;\n  let iterations = 0;\n\n  const plot = () => {\n    if (chart) {\n      if (memoryData.length > 0) {\n        if (plotted) {\n          clearLines(chartHeight + 2);\n        }\n\n        process.stdout.write(\n          ` │ ${asciichart\n            .plot(memoryData, { height: chartHeight })\n            .replace(/\\n/gm, \"\\n │ \")}`\n        );\n\n        process.stdout.write(\n          `\\n ╰ ${(memoryUsage / MB).toFixed(\n            2\n          )} MB (${timeElapsedHuman}s, ${iterations} iterations) `\n        );\n      }\n    } else {\n      if (plotted) {\n        clearLines(1);\n      }\n\n      process.stdout.write(\n        ` ├ ${test.label}: ${(memoryUsage / MB).toFixed(\n          2\n        )} MB (${timeElapsedHuman}s, ${iterations} iterations) `\n      );\n    }\n\n    plotted = true;\n  };\n\n  await pause(1000);\n\n  let memoryExceeded = false;\n\n  try {\n    while (true) {\n      iterations += 1;\n\n      const timeElapsed = Date.now() - start;\n      timeElapsedHuman = ((Date.now() - start) / 1000).toFixed(2);\n\n      await test.run(page);\n\n      memoryUsage = await page.evaluate(\n        () => performance.memory.usedJSHeapSize\n      );\n\n      memoryData.push((memoryUsage / MB).toFixed(2));\n\n      plot();\n\n      if (memoryUsage > memoryThreshold) {\n        memoryExceeded = true;\n\n        console.error(\n          `❌ Test failed. Memory usage exceeded ${threshold} MB threshold in ${timeElapsedHuman}s.`\n        );\n\n        break;\n      }\n\n      if (timeElapsed > timeout) {\n        break;\n      }\n    }\n\n    if (!memoryExceeded) {\n      plot();\n\n      console.log(`✅ Test successful.`);\n    }\n  } catch (err) {\n    console.error(\"Error during smoke test:\", err);\n  } finally {\n    await browser.close();\n  }\n\n  return {\n    memoryUsage,\n    memoryExceeded,\n    timeElapsedHuman,\n    memoryData,\n    iterations,\n  };\n};\n\nexport const smoke = async (tests, config) => {\n  console.log(`Beginning smoke tests...`);\n  const results = [];\n\n  for (let i = 0; i < tests.length; i++) {\n    const test = tests[i];\n\n    results.push({\n      test,\n      result: await execute({ ...config, ...test }),\n    });\n  }\n\n  const numSucceeded = results.reduce(\n    (acc, result) => (result.result.memoryExceeded ? acc : acc + 1),\n    0\n  );\n\n  console.log(`${numSucceeded}/${results.length} tests passed`);\n\n  if (numSucceeded < 0) {\n    process.exit(1);\n  }\n};\n"
  },
  {
    "path": "scripts/e2e/smoke.mjs",
    "content": "import { smoke } from \"./smoke-framework.mjs\";\nimport { dragAndDrop } from \"./utils/drag-and-drop.mjs\";\n\nconst runs = {\n  oneLevel: async (page) => {\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Heading\"]',\n      '[data-testid=\"dropzone:root:default-zone\"]'\n    );\n  },\n  twoLevels: async (page) => {\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"]',\n      \"top\"\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Heading\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone]'\n    );\n  },\n  threeLevels: async (page) => {\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"]',\n      \"top\"\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone]'\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Heading\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone] [data-puck-dropzone]'\n    );\n  },\n  sixLevels: async (page) => {\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"]',\n      \"top\"\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone]'\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone] [data-puck-dropzone]'\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone] [data-puck-dropzone] [data-puck-dropzone]'\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Grid\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone] [data-puck-dropzone] [data-puck-dropzone] [data-puck-dropzone]'\n    );\n\n    await dragAndDrop(\n      page,\n      '[data-testid=\"drawer-item:Heading\"]',\n      '[data-testid=\"dropzone:root:default-zone\"] [data-puck-dropzone] [data-puck-dropzone] [data-puck-dropzone] [data-puck-dropzone] [data-puck-dropzone]'\n    );\n  },\n};\n\nconst tests = [\n  {\n    label: \"iframe, 2 levels\",\n    url: \"http://localhost:3000/test/edit?disableIframe=false\",\n    run: runs.twoLevels,\n  },\n  {\n    label: \"iframe, 6 levels\",\n    url: \"http://localhost:3000/test/edit?disableIframe=false\",\n    run: runs.sixLevels,\n  },\n  {\n    label: \"no iframe, 2 levels\",\n    url: \"http://localhost:3000/test/edit?disableIframe=true\",\n    run: runs.twoLevels,\n  },\n  {\n    label: \"no iframe, 6 levels\",\n    url: \"http://localhost:3000/test/edit?disableIframe=true\",\n    run: runs.sixLevels,\n  },\n];\n\n(async () => {\n  await smoke(tests, {\n    chart: true,\n    duration: 180,\n    threshold: 300,\n    puppeteerOptions: { headless: true },\n  });\n})();\n"
  },
  {
    "path": "scripts/e2e/utils/drag-and-drop.mjs",
    "content": "import { getBox } from \"./get-box.mjs\";\nimport { pause } from \"./pause.mjs\";\n\nconst TIME_PREFIX = 0;\n\nexport async function dragAndDrop(\n  page,\n  sourceSelector,\n  targetSelector,\n  position\n) {\n  const sourceBox = await getBox(page, sourceSelector);\n  const targetBox = await getBox(page, targetSelector);\n\n  // Simulate drag and drop\n  await page.mouse.move(\n    sourceBox.x + sourceBox.width / 2,\n    sourceBox.y + sourceBox.height / 2\n  );\n  await page.mouse.down(); // Simulate drag start\n\n  await pause(TIME_PREFIX + 300);\n\n  if (position === \"top\") {\n    // Move to top\n    await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y - 4);\n\n    await pause(TIME_PREFIX + 50);\n\n    // Move to center\n    await page.mouse.move(\n      targetBox.x + targetBox.width / 2,\n      targetBox.y + targetBox.height / 2\n    );\n    await pause(TIME_PREFIX + 50);\n\n    // Move to top\n    await page.mouse.move(targetBox.x + targetBox.width / 2, targetBox.y - 4);\n  } else {\n    await page.mouse.move(\n      targetBox.x + targetBox.width / 2,\n      targetBox.y + targetBox.height / 2\n    );\n  }\n\n  await pause(TIME_PREFIX + 10);\n\n  await page.mouse.up(); // Simulate drop\n\n  await pause(TIME_PREFIX + 500);\n}\n"
  },
  {
    "path": "scripts/e2e/utils/get-box.mjs",
    "content": "/**\n * Custom box function to get scaled bounding box across documents\n *\n * @param {*} page\n * @param {*} selector\n * @returns\n */\nexport const getBox = async (page, targetSelector) =>\n  await page.evaluate((selector) => {\n    function getFrameElement(el) {\n      const refWindow = el?.ownerDocument.defaultView;\n\n      if (refWindow && refWindow.self !== refWindow.parent) {\n        return refWindow.frameElement;\n      }\n\n      return null;\n    }\n\n    function getFrameTransform(frameEl, boundary = window.frameElement) {\n      const transform = {\n        x: 0,\n        y: 0,\n        scaleX: 1,\n        scaleY: 1,\n      };\n\n      while (frameEl) {\n        if (frameEl === boundary) {\n          return transform;\n        }\n\n        const rect = frameEl.getBoundingClientRect();\n        const { x: scaleX, y: scaleY } = getScale(frameEl, rect);\n\n        transform.x = transform.x + rect.left;\n        transform.y = transform.y + rect.top;\n        transform.scaleX = transform.scaleX * scaleX;\n        transform.scaleY = transform.scaleY * scaleY;\n\n        frameEl = getFrameElement(frameEl);\n      }\n\n      return transform;\n    }\n\n    function getScale(\n      element,\n      boundingRectangle = element.getBoundingClientRect()\n    ) {\n      const width = Math.round(boundingRectangle.width);\n      const height = Math.round(boundingRectangle.height);\n\n      return {\n        x: width / element.offsetWidth,\n        y: height / element.offsetHeight,\n      };\n    }\n\n    const frameEl = document.querySelector(\"#preview-frame\");\n    const frame = frameEl ? frameEl?.contentDocument || null : null;\n\n    const el = document.querySelector(selector);\n\n    if (el) {\n      const rect = el.getBoundingClientRect();\n\n      return {\n        x: rect.left,\n        y: rect.top,\n        width: rect.width,\n        height: rect.height,\n      };\n    } else if (frame) {\n      const el = frame.querySelector(selector);\n\n      if (!el) return null;\n\n      const rect = el.getBoundingClientRect();\n      const frameRect = frameEl.getBoundingClientRect();\n      const transform = getFrameTransform(frameEl);\n\n      return {\n        x: rect.left * transform.scaleX + frameRect.left,\n        y: rect.top * transform.scaleY + frameRect.top,\n        width: rect.width * transform.scaleX,\n        height: rect.height * transform.scaleY,\n      };\n    }\n\n    return null;\n  }, targetSelector);\n"
  },
  {
    "path": "scripts/e2e/utils/pause.mjs",
    "content": "export const pause = (timeout) =>\n  new Promise((resolve) => setTimeout(resolve, timeout));\n"
  },
  {
    "path": "scripts/e2e/utils/setup.mjs",
    "content": "import puppeteer from \"puppeteer\";\n\nexport const setup = async (\n  url = \"http://localhost:3000/test/edit?disableIframe=false\",\n  { headless = true, ...options } = {}\n) => {\n  const browser = await puppeteer.launch({ headless, ...options });\n  const page = await browser.newPage();\n  await page.setViewport({ width: 1200, height: 812 });\n  await page.goto(url);\n\n  return { browser, page };\n};\n"
  },
  {
    "path": "scripts/get-unstable-version.js",
    "content": "const { exec } = require(\"child_process\");\nconst pkg = require(\"../package.json\");\n\nexec(\"git rev-parse --short HEAD\", (_, stdout) => {\n  console.log(`${pkg.version}-${process.argv[2] || \"canary\"}.${stdout}`);\n  process.exit();\n});\n"
  },
  {
    "path": "scripts/publish.sh",
    "content": "cd packages/core && npm publish --access public --tag $1\ncd ../../\n\ncd packages/field-contentful && npm publish --access public --tag $1\ncd ../../\n\ncd packages/plugin-emotion-cache && npm publish --access public --tag $1\ncd ../../\n\ncd packages/plugin-heading-analyzer && npm publish --access public --tag $1\ncd ../../\n\ncd packages/create-puck-app && npm run removeGitignore && npm publish --access public --tag $1 && npm run restoreGitignore\ncd ../../\n"
  },
  {
    "path": "turbo.json",
    "content": "{\n  \"$schema\": \"https://turbo.build/schema.json\",\n  \"globalDependencies\": [\"**/.env.*local\"],\n  \"globalEnv\": [\n    \"NEXT_PUBLIC_PLAUSIBLE_DATA_DOMAIN\",\n    \"NEXT_PUBLIC_IS_LATEST\",\n    \"NEXT_PUBLIC_IS_CANARY\",\n    \"VERCEL_GIT_COMMIT_REF\",\n    \"NEXT_PUBLIC_BASE_URL\",\n    \"NODE_ENV\"\n  ],\n  \"tasks\": {\n    \"build\": {\n      \"dependsOn\": [\"^build\"],\n      \"outputs\": [\".next/**\", \"!.next/cache/**\", \"dist/**\"]\n    },\n    \"lint\": {},\n    \"test\": {},\n    \"dev\": {\n      \"cache\": false,\n      \"persistent\": true\n    }\n  }\n}\n"
  }
]