payload-better-editor 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (182) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +57 -0
  3. package/dist/admin/ErrorBoundary.d.ts +17 -0
  4. package/dist/admin/ErrorBoundary.js +62 -0
  5. package/dist/admin/ErrorBoundary.js.map +1 -0
  6. package/dist/admin/LiveEditorOverlay.d.ts +12 -0
  7. package/dist/admin/LiveEditorOverlay.js +160 -0
  8. package/dist/admin/LiveEditorOverlay.js.map +1 -0
  9. package/dist/admin/LiveEditorToggle.d.ts +7 -0
  10. package/dist/admin/LiveEditorToggle.js +84 -0
  11. package/dist/admin/LiveEditorToggle.js.map +1 -0
  12. package/dist/admin/PreviewFrame.d.ts +22 -0
  13. package/dist/admin/PreviewFrame.js +137 -0
  14. package/dist/admin/PreviewFrame.js.map +1 -0
  15. package/dist/admin/PreviewToolbar.d.ts +16 -0
  16. package/dist/admin/PreviewToolbar.js +90 -0
  17. package/dist/admin/PreviewToolbar.js.map +1 -0
  18. package/dist/admin/SettingsBanner.d.ts +3 -0
  19. package/dist/admin/SettingsBanner.js +105 -0
  20. package/dist/admin/SettingsBanner.js.map +1 -0
  21. package/dist/admin/ViewportToggle.d.ts +7 -0
  22. package/dist/admin/ViewportToggle.js +79 -0
  23. package/dist/admin/ViewportToggle.js.map +1 -0
  24. package/dist/admin/blocks/AddBlockDrawer.d.ts +9 -0
  25. package/dist/admin/blocks/AddBlockDrawer.js +16 -0
  26. package/dist/admin/blocks/AddBlockDrawer.js.map +1 -0
  27. package/dist/admin/blocks/BlockActionsToolbar.d.ts +15 -0
  28. package/dist/admin/blocks/BlockActionsToolbar.js +102 -0
  29. package/dist/admin/blocks/BlockActionsToolbar.js.map +1 -0
  30. package/dist/admin/blocks/BlockEmptyState.d.ts +6 -0
  31. package/dist/admin/blocks/BlockEmptyState.js +26 -0
  32. package/dist/admin/blocks/BlockEmptyState.js.map +1 -0
  33. package/dist/admin/blocks/BlockHeader.d.ts +7 -0
  34. package/dist/admin/blocks/BlockHeader.js +32 -0
  35. package/dist/admin/blocks/BlockHeader.js.map +1 -0
  36. package/dist/admin/blocks/schema.d.ts +19 -0
  37. package/dist/admin/blocks/schema.js +80 -0
  38. package/dist/admin/blocks/schema.js.map +1 -0
  39. package/dist/admin/blocks/useBlockActions.d.ts +24 -0
  40. package/dist/admin/blocks/useBlockActions.js +100 -0
  41. package/dist/admin/blocks/useBlockActions.js.map +1 -0
  42. package/dist/admin/icons.d.ts +24 -0
  43. package/dist/admin/icons.js +36 -0
  44. package/dist/admin/icons.js.map +1 -0
  45. package/dist/admin/sidebar/BlockSettingsTab.d.ts +10 -0
  46. package/dist/admin/sidebar/BlockSettingsTab.js +153 -0
  47. package/dist/admin/sidebar/BlockSettingsTab.js.map +1 -0
  48. package/dist/admin/sidebar/DocumentFieldsTab.d.ts +8 -0
  49. package/dist/admin/sidebar/DocumentFieldsTab.js +38 -0
  50. package/dist/admin/sidebar/DocumentFieldsTab.js.map +1 -0
  51. package/dist/admin/sidebar/DocumentMetaTab.d.ts +2 -0
  52. package/dist/admin/sidebar/DocumentMetaTab.js +11 -0
  53. package/dist/admin/sidebar/DocumentMetaTab.js.map +1 -0
  54. package/dist/admin/sidebar/DocumentSettingsTab.d.ts +2 -0
  55. package/dist/admin/sidebar/DocumentSettingsTab.js +48 -0
  56. package/dist/admin/sidebar/DocumentSettingsTab.js.map +1 -0
  57. package/dist/admin/sidebar/Sidebar.d.ts +10 -0
  58. package/dist/admin/sidebar/Sidebar.js +92 -0
  59. package/dist/admin/sidebar/Sidebar.js.map +1 -0
  60. package/dist/client.d.ts +34 -0
  61. package/dist/client.js +30 -0
  62. package/dist/client.js.map +1 -0
  63. package/dist/global.d.ts +4 -0
  64. package/dist/global.js +200 -0
  65. package/dist/global.js.map +1 -0
  66. package/dist/hooks/useAddBlockDrawer.d.ts +14 -0
  67. package/dist/hooks/useAddBlockDrawer.js +26 -0
  68. package/dist/hooks/useAddBlockDrawer.js.map +1 -0
  69. package/dist/hooks/useBlockActionMessages.d.ts +8 -0
  70. package/dist/hooks/useBlockActionMessages.js +107 -0
  71. package/dist/hooks/useBlockActionMessages.js.map +1 -0
  72. package/dist/hooks/useDocConfig.d.ts +6 -0
  73. package/dist/hooks/useDocConfig.js +18 -0
  74. package/dist/hooks/useDocConfig.js.map +1 -0
  75. package/dist/hooks/useFocusTrap.d.ts +2 -0
  76. package/dist/hooks/useFocusTrap.js +84 -0
  77. package/dist/hooks/useFocusTrap.js.map +1 -0
  78. package/dist/hooks/useFullscreenOverlay.d.ts +2 -0
  79. package/dist/hooks/useFullscreenOverlay.js +30 -0
  80. package/dist/hooks/useFullscreenOverlay.js.map +1 -0
  81. package/dist/hooks/useIframeResizeObserver.d.ts +2 -0
  82. package/dist/hooks/useIframeResizeObserver.js +20 -0
  83. package/dist/hooks/useIframeResizeObserver.js.map +1 -0
  84. package/dist/hooks/useLatestRef.d.ts +6 -0
  85. package/dist/hooks/useLatestRef.js +12 -0
  86. package/dist/hooks/useLatestRef.js.map +1 -0
  87. package/dist/hooks/useMainWrapperPortal.d.ts +1 -0
  88. package/dist/hooks/useMainWrapperPortal.js +64 -0
  89. package/dist/hooks/useMainWrapperPortal.js.map +1 -0
  90. package/dist/hooks/useOverlayKeyboard.d.ts +6 -0
  91. package/dist/hooks/useOverlayKeyboard.js +43 -0
  92. package/dist/hooks/useOverlayKeyboard.js.map +1 -0
  93. package/dist/hooks/usePreviewBinding.d.ts +28 -0
  94. package/dist/hooks/usePreviewBinding.js +108 -0
  95. package/dist/hooks/usePreviewBinding.js.map +1 -0
  96. package/dist/hooks/usePreviewHandleDrag.d.ts +11 -0
  97. package/dist/hooks/usePreviewHandleDrag.js +53 -0
  98. package/dist/hooks/usePreviewHandleDrag.js.map +1 -0
  99. package/dist/hooks/usePreviewSelectionSync.d.ts +15 -0
  100. package/dist/hooks/usePreviewSelectionSync.js +80 -0
  101. package/dist/hooks/usePreviewSelectionSync.js.map +1 -0
  102. package/dist/hooks/usePreviewSettingsSync.d.ts +17 -0
  103. package/dist/hooks/usePreviewSettingsSync.js +55 -0
  104. package/dist/hooks/usePreviewSettingsSync.js.map +1 -0
  105. package/dist/hooks/useSidebarResize.d.ts +8 -0
  106. package/dist/hooks/useSidebarResize.js +101 -0
  107. package/dist/hooks/useSidebarResize.js.map +1 -0
  108. package/dist/hooks/useViewportState.d.ts +10 -0
  109. package/dist/hooks/useViewportState.js +44 -0
  110. package/dist/hooks/useViewportState.js.map +1 -0
  111. package/dist/index.d.ts +25 -0
  112. package/dist/index.js +104 -0
  113. package/dist/index.js.map +1 -0
  114. package/dist/internal/constants.d.ts +22 -0
  115. package/dist/internal/constants.js +38 -0
  116. package/dist/internal/constants.js.map +1 -0
  117. package/dist/internal/dom.d.ts +4 -0
  118. package/dist/internal/dom.js +6 -0
  119. package/dist/internal/dom.js.map +1 -0
  120. package/dist/internal/iframe.d.ts +5 -0
  121. package/dist/internal/iframe.js +12 -0
  122. package/dist/internal/iframe.js.map +1 -0
  123. package/dist/internal/limits.d.ts +9 -0
  124. package/dist/internal/limits.js +11 -0
  125. package/dist/internal/limits.js.map +1 -0
  126. package/dist/internal/path.d.ts +5 -0
  127. package/dist/internal/path.js +12 -0
  128. package/dist/internal/path.js.map +1 -0
  129. package/dist/internal/postmessage.d.ts +3 -0
  130. package/dist/internal/postmessage.js +21 -0
  131. package/dist/internal/postmessage.js.map +1 -0
  132. package/dist/internal/storage-keys.d.ts +8 -0
  133. package/dist/internal/storage-keys.js +9 -0
  134. package/dist/internal/storage-keys.js.map +1 -0
  135. package/dist/internal/storage.d.ts +2 -0
  136. package/dist/internal/storage.js +20 -0
  137. package/dist/internal/storage.js.map +1 -0
  138. package/dist/preview/HoverToolbar.d.ts +8 -0
  139. package/dist/preview/HoverToolbar.js +48 -0
  140. package/dist/preview/HoverToolbar.js.map +1 -0
  141. package/dist/preview/HoverToolbarController.d.ts +31 -0
  142. package/dist/preview/HoverToolbarController.js +160 -0
  143. package/dist/preview/HoverToolbarController.js.map +1 -0
  144. package/dist/preview/hover-css.d.ts +11 -0
  145. package/dist/preview/hover-css.js +94 -0
  146. package/dist/preview/hover-css.js.map +1 -0
  147. package/dist/preview/installClickToFocus.d.ts +6 -0
  148. package/dist/preview/installClickToFocus.js +21 -0
  149. package/dist/preview/installClickToFocus.js.map +1 -0
  150. package/dist/preview/installHoverStyles.d.ts +2 -0
  151. package/dist/preview/installHoverStyles.js +15 -0
  152. package/dist/preview/installHoverStyles.js.map +1 -0
  153. package/dist/preview/protocol.d.ts +11 -0
  154. package/dist/preview/protocol.js +19 -0
  155. package/dist/preview/protocol.js.map +1 -0
  156. package/dist/preview/toolbar-position.d.ts +20 -0
  157. package/dist/preview/toolbar-position.js +22 -0
  158. package/dist/preview/toolbar-position.js.map +1 -0
  159. package/dist/providers/BetterEditorConfigProvider.d.ts +14 -0
  160. package/dist/providers/BetterEditorConfigProvider.js +26 -0
  161. package/dist/providers/BetterEditorConfigProvider.js.map +1 -0
  162. package/dist/providers/OverlayProviders.d.ts +8 -0
  163. package/dist/providers/OverlayProviders.js +22 -0
  164. package/dist/providers/OverlayProviders.js.map +1 -0
  165. package/dist/state/useBetterEditorSettings.d.ts +18 -0
  166. package/dist/state/useBetterEditorSettings.js +65 -0
  167. package/dist/state/useBetterEditorSettings.js.map +1 -0
  168. package/dist/state/useEditorHistory.d.ts +16 -0
  169. package/dist/state/useEditorHistory.js +157 -0
  170. package/dist/state/useEditorHistory.js.map +1 -0
  171. package/dist/styles/blocks-tab.css +163 -0
  172. package/dist/styles/overlay.css +133 -0
  173. package/dist/styles/preview.css +211 -0
  174. package/dist/styles/settings-banner.css +73 -0
  175. package/dist/styles/sidebar.css +88 -0
  176. package/dist/types.d.ts +41 -0
  177. package/dist/types.js +3 -0
  178. package/dist/types.js.map +1 -0
  179. package/dist/version.d.ts +1 -0
  180. package/dist/version.js +6 -0
  181. package/dist/version.js.map +1 -0
  182. package/package.json +117 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 scorpio-99
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,57 @@
1
+ # payload-better-editor
2
+
3
+ [![npm](https://img.shields.io/npm/v/payload-better-editor?logo=npm&color=ce421b)](https://www.npmjs.com/package/payload-better-editor)
4
+ [![downloads](https://img.shields.io/npm/dt/payload-better-editor?logo=npm&color=ce421b)](https://www.npmjs.com/package/payload-better-editor)
5
+ [![stars](https://img.shields.io/github/stars/scorpio-99/payload-better-editor?logo=github)](https://github.com/scorpio-99/payload-better-editor)
6
+
7
+ Block editor plugin for [Payload CMS](https://payloadcms.com) that adds a side-by-side live-preview iframe and sidebar to the edit view.
8
+
9
+ ## Skip the giant form - pick a block, edit just that block
10
+
11
+ Open the editor from any document's edit view. The preview iframe loads your live frontend; clicking a block selects it and opens its real Payload fields in the sidebar - no schema duplication, no custom components.
12
+
13
+ ![Better Editor overview](./assets/overview.gif)
14
+
15
+ ## Floating in-iframe toolbar
16
+
17
+ Hover any block in the preview to surface its floating action toolbar - move up, move down, duplicate, add-below, delete. All actions go through Payload's form state and are tracked by the plugin's undo/redo history.
18
+
19
+ ![Inline block toolbar](./assets/toolbar.gif)
20
+
21
+ ## Viewports & layout controls
22
+
23
+ Switch between Desktop, Tablet, Mobile, and Responsive (drag-resizable). The fullscreen button is independent - it puts the whole editor (preview + sidebar) into fullscreen while the iframe keeps the viewport size you picked. The sidebar itself can be drag-resized to any width or collapsed entirely to give the preview the full canvas.
24
+
25
+ ![Viewport switcher and sidebar controls](./assets/viewports.gif)
26
+
27
+ ## Further features
28
+
29
+ - **Page, Blocks, Settings tabs** auto-derived from your document's tab structure
30
+ - **`BetterEditorSettings` global** so admins can change sidebar position, hover colours, tablet/mobile breakpoints, and hover-toolbar placement live - no re-deploy
31
+ - **Block actions in the sidebar too** - the same move / duplicate / add-below / delete actions are mirrored in the sidebar's Blocks tab, so power users don't have to reach for the iframe toolbar
32
+ - **Undo and Redo** with `Cmd/Ctrl+Z` and `Cmd/Ctrl+Shift+Z` - snapshot-based, covers every block mutation
33
+ - **Interact mode** toggle so clicks pass through to forms, accordions, links inside the preview
34
+ - **Loading skeleton** in the iframe and an error boundary so a single bad block can't take the admin down
35
+ - **Click-to-edit** works at arbitrary nesting depth - clicking a deeply nested block walks up to its innermost wrapper
36
+ - **Real Payload fields** in the sidebar via `RenderFields`, so custom field components, validations, and access control all just work
37
+
38
+ ## Install
39
+
40
+ ```bash
41
+ pnpm add payload-better-editor
42
+ ```
43
+
44
+ See [DEVELOPERS.md](./DEVELOPERS.md) for setup, plugin options, runtime settings, and architecture notes.
45
+
46
+ ## Requirements
47
+
48
+ - Payload `>=3.81.0`
49
+ - React 19
50
+
51
+ > **Found a bug?** Early-stage plugin, feedback is appreciated. [Open an issue](https://github.com/scorpio-99/payload-better-editor/issues/new) with steps to reproduce, your Payload version, and a minimal example. PRs welcome.
52
+
53
+ ## Contributors
54
+
55
+ <a href="https://github.com/scorpio-99/payload-better-editor/graphs/contributors">
56
+ <img src="https://contrib.rocks/image?repo=scorpio-99/payload-better-editor" />
57
+ </a>
@@ -0,0 +1,17 @@
1
+ import React from 'react';
2
+ type Props = {
3
+ onClose: () => void;
4
+ onReset?: () => void;
5
+ children: React.ReactNode;
6
+ };
7
+ type State = {
8
+ error: Error | null;
9
+ };
10
+ export declare class OverlayErrorBoundary extends React.Component<Props, State> {
11
+ state: State;
12
+ static getDerivedStateFromError(error: Error): State;
13
+ componentDidCatch(error: Error, info: React.ErrorInfo): void;
14
+ private reset;
15
+ render(): string | number | bigint | boolean | Iterable<React.ReactNode> | Promise<string | number | bigint | boolean | React.ReactPortal | React.ReactElement<unknown, string | React.JSXElementConstructor<any>> | Iterable<React.ReactNode> | null | undefined> | import("react/jsx-runtime").JSX.Element | null | undefined;
16
+ }
17
+ export {};
@@ -0,0 +1,62 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React from 'react';
4
+ const isDev = process.env.NODE_ENV !== 'production';
5
+ export class OverlayErrorBoundary extends React.Component {
6
+ state = {
7
+ error: null
8
+ };
9
+ static getDerivedStateFromError(error) {
10
+ return {
11
+ error
12
+ };
13
+ }
14
+ componentDidCatch(error, info) {
15
+ console.error('[better-editor] overlay crashed', error, info);
16
+ }
17
+ reset = ()=>{
18
+ this.props.onReset?.();
19
+ this.setState({
20
+ error: null
21
+ });
22
+ };
23
+ render() {
24
+ const { error } = this.state;
25
+ if (!error) return this.props.children;
26
+ return /*#__PURE__*/ _jsx("div", {
27
+ className: "better-editor better-editor--errored",
28
+ role: "alert",
29
+ children: /*#__PURE__*/ _jsxs("div", {
30
+ className: "better-editor__error",
31
+ children: [
32
+ /*#__PURE__*/ _jsx("h3", {
33
+ children: "Better Editor crashed"
34
+ }),
35
+ /*#__PURE__*/ _jsx("p", {
36
+ children: error.message || 'Unknown error.'
37
+ }),
38
+ isDev && error.stack ? /*#__PURE__*/ _jsx("pre", {
39
+ children: error.stack
40
+ }) : null,
41
+ /*#__PURE__*/ _jsxs("div", {
42
+ className: "better-editor__error-actions",
43
+ children: [
44
+ /*#__PURE__*/ _jsx("button", {
45
+ type: "button",
46
+ onClick: this.reset,
47
+ children: "Try again"
48
+ }),
49
+ /*#__PURE__*/ _jsx("button", {
50
+ type: "button",
51
+ onClick: this.props.onClose,
52
+ children: "Close editor"
53
+ })
54
+ ]
55
+ })
56
+ ]
57
+ })
58
+ });
59
+ }
60
+ }
61
+
62
+ //# sourceMappingURL=ErrorBoundary.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/ErrorBoundary.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\n\ntype Props = {\n onClose: () => void\n onReset?: () => void\n children: React.ReactNode\n}\n\ntype State = {\n error: Error | null\n}\n\nconst isDev = process.env.NODE_ENV !== 'production'\n\nexport class OverlayErrorBoundary extends React.Component<Props, State> {\n state: State = { error: null }\n\n static getDerivedStateFromError(error: Error): State {\n return { error }\n }\n\n componentDidCatch(error: Error, info: React.ErrorInfo) {\n console.error('[better-editor] overlay crashed', error, info)\n }\n\n private reset = () => {\n this.props.onReset?.()\n this.setState({ error: null })\n }\n\n render() {\n const { error } = this.state\n if (!error) return this.props.children\n\n return (\n <div className=\"better-editor better-editor--errored\" role=\"alert\">\n <div className=\"better-editor__error\">\n <h3>Better Editor crashed</h3>\n <p>{error.message || 'Unknown error.'}</p>\n {isDev && error.stack ? <pre>{error.stack}</pre> : null}\n <div className=\"better-editor__error-actions\">\n <button type=\"button\" onClick={this.reset}>\n Try again\n </button>\n <button type=\"button\" onClick={this.props.onClose}>\n Close editor\n </button>\n </div>\n </div>\n </div>\n )\n }\n}\n"],"names":["React","isDev","process","env","NODE_ENV","OverlayErrorBoundary","Component","state","error","getDerivedStateFromError","componentDidCatch","info","console","reset","props","onReset","setState","render","children","div","className","role","h3","p","message","stack","pre","button","type","onClick","onClose"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AAYzB,MAAMC,QAAQC,QAAQC,GAAG,CAACC,QAAQ,KAAK;AAEvC,OAAO,MAAMC,6BAA6BL,MAAMM,SAAS;IACvDC,QAAe;QAAEC,OAAO;IAAK,EAAC;IAE9B,OAAOC,yBAAyBD,KAAY,EAAS;QACnD,OAAO;YAAEA;QAAM;IACjB;IAEAE,kBAAkBF,KAAY,EAAEG,IAAqB,EAAE;QACrDC,QAAQJ,KAAK,CAAC,mCAAmCA,OAAOG;IAC1D;IAEQE,QAAQ;QACd,IAAI,CAACC,KAAK,CAACC,OAAO;QAClB,IAAI,CAACC,QAAQ,CAAC;YAAER,OAAO;QAAK;IAC9B,EAAC;IAEDS,SAAS;QACP,MAAM,EAAET,KAAK,EAAE,GAAG,IAAI,CAACD,KAAK;QAC5B,IAAI,CAACC,OAAO,OAAO,IAAI,CAACM,KAAK,CAACI,QAAQ;QAEtC,qBACE,KAACC;YAAIC,WAAU;YAAuCC,MAAK;sBACzD,cAAA,MAACF;gBAAIC,WAAU;;kCACb,KAACE;kCAAG;;kCACJ,KAACC;kCAAGf,MAAMgB,OAAO,IAAI;;oBACpBvB,SAASO,MAAMiB,KAAK,iBAAG,KAACC;kCAAKlB,MAAMiB,KAAK;yBAAU;kCACnD,MAACN;wBAAIC,WAAU;;0CACb,KAACO;gCAAOC,MAAK;gCAASC,SAAS,IAAI,CAAChB,KAAK;0CAAE;;0CAG3C,KAACc;gCAAOC,MAAK;gCAASC,SAAS,IAAI,CAACf,KAAK,CAACgB,OAAO;0CAAE;;;;;;;IAO7D;AACF"}
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import '../styles/overlay.css';
3
+ import '../styles/preview.css';
4
+ import '../styles/sidebar.css';
5
+ import '../styles/blocks-tab.css';
6
+ export type LiveEditorOverlayProps = {
7
+ onClose: () => void;
8
+ blocksField: string;
9
+ storageNamespace?: string;
10
+ adminPortalSelector?: string;
11
+ };
12
+ export declare const LiveEditorOverlay: React.FC<LiveEditorOverlayProps>;
@@ -0,0 +1,160 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { useCallback, useState } from 'react';
4
+ import { useLivePreviewContext } from '@payloadcms/ui';
5
+ import { PreviewFrame } from './PreviewFrame';
6
+ import { PreviewToolbar } from './PreviewToolbar';
7
+ import { Sidebar } from './sidebar/Sidebar';
8
+ import { useBetterEditorSettings } from '../state/useBetterEditorSettings';
9
+ import { useEditorHistory } from '../state/useEditorHistory';
10
+ import { useSidebarResize } from '../hooks/useSidebarResize';
11
+ import { useViewportState } from '../hooks/useViewportState';
12
+ import { useFullscreenOverlay } from '../hooks/useFullscreenOverlay';
13
+ import { useBlockActionMessages } from '../hooks/useBlockActionMessages';
14
+ import { useOverlayKeyboard } from '../hooks/useOverlayKeyboard';
15
+ import { useFocusTrap } from '../hooks/useFocusTrap';
16
+ import { OverlayProviders } from '../providers/OverlayProviders';
17
+ import '../styles/overlay.css';
18
+ import '../styles/preview.css';
19
+ import '../styles/sidebar.css';
20
+ import '../styles/blocks-tab.css';
21
+ const RESIZE_HANDLE_PX = 6;
22
+ const classes = (...parts)=>parts.filter(Boolean).join(' ');
23
+ export const LiveEditorOverlay = ({ onClose, blocksField, storageNamespace, adminPortalSelector })=>{
24
+ // Selection state lives outside OverlayProviders so the error boundary's
25
+ // onReset can clear it without remounting providers.
26
+ const [selectedBlockPath, setSelectedBlockPath] = useState(null);
27
+ const clearSelection = useCallback(()=>setSelectedBlockPath(null), []);
28
+ return /*#__PURE__*/ _jsx(OverlayProviders, {
29
+ onClose: onClose,
30
+ onReset: clearSelection,
31
+ storageNamespace: storageNamespace,
32
+ adminPortalSelector: adminPortalSelector,
33
+ children: /*#__PURE__*/ _jsx(LiveEditorOverlayInner, {
34
+ onClose: onClose,
35
+ blocksField: blocksField,
36
+ selectedBlockPath: selectedBlockPath,
37
+ setSelectedBlockPath: setSelectedBlockPath
38
+ })
39
+ });
40
+ };
41
+ const LiveEditorOverlayInner = ({ onClose, blocksField, selectedBlockPath, setSelectedBlockPath })=>{
42
+ const settings = useBetterEditorSettings();
43
+ const history = useEditorHistory();
44
+ const { previewURL } = useLivePreviewContext();
45
+ const { sidebarWidth, isResizing, onResizeStart, onResizeKeyDown } = useSidebarResize(settings.sidebarPosition);
46
+ const { viewport, setViewport, setResponsiveWidth, viewportWidth } = useViewportState(settings);
47
+ // Live-observed iframe width; PreviewFrame reports it via the
48
+ // ResizeObserver, the toolbar's width-chip reads it.
49
+ const [iframeWidth, setIframeWidth] = useState(null);
50
+ const [isFullscreen, setIsFullscreen] = useState(false);
51
+ const toggleFullscreen = useCallback(()=>setIsFullscreen((v)=>!v), []);
52
+ const exitFullscreen = useCallback(()=>setIsFullscreen(false), []);
53
+ const overlayRef = useFullscreenOverlay(isFullscreen, exitFullscreen);
54
+ useFocusTrap(overlayRef);
55
+ const clearSelection = useCallback(()=>setSelectedBlockPath(null), [
56
+ setSelectedBlockPath
57
+ ]);
58
+ const { addBelowRequestId } = useBlockActionMessages({
59
+ selectedBlockPath,
60
+ setSelectedBlockPath
61
+ });
62
+ useOverlayKeyboard({
63
+ onClose,
64
+ history
65
+ });
66
+ const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
67
+ const toggleSidebar = useCallback(()=>setSidebarCollapsed((v)=>!v), []);
68
+ const [interactMode, setInteractMode] = useState(false);
69
+ const toggleInteractMode = useCallback(()=>setInteractMode((v)=>!v), []);
70
+ const isLeft = settings.sidebarPosition === 'left';
71
+ const showSidebar = !sidebarCollapsed;
72
+ const gridTemplateColumns = !showSidebar ? '1fr' : isLeft ? `${sidebarWidth}px ${RESIZE_HANDLE_PX}px 1fr` : `1fr ${RESIZE_HANDLE_PX}px ${sidebarWidth}px`;
73
+ return /*#__PURE__*/ _jsx("div", {
74
+ ref: overlayRef,
75
+ className: classes('better-editor', isResizing && 'better-editor--resizing', isFullscreen && 'better-editor--fullscreen'),
76
+ role: "dialog",
77
+ "aria-modal": "true",
78
+ "aria-label": "Better Editor",
79
+ tabIndex: -1,
80
+ children: /*#__PURE__*/ _jsxs("div", {
81
+ className: "better-editor__body",
82
+ style: {
83
+ gridTemplateColumns
84
+ },
85
+ children: [
86
+ /*#__PURE__*/ _jsxs("div", {
87
+ className: "better-editor__preview",
88
+ style: {
89
+ order: isLeft ? 2 : 0
90
+ },
91
+ children: [
92
+ /*#__PURE__*/ _jsx(PreviewToolbar, {
93
+ history: history,
94
+ viewport: viewport,
95
+ onViewportChange: setViewport,
96
+ iframeWidth: iframeWidth,
97
+ isFullscreen: isFullscreen,
98
+ onFullscreenToggle: toggleFullscreen,
99
+ interactMode: interactMode,
100
+ onInteractToggle: toggleInteractMode,
101
+ sidebarCollapsed: sidebarCollapsed,
102
+ onSidebarToggle: toggleSidebar
103
+ }),
104
+ /*#__PURE__*/ _jsx("div", {
105
+ className: "better-editor__preview-stage",
106
+ children: /*#__PURE__*/ _jsx(PreviewFrame, {
107
+ previewURL: previewURL,
108
+ hoverColorTopLevel: settings.hoverColorTopLevel,
109
+ hoverColorNested: settings.hoverColorNested,
110
+ hoverOutlineWidth: settings.hoverOutlineWidth,
111
+ showHoverToolbar: settings.showHoverToolbar,
112
+ hoverToolbarPosition: settings.hoverToolbarPosition,
113
+ selectedBlockPath: selectedBlockPath,
114
+ interactMode: interactMode,
115
+ viewport: viewport,
116
+ viewportWidth: viewportWidth,
117
+ resizable: viewport === 'responsive',
118
+ onResize: setResponsiveWidth,
119
+ onIframeWidthChange: setIframeWidth
120
+ })
121
+ })
122
+ ]
123
+ }),
124
+ showSidebar ? /*#__PURE__*/ _jsxs(_Fragment, {
125
+ children: [
126
+ /*#__PURE__*/ _jsx("div", {
127
+ className: "better-editor__resize-handle",
128
+ style: {
129
+ order: 1
130
+ },
131
+ role: "separator",
132
+ "aria-orientation": "vertical",
133
+ "aria-label": "Resize sidebar (use ← / → arrow keys)",
134
+ "aria-valuenow": sidebarWidth,
135
+ tabIndex: 0,
136
+ onMouseDown: onResizeStart,
137
+ onKeyDown: onResizeKeyDown
138
+ }),
139
+ /*#__PURE__*/ _jsx("aside", {
140
+ className: "better-editor__sidebar",
141
+ style: {
142
+ order: isLeft ? 0 : 2
143
+ },
144
+ children: /*#__PURE__*/ _jsx(Sidebar, {
145
+ selectedBlockPath: selectedBlockPath,
146
+ onClearSelection: clearSelection,
147
+ onSelectPath: setSelectedBlockPath,
148
+ forceFullWidthFields: settings.forceFullWidthFields,
149
+ blocksField: blocksField,
150
+ addBelowRequestId: addBelowRequestId
151
+ })
152
+ })
153
+ ]
154
+ }) : null
155
+ ]
156
+ })
157
+ });
158
+ };
159
+
160
+ //# sourceMappingURL=LiveEditorOverlay.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/LiveEditorOverlay.tsx"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useState } from 'react'\nimport { useLivePreviewContext } from '@payloadcms/ui'\nimport { PreviewFrame } from './PreviewFrame'\nimport { PreviewToolbar } from './PreviewToolbar'\nimport { Sidebar } from './sidebar/Sidebar'\nimport { useBetterEditorSettings } from '../state/useBetterEditorSettings'\nimport { useEditorHistory } from '../state/useEditorHistory'\nimport { useSidebarResize } from '../hooks/useSidebarResize'\nimport { useViewportState } from '../hooks/useViewportState'\nimport { useFullscreenOverlay } from '../hooks/useFullscreenOverlay'\nimport { useBlockActionMessages } from '../hooks/useBlockActionMessages'\nimport { useOverlayKeyboard } from '../hooks/useOverlayKeyboard'\nimport { useFocusTrap } from '../hooks/useFocusTrap'\nimport { OverlayProviders } from '../providers/OverlayProviders'\nimport '../styles/overlay.css'\nimport '../styles/preview.css'\nimport '../styles/sidebar.css'\nimport '../styles/blocks-tab.css'\n\nexport type LiveEditorOverlayProps = {\n onClose: () => void\n blocksField: string\n storageNamespace?: string\n adminPortalSelector?: string\n}\n\nconst RESIZE_HANDLE_PX = 6\n\nconst classes = (...parts: Array<string | false | null | undefined>): string =>\n parts.filter(Boolean).join(' ')\n\nexport const LiveEditorOverlay: React.FC<LiveEditorOverlayProps> = ({\n onClose,\n blocksField,\n storageNamespace,\n adminPortalSelector,\n}) => {\n // Selection state lives outside OverlayProviders so the error boundary's\n // onReset can clear it without remounting providers.\n const [selectedBlockPath, setSelectedBlockPath] = useState<string | null>(null)\n const clearSelection = useCallback(() => setSelectedBlockPath(null), [])\n\n return (\n <OverlayProviders\n onClose={onClose}\n onReset={clearSelection}\n storageNamespace={storageNamespace}\n adminPortalSelector={adminPortalSelector}\n >\n <LiveEditorOverlayInner\n onClose={onClose}\n blocksField={blocksField}\n selectedBlockPath={selectedBlockPath}\n setSelectedBlockPath={setSelectedBlockPath}\n />\n </OverlayProviders>\n )\n}\n\ntype InnerProps = LiveEditorOverlayProps & {\n selectedBlockPath: string | null\n setSelectedBlockPath: React.Dispatch<React.SetStateAction<string | null>>\n}\n\nconst LiveEditorOverlayInner: React.FC<InnerProps> = ({\n onClose,\n blocksField,\n selectedBlockPath,\n setSelectedBlockPath,\n}) => {\n const settings = useBetterEditorSettings()\n const history = useEditorHistory()\n const { previewURL } = useLivePreviewContext()\n\n const { sidebarWidth, isResizing, onResizeStart, onResizeKeyDown } = useSidebarResize(\n settings.sidebarPosition,\n )\n const {\n viewport,\n setViewport,\n setResponsiveWidth,\n viewportWidth,\n } = useViewportState(settings)\n // Live-observed iframe width; PreviewFrame reports it via the\n // ResizeObserver, the toolbar's width-chip reads it.\n const [iframeWidth, setIframeWidth] = useState<number | null>(null)\n\n const [isFullscreen, setIsFullscreen] = useState(false)\n const toggleFullscreen = useCallback(() => setIsFullscreen((v) => !v), [])\n const exitFullscreen = useCallback(() => setIsFullscreen(false), [])\n const overlayRef = useFullscreenOverlay(isFullscreen, exitFullscreen)\n useFocusTrap(overlayRef)\n\n const clearSelection = useCallback(\n () => setSelectedBlockPath(null),\n [setSelectedBlockPath],\n )\n\n const { addBelowRequestId } = useBlockActionMessages({\n selectedBlockPath,\n setSelectedBlockPath,\n })\n\n useOverlayKeyboard({ onClose, history })\n\n const [sidebarCollapsed, setSidebarCollapsed] = useState(false)\n const toggleSidebar = useCallback(() => setSidebarCollapsed((v) => !v), [])\n\n const [interactMode, setInteractMode] = useState(false)\n const toggleInteractMode = useCallback(() => setInteractMode((v) => !v), [])\n\n const isLeft = settings.sidebarPosition === 'left'\n const showSidebar = !sidebarCollapsed\n const gridTemplateColumns = !showSidebar\n ? '1fr'\n : isLeft\n ? `${sidebarWidth}px ${RESIZE_HANDLE_PX}px 1fr`\n : `1fr ${RESIZE_HANDLE_PX}px ${sidebarWidth}px`\n\n return (\n <div\n ref={overlayRef}\n className={classes(\n 'better-editor',\n isResizing && 'better-editor--resizing',\n isFullscreen && 'better-editor--fullscreen',\n )}\n role=\"dialog\"\n aria-modal=\"true\"\n aria-label=\"Better Editor\"\n tabIndex={-1}\n >\n <div className=\"better-editor__body\" style={{ gridTemplateColumns }}>\n <div className=\"better-editor__preview\" style={{ order: isLeft ? 2 : 0 }}>\n <PreviewToolbar\n history={history}\n viewport={viewport}\n onViewportChange={setViewport}\n iframeWidth={iframeWidth}\n isFullscreen={isFullscreen}\n onFullscreenToggle={toggleFullscreen}\n interactMode={interactMode}\n onInteractToggle={toggleInteractMode}\n sidebarCollapsed={sidebarCollapsed}\n onSidebarToggle={toggleSidebar}\n />\n <div className=\"better-editor__preview-stage\">\n <PreviewFrame\n previewURL={previewURL}\n hoverColorTopLevel={settings.hoverColorTopLevel}\n hoverColorNested={settings.hoverColorNested}\n hoverOutlineWidth={settings.hoverOutlineWidth}\n showHoverToolbar={settings.showHoverToolbar}\n hoverToolbarPosition={settings.hoverToolbarPosition}\n selectedBlockPath={selectedBlockPath}\n interactMode={interactMode}\n viewport={viewport}\n viewportWidth={viewportWidth}\n resizable={viewport === 'responsive'}\n onResize={setResponsiveWidth}\n onIframeWidthChange={setIframeWidth}\n />\n </div>\n </div>\n {showSidebar ? (\n <>\n <div\n className=\"better-editor__resize-handle\"\n style={{ order: 1 }}\n role=\"separator\"\n aria-orientation=\"vertical\"\n aria-label=\"Resize sidebar (use ← / → arrow keys)\"\n aria-valuenow={sidebarWidth}\n tabIndex={0}\n onMouseDown={onResizeStart}\n onKeyDown={onResizeKeyDown}\n />\n <aside\n className=\"better-editor__sidebar\"\n style={{ order: isLeft ? 0 : 2 }}\n >\n <Sidebar\n selectedBlockPath={selectedBlockPath}\n onClearSelection={clearSelection}\n onSelectPath={setSelectedBlockPath}\n forceFullWidthFields={settings.forceFullWidthFields}\n blocksField={blocksField}\n addBelowRequestId={addBelowRequestId}\n />\n </aside>\n </>\n ) : null}\n </div>\n </div>\n )\n}\n\n"],"names":["React","useCallback","useState","useLivePreviewContext","PreviewFrame","PreviewToolbar","Sidebar","useBetterEditorSettings","useEditorHistory","useSidebarResize","useViewportState","useFullscreenOverlay","useBlockActionMessages","useOverlayKeyboard","useFocusTrap","OverlayProviders","RESIZE_HANDLE_PX","classes","parts","filter","Boolean","join","LiveEditorOverlay","onClose","blocksField","storageNamespace","adminPortalSelector","selectedBlockPath","setSelectedBlockPath","clearSelection","onReset","LiveEditorOverlayInner","settings","history","previewURL","sidebarWidth","isResizing","onResizeStart","onResizeKeyDown","sidebarPosition","viewport","setViewport","setResponsiveWidth","viewportWidth","iframeWidth","setIframeWidth","isFullscreen","setIsFullscreen","toggleFullscreen","v","exitFullscreen","overlayRef","addBelowRequestId","sidebarCollapsed","setSidebarCollapsed","toggleSidebar","interactMode","setInteractMode","toggleInteractMode","isLeft","showSidebar","gridTemplateColumns","div","ref","className","role","aria-modal","aria-label","tabIndex","style","order","onViewportChange","onFullscreenToggle","onInteractToggle","onSidebarToggle","hoverColorTopLevel","hoverColorNested","hoverOutlineWidth","showHoverToolbar","hoverToolbarPosition","resizable","onResize","onIframeWidthChange","aria-orientation","aria-valuenow","onMouseDown","onKeyDown","aside","onClearSelection","onSelectPath","forceFullWidthFields"],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ export type LiveEditorToggleProps = {
3
+ blocksField: string;
4
+ adminPortalSelector?: string;
5
+ storageNamespace?: string;
6
+ };
7
+ export declare const LiveEditorToggle: React.FC<LiveEditorToggleProps>;
@@ -0,0 +1,84 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { createPortal } from 'react-dom';
5
+ import { useDocumentInfo, useLivePreviewContext, usePreferences } from '@payloadcms/ui';
6
+ import { LiveEditorOverlay } from './LiveEditorOverlay';
7
+ import { useMainWrapperPortal } from '../hooks/useMainWrapperPortal';
8
+ import { buildStorageKeys } from '../internal/storage-keys';
9
+ import { LayoutIcon } from './icons';
10
+ export const LiveEditorToggle = ({ blocksField, adminPortalSelector, storageNamespace })=>{
11
+ const [open, setOpen] = useState(false);
12
+ const { collectionSlug, globalSlug } = useDocumentInfo();
13
+ const { previewURL } = useLivePreviewContext();
14
+ const { getPreference, setPreference } = usePreferences();
15
+ const storageKeys = useMemo(()=>buildStorageKeys(storageNamespace), [
16
+ storageNamespace
17
+ ]);
18
+ const prefKey = storageKeys.togglePreference(collectionSlug, globalSlug);
19
+ // Tracks the prefKey we've successfully hydrated against so persistence
20
+ // can't fire with the initial `false` before the read resolves, and so
21
+ // switching documents reseeds without clobbering the new doc's pref.
22
+ const hydratedKeyRef = useRef(null);
23
+ useEffect(()=>{
24
+ let cancelled = false;
25
+ hydratedKeyRef.current = null;
26
+ void getPreference(prefKey).then((pref)=>{
27
+ if (cancelled) return;
28
+ hydratedKeyRef.current = prefKey;
29
+ setOpen(Boolean(pref?.open));
30
+ });
31
+ return ()=>{
32
+ cancelled = true;
33
+ };
34
+ }, [
35
+ prefKey,
36
+ getPreference
37
+ ]);
38
+ useEffect(()=>{
39
+ if (hydratedKeyRef.current !== prefKey) return;
40
+ void setPreference(prefKey, {
41
+ open
42
+ }, true);
43
+ }, [
44
+ open,
45
+ prefKey,
46
+ setPreference
47
+ ]);
48
+ const handleToggle = useCallback(()=>setOpen((v)=>!v), []);
49
+ const handleClose = useCallback(()=>setOpen(false), []);
50
+ const mountNode = useMainWrapperPortal(open, adminPortalSelector);
51
+ const label = open ? 'Close Better Editor' : 'Open Better Editor';
52
+ // Mirror Payload's official live-preview behaviour: only surface the
53
+ // toggle once a previewURL is actually resolvable (collection has
54
+ // `admin.livePreview.url` configured AND the document has the data
55
+ // the URL function depends on, e.g. slug). Hiding the button avoids
56
+ // the misleading "Loading preview URL…" / "not configured" empty
57
+ // states inside the overlay entirely.
58
+ if (!previewURL) return null;
59
+ return /*#__PURE__*/ _jsxs(_Fragment, {
60
+ children: [
61
+ /*#__PURE__*/ _jsx("button", {
62
+ "aria-label": label,
63
+ "aria-pressed": open,
64
+ className: "preview-btn",
65
+ onClick: handleToggle,
66
+ title: label,
67
+ type: "button",
68
+ style: open ? {
69
+ borderColor: 'var(--theme-elevation-300)',
70
+ backgroundColor: 'var(--theme-elevation-100)'
71
+ } : undefined,
72
+ children: /*#__PURE__*/ _jsx(LayoutIcon, {})
73
+ }),
74
+ open && mountNode ? /*#__PURE__*/ createPortal(/*#__PURE__*/ _jsx(LiveEditorOverlay, {
75
+ onClose: handleClose,
76
+ blocksField: blocksField,
77
+ storageNamespace: storageNamespace,
78
+ adminPortalSelector: adminPortalSelector
79
+ }), mountNode) : null
80
+ ]
81
+ });
82
+ };
83
+
84
+ //# sourceMappingURL=LiveEditorToggle.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/admin/LiveEditorToggle.tsx"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'\nimport { createPortal } from 'react-dom'\nimport { useDocumentInfo, useLivePreviewContext, usePreferences } from '@payloadcms/ui'\nimport { LiveEditorOverlay } from './LiveEditorOverlay'\nimport { useMainWrapperPortal } from '../hooks/useMainWrapperPortal'\nimport { buildStorageKeys } from '../internal/storage-keys'\nimport { LayoutIcon } from './icons'\n\ntype Pref = { open?: boolean }\n\nexport type LiveEditorToggleProps = {\n blocksField: string\n adminPortalSelector?: string\n storageNamespace?: string\n}\n\nexport const LiveEditorToggle: React.FC<LiveEditorToggleProps> = ({\n blocksField,\n adminPortalSelector,\n storageNamespace,\n}) => {\n const [open, setOpen] = useState(false)\n const { collectionSlug, globalSlug } = useDocumentInfo()\n const { previewURL } = useLivePreviewContext()\n const { getPreference, setPreference } = usePreferences()\n const storageKeys = useMemo(() => buildStorageKeys(storageNamespace), [storageNamespace])\n const prefKey = storageKeys.togglePreference(collectionSlug, globalSlug)\n\n // Tracks the prefKey we've successfully hydrated against so persistence\n // can't fire with the initial `false` before the read resolves, and so\n // switching documents reseeds without clobbering the new doc's pref.\n const hydratedKeyRef = useRef<string | null>(null)\n\n useEffect(() => {\n let cancelled = false\n hydratedKeyRef.current = null\n void getPreference<Pref>(prefKey).then((pref) => {\n if (cancelled) return\n hydratedKeyRef.current = prefKey\n setOpen(Boolean(pref?.open))\n })\n return () => {\n cancelled = true\n }\n }, [prefKey, getPreference])\n\n useEffect(() => {\n if (hydratedKeyRef.current !== prefKey) return\n void setPreference<Pref>(prefKey, { open }, true)\n }, [open, prefKey, setPreference])\n\n const handleToggle = useCallback(() => setOpen((v) => !v), [])\n const handleClose = useCallback(() => setOpen(false), [])\n\n const mountNode = useMainWrapperPortal(open, adminPortalSelector)\n const label = open ? 'Close Better Editor' : 'Open Better Editor'\n\n // Mirror Payload's official live-preview behaviour: only surface the\n // toggle once a previewURL is actually resolvable (collection has\n // `admin.livePreview.url` configured AND the document has the data\n // the URL function depends on, e.g. slug). Hiding the button avoids\n // the misleading \"Loading preview URL…\" / \"not configured\" empty\n // states inside the overlay entirely.\n if (!previewURL) return null\n\n return (\n <>\n <button\n aria-label={label}\n aria-pressed={open}\n className=\"preview-btn\"\n onClick={handleToggle}\n title={label}\n type=\"button\"\n style={\n open\n ? {\n borderColor: 'var(--theme-elevation-300)',\n backgroundColor: 'var(--theme-elevation-100)',\n }\n : undefined\n }\n >\n <LayoutIcon />\n </button>\n\n {open && mountNode\n ? createPortal(\n <LiveEditorOverlay\n onClose={handleClose}\n blocksField={blocksField}\n storageNamespace={storageNamespace}\n adminPortalSelector={adminPortalSelector}\n />,\n mountNode,\n )\n : null}\n </>\n )\n}\n"],"names":["React","useCallback","useEffect","useMemo","useRef","useState","createPortal","useDocumentInfo","useLivePreviewContext","usePreferences","LiveEditorOverlay","useMainWrapperPortal","buildStorageKeys","LayoutIcon","LiveEditorToggle","blocksField","adminPortalSelector","storageNamespace","open","setOpen","collectionSlug","globalSlug","previewURL","getPreference","setPreference","storageKeys","prefKey","togglePreference","hydratedKeyRef","cancelled","current","then","pref","Boolean","handleToggle","v","handleClose","mountNode","label","button","aria-label","aria-pressed","className","onClick","title","type","style","borderColor","backgroundColor","undefined","onClose"],"mappings":"AAAA;;AAEA,OAAOA,SAASC,WAAW,EAAEC,SAAS,EAAEC,OAAO,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAChF,SAASC,YAAY,QAAQ,YAAW;AACxC,SAASC,eAAe,EAAEC,qBAAqB,EAAEC,cAAc,QAAQ,iBAAgB;AACvF,SAASC,iBAAiB,QAAQ,sBAAqB;AACvD,SAASC,oBAAoB,QAAQ,gCAA+B;AACpE,SAASC,gBAAgB,QAAQ,2BAA0B;AAC3D,SAASC,UAAU,QAAQ,UAAS;AAUpC,OAAO,MAAMC,mBAAoD,CAAC,EAChEC,WAAW,EACXC,mBAAmB,EACnBC,gBAAgB,EACjB;IACC,MAAM,CAACC,MAAMC,QAAQ,GAAGd,SAAS;IACjC,MAAM,EAAEe,cAAc,EAAEC,UAAU,EAAE,GAAGd;IACvC,MAAM,EAAEe,UAAU,EAAE,GAAGd;IACvB,MAAM,EAAEe,aAAa,EAAEC,aAAa,EAAE,GAAGf;IACzC,MAAMgB,cAActB,QAAQ,IAAMS,iBAAiBK,mBAAmB;QAACA;KAAiB;IACxF,MAAMS,UAAUD,YAAYE,gBAAgB,CAACP,gBAAgBC;IAE7D,wEAAwE;IACxE,uEAAuE;IACvE,qEAAqE;IACrE,MAAMO,iBAAiBxB,OAAsB;IAE7CF,UAAU;QACR,IAAI2B,YAAY;QAChBD,eAAeE,OAAO,GAAG;QACzB,KAAKP,cAAoBG,SAASK,IAAI,CAAC,CAACC;YACtC,IAAIH,WAAW;YACfD,eAAeE,OAAO,GAAGJ;YACzBP,QAAQc,QAAQD,MAAMd;QACxB;QACA,OAAO;YACLW,YAAY;QACd;IACF,GAAG;QAACH;QAASH;KAAc;IAE3BrB,UAAU;QACR,IAAI0B,eAAeE,OAAO,KAAKJ,SAAS;QACxC,KAAKF,cAAoBE,SAAS;YAAER;QAAK,GAAG;IAC9C,GAAG;QAACA;QAAMQ;QAASF;KAAc;IAEjC,MAAMU,eAAejC,YAAY,IAAMkB,QAAQ,CAACgB,IAAM,CAACA,IAAI,EAAE;IAC7D,MAAMC,cAAcnC,YAAY,IAAMkB,QAAQ,QAAQ,EAAE;IAExD,MAAMkB,YAAY1B,qBAAqBO,MAAMF;IAC7C,MAAMsB,QAAQpB,OAAO,wBAAwB;IAE7C,qEAAqE;IACrE,kEAAkE;IAClE,mEAAmE;IACnE,oEAAoE;IACpE,iEAAiE;IACjE,sCAAsC;IACtC,IAAI,CAACI,YAAY,OAAO;IAExB,qBACE;;0BACE,KAACiB;gBACCC,cAAYF;gBACZG,gBAAcvB;gBACdwB,WAAU;gBACVC,SAAST;gBACTU,OAAON;gBACPO,MAAK;gBACLC,OACE5B,OACI;oBACE6B,aAAa;oBACbC,iBAAiB;gBACnB,IACAC;0BAGN,cAAA,KAACpC;;YAGFK,QAAQmB,0BACL/B,2BACE,KAACI;gBACCwC,SAASd;gBACTrB,aAAaA;gBACbE,kBAAkBA;gBAClBD,qBAAqBA;gBAEvBqB,aAEF;;;AAGV,EAAC"}
@@ -0,0 +1,22 @@
1
+ import React from 'react';
2
+ import type { HoverToolbarPosition } from '../internal/constants';
3
+ import type { Viewport } from './ViewportToggle';
4
+ export type PreviewFrameProps = {
5
+ previewURL: string | undefined;
6
+ hoverColorTopLevel: string;
7
+ hoverColorNested: string;
8
+ hoverOutlineWidth: number;
9
+ showHoverToolbar: boolean;
10
+ hoverToolbarPosition: HoverToolbarPosition;
11
+ selectedBlockPath: string | null;
12
+ /** When true, clicks pass through to the consumer page and the
13
+ * hover/selection affordances are suppressed so users can interact
14
+ * with forms, accordions, links inside the preview. */
15
+ interactMode: boolean;
16
+ viewport?: Viewport;
17
+ viewportWidth?: number | null;
18
+ resizable?: boolean;
19
+ onResize?: (next: number) => void;
20
+ onIframeWidthChange?: (width: number) => void;
21
+ };
22
+ export declare const PreviewFrame: React.FC<PreviewFrameProps>;
@@ -0,0 +1,137 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
+ import { postToParent } from '../internal/postmessage';
5
+ import { useIframeResizeObserver } from '../hooks/useIframeResizeObserver';
6
+ import { usePreviewHandleDrag } from '../hooks/usePreviewHandleDrag';
7
+ import { useLatestRef } from '../hooks/useLatestRef';
8
+ import { usePreviewBinding } from '../hooks/usePreviewBinding';
9
+ import { usePreviewSettingsSync } from '../hooks/usePreviewSettingsSync';
10
+ import { usePreviewSelectionSync } from '../hooks/usePreviewSelectionSync';
11
+ export const PreviewFrame = ({ previewURL, hoverColorTopLevel, hoverColorNested, hoverOutlineWidth, showHoverToolbar, hoverToolbarPosition, selectedBlockPath, interactMode, viewport, viewportWidth, resizable = false, onResize, onIframeWidthChange })=>{
12
+ const iframeRef = useRef(null);
13
+ const [isLoading, setIsLoading] = useState(true);
14
+ const interactModeRef = useLatestRef(interactMode);
15
+ const { isResizing, onHandleMouseDown } = usePreviewHandleDrag({
16
+ resizable,
17
+ viewportWidth,
18
+ onResize
19
+ });
20
+ useIframeResizeObserver(iframeRef, onIframeWidthChange);
21
+ useEffect(()=>{
22
+ setIsLoading(true);
23
+ }, [
24
+ previewURL
25
+ ]);
26
+ const onFocusBlock = useCallback((id)=>{
27
+ postToParent({
28
+ type: 'focus-block',
29
+ id
30
+ });
31
+ }, []);
32
+ const onBlockAction = useCallback((id, action)=>{
33
+ postToParent({
34
+ type: 'block-action',
35
+ id,
36
+ action
37
+ });
38
+ }, []);
39
+ const settings = useMemo(()=>({
40
+ hoverColorTopLevel,
41
+ hoverColorNested,
42
+ hoverOutlineWidth,
43
+ showHoverToolbar,
44
+ hoverToolbarPosition
45
+ }), [
46
+ hoverColorTopLevel,
47
+ hoverColorNested,
48
+ hoverOutlineWidth,
49
+ showHoverToolbar,
50
+ hoverToolbarPosition
51
+ ]);
52
+ const { controllerRef, isBoundRef } = usePreviewBinding({
53
+ iframeRef,
54
+ settings,
55
+ interactModeRef,
56
+ onFocusBlock,
57
+ onBlockAction,
58
+ onLoadingChange: setIsLoading
59
+ });
60
+ usePreviewSettingsSync({
61
+ iframeRef,
62
+ controllerRef,
63
+ isBoundRef,
64
+ settings,
65
+ onBlockAction
66
+ });
67
+ usePreviewSelectionSync({
68
+ iframeRef,
69
+ controllerRef,
70
+ selectedBlockPath,
71
+ interactMode,
72
+ previewURL
73
+ });
74
+ const constrained = typeof viewportWidth === 'number' && viewportWidth > 0;
75
+ const viewportClassName = [
76
+ 'better-editor-frame__viewport',
77
+ constrained && 'better-editor-frame__viewport--constrained',
78
+ constrained && viewport === 'tablet' && 'better-editor-frame__viewport--tablet',
79
+ constrained && viewport === 'mobile' && 'better-editor-frame__viewport--mobile',
80
+ resizable && 'better-editor-frame__viewport--resizable',
81
+ isResizing && 'better-editor-frame__viewport--resizing'
82
+ ].filter(Boolean).join(' ');
83
+ const iframeStyle = constrained ? {
84
+ width: `${viewportWidth}px`,
85
+ maxWidth: '100%'
86
+ } : undefined;
87
+ // The iframe stays in the same wrapper across viewport modes so React
88
+ // doesn't remount it — that would reload the page and drop the
89
+ // ResizeObserver.
90
+ return /*#__PURE__*/ _jsxs("div", {
91
+ className: viewportClassName,
92
+ children: [
93
+ resizable ? /*#__PURE__*/ _jsx("div", {
94
+ className: "better-editor-frame__handle better-editor-frame__handle--left",
95
+ role: "separator",
96
+ "aria-orientation": "vertical",
97
+ "aria-label": "Resize preview from left",
98
+ onMouseDown: onHandleMouseDown('left')
99
+ }) : null,
100
+ /*#__PURE__*/ _jsx("iframe", {
101
+ ref: iframeRef,
102
+ className: "better-editor-frame",
103
+ src: previewURL,
104
+ title: "Better Editor preview",
105
+ style: iframeStyle
106
+ }),
107
+ isLoading ? /*#__PURE__*/ _jsxs("div", {
108
+ className: "better-editor-frame__skeleton",
109
+ role: "status",
110
+ "aria-label": "Loading preview",
111
+ children: [
112
+ /*#__PURE__*/ _jsx("div", {
113
+ className: "better-editor-frame__skeleton-bar better-editor-frame__skeleton-bar--lg"
114
+ }),
115
+ /*#__PURE__*/ _jsx("div", {
116
+ className: "better-editor-frame__skeleton-bar"
117
+ }),
118
+ /*#__PURE__*/ _jsx("div", {
119
+ className: "better-editor-frame__skeleton-bar better-editor-frame__skeleton-bar--sm"
120
+ }),
121
+ /*#__PURE__*/ _jsx("div", {
122
+ className: "better-editor-frame__skeleton-block"
123
+ })
124
+ ]
125
+ }) : null,
126
+ resizable ? /*#__PURE__*/ _jsx("div", {
127
+ className: "better-editor-frame__handle better-editor-frame__handle--right",
128
+ role: "separator",
129
+ "aria-orientation": "vertical",
130
+ "aria-label": "Resize preview from right",
131
+ onMouseDown: onHandleMouseDown('right')
132
+ }) : null
133
+ ]
134
+ });
135
+ };
136
+
137
+ //# sourceMappingURL=PreviewFrame.js.map