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
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/usePreviewHandleDrag.ts"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport { clampViewport } from '../internal/limits'\n\nexport type UsePreviewHandleDragOptions = {\n resizable: boolean\n viewportWidth?: number | null\n onResize?: (next: number) => void\n}\n\nexport type UsePreviewHandleDragReturn = {\n isResizing: boolean\n onHandleMouseDown: (side: 'left' | 'right') => (e: React.MouseEvent) => void\n}\n\nexport const usePreviewHandleDrag = ({\n resizable,\n viewportWidth,\n onResize,\n}: UsePreviewHandleDragOptions): UsePreviewHandleDragReturn => {\n const [isResizing, setIsResizing] = useState(false)\n\n // Track in-flight drag so unmount mid-drag can release body styles + listeners.\n const dragCleanupRef = useRef<(() => void) | null>(null)\n const isMountedRef = useRef(true)\n useEffect(() => {\n isMountedRef.current = true\n return () => {\n isMountedRef.current = false\n dragCleanupRef.current?.()\n }\n }, [])\n\n const onHandleMouseDown = useCallback(\n (side: 'left' | 'right') => (e: React.MouseEvent) => {\n if (!resizable || !onResize || !viewportWidth) return\n e.preventDefault()\n const startX = e.clientX\n const startWidth = viewportWidth\n // Iframe is centered; dragging either edge by N px symmetrically grows\n // the width by 2N. Right handle: positive delta increases width.\n const dir = side === 'right' ? 2 : -2\n setIsResizing(true)\n const onMove = (ev: MouseEvent) => {\n onResize(clampViewport(startWidth + (ev.clientX - startX) * dir))\n }\n const cleanup = () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', onUp)\n document.body.style.cursor = ''\n document.body.style.userSelect = ''\n if (isMountedRef.current) setIsResizing(false)\n dragCleanupRef.current = null\n }\n const onUp = () => cleanup()\n dragCleanupRef.current = cleanup\n document.body.style.cursor = 'ew-resize'\n document.body.style.userSelect = 'none'\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', onUp)\n },\n [resizable, onResize, viewportWidth],\n )\n\n return { isResizing, onHandleMouseDown }\n}\n"],"names":["React","useCallback","useEffect","useRef","useState","clampViewport","usePreviewHandleDrag","resizable","viewportWidth","onResize","isResizing","setIsResizing","dragCleanupRef","isMountedRef","current","onHandleMouseDown","side","e","preventDefault","startX","clientX","startWidth","dir","onMove","ev","cleanup","window","removeEventListener","onUp","document","body","style","cursor","userSelect","addEventListener"],"mappings":"AAAA;AAEA,OAAOA,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACvE,SAASC,aAAa,QAAQ,qBAAoB;AAalD,OAAO,MAAMC,uBAAuB,CAAC,EACnCC,SAAS,EACTC,aAAa,EACbC,QAAQ,EACoB;IAC5B,MAAM,CAACC,YAAYC,cAAc,GAAGP,SAAS;IAE7C,gFAAgF;IAChF,MAAMQ,iBAAiBT,OAA4B;IACnD,MAAMU,eAAeV,OAAO;IAC5BD,UAAU;QACRW,aAAaC,OAAO,GAAG;QACvB,OAAO;YACLD,aAAaC,OAAO,GAAG;YACvBF,eAAeE,OAAO;QACxB;IACF,GAAG,EAAE;IAEL,MAAMC,oBAAoBd,YACxB,CAACe,OAA2B,CAACC;YAC3B,IAAI,CAACV,aAAa,CAACE,YAAY,CAACD,eAAe;YAC/CS,EAAEC,cAAc;YAChB,MAAMC,SAASF,EAAEG,OAAO;YACxB,MAAMC,aAAab;YACnB,uEAAuE;YACvE,iEAAiE;YACjE,MAAMc,MAAMN,SAAS,UAAU,IAAI,CAAC;YACpCL,cAAc;YACd,MAAMY,SAAS,CAACC;gBACdf,SAASJ,cAAcgB,aAAa,AAACG,CAAAA,GAAGJ,OAAO,GAAGD,MAAK,IAAKG;YAC9D;YACA,MAAMG,UAAU;gBACdC,OAAOC,mBAAmB,CAAC,aAAaJ;gBACxCG,OAAOC,mBAAmB,CAAC,WAAWC;gBACtCC,SAASC,IAAI,CAACC,KAAK,CAACC,MAAM,GAAG;gBAC7BH,SAASC,IAAI,CAACC,KAAK,CAACE,UAAU,GAAG;gBACjC,IAAIpB,aAAaC,OAAO,EAAEH,cAAc;gBACxCC,eAAeE,OAAO,GAAG;YAC3B;YACA,MAAMc,OAAO,IAAMH;YACnBb,eAAeE,OAAO,GAAGW;YACzBI,SAASC,IAAI,CAACC,KAAK,CAACC,MAAM,GAAG;YAC7BH,SAASC,IAAI,CAACC,KAAK,CAACE,UAAU,GAAG;YACjCP,OAAOQ,gBAAgB,CAAC,aAAaX;YACrCG,OAAOQ,gBAAgB,CAAC,WAAWN;QACrC,GACA;QAACrB;QAAWE;QAAUD;KAAc;IAGtC,OAAO;QAAEE;QAAYK;IAAkB;AACzC,EAAC"}
@@ -0,0 +1,15 @@
1
+ import { type RefObject } from 'react';
2
+ import { HoverToolbarController } from '../preview/HoverToolbarController';
3
+ export type UsePreviewSelectionSyncArgs = {
4
+ iframeRef: RefObject<HTMLIFrameElement | null>;
5
+ controllerRef: RefObject<HoverToolbarController | null>;
6
+ selectedBlockPath: string | null;
7
+ interactMode: boolean;
8
+ previewURL: string | undefined;
9
+ };
10
+ /**
11
+ * Bridges three iframe-side concerns to admin state: live-preview document
12
+ * events, the interact-mode body attribute, and selection sync between the
13
+ * sidebar and the in-iframe hover toolbar.
14
+ */
15
+ export declare const usePreviewSelectionSync: ({ iframeRef, controllerRef, selectedBlockPath, interactMode, previewURL, }: UsePreviewSelectionSyncArgs) => void;
@@ -0,0 +1,80 @@
1
+ 'use client';
2
+ import { useEffect, useMemo } from 'react';
3
+ import { useAllFormFields, useDocumentEvents, useDocumentInfo } from '@payloadcms/ui';
4
+ import { INTERACT_BODY_ATTR } from '../preview/hover-css';
5
+ import { getSameOriginDocument } from '../internal/iframe';
6
+ import { useEditorHistory } from '../state/useEditorHistory';
7
+ /**
8
+ * Bridges three iframe-side concerns to admin state: live-preview document
9
+ * events, the interact-mode body attribute, and selection sync between the
10
+ * sidebar and the in-iframe hover toolbar.
11
+ */ export const usePreviewSelectionSync = ({ iframeRef, controllerRef, selectedBlockPath, interactMode, previewURL })=>{
12
+ const { mostRecentUpdate } = useDocumentEvents();
13
+ const { id } = useDocumentInfo();
14
+ const previewOrigin = useMemo(()=>{
15
+ if (!previewURL) return null;
16
+ try {
17
+ return new URL(previewURL, window.location.origin).origin;
18
+ } catch {
19
+ return null;
20
+ }
21
+ }, [
22
+ previewURL
23
+ ]);
24
+ useEffect(()=>{
25
+ if (!mostRecentUpdate || !previewOrigin) return;
26
+ if (id != null && mostRecentUpdate.id !== id) return;
27
+ iframeRef.current?.contentWindow?.postMessage({
28
+ type: 'payload-document-event'
29
+ }, previewOrigin);
30
+ }, [
31
+ iframeRef,
32
+ mostRecentUpdate,
33
+ id,
34
+ previewOrigin
35
+ ]);
36
+ const [allFields] = useAllFormFields();
37
+ const selectedBlockId = useMemo(()=>{
38
+ if (!selectedBlockPath) return null;
39
+ const v = allFields[`${selectedBlockPath}.id`]?.value;
40
+ return typeof v === 'string' ? v : null;
41
+ }, [
42
+ allFields,
43
+ selectedBlockPath
44
+ ]);
45
+ // Toggle the iframe body attribute that gates hover/active CSS + clicks.
46
+ useEffect(()=>{
47
+ const iframe = iframeRef.current;
48
+ if (!iframe) return;
49
+ const doc = getSameOriginDocument(iframe);
50
+ if (!doc) return;
51
+ if (interactMode) doc.body.setAttribute(INTERACT_BODY_ATTR, '');
52
+ else doc.body.removeAttribute(INTERACT_BODY_ATTR);
53
+ }, [
54
+ iframeRef,
55
+ interactMode
56
+ ]);
57
+ const { mutationToken } = useEditorHistory();
58
+ useEffect(()=>{
59
+ const controller = controllerRef.current;
60
+ if (!controller) return;
61
+ if (!selectedBlockId || interactMode) {
62
+ controller.deselect();
63
+ return;
64
+ }
65
+ // Defer one frame so the iframe DOM has settled after a mutation.
66
+ const view = iframeRef.current?.contentWindow;
67
+ const raf = view?.requestAnimationFrame(()=>controller.select(selectedBlockId));
68
+ return ()=>{
69
+ if (raf !== undefined) view?.cancelAnimationFrame(raf);
70
+ };
71
+ }, [
72
+ iframeRef,
73
+ controllerRef,
74
+ selectedBlockId,
75
+ mutationToken,
76
+ interactMode
77
+ ]);
78
+ };
79
+
80
+ //# sourceMappingURL=usePreviewSelectionSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/usePreviewSelectionSync.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useMemo, type RefObject } from 'react'\nimport { useAllFormFields, useDocumentEvents, useDocumentInfo } from '@payloadcms/ui'\nimport { HoverToolbarController } from '../preview/HoverToolbarController'\nimport { INTERACT_BODY_ATTR } from '../preview/hover-css'\nimport { getSameOriginDocument } from '../internal/iframe'\nimport { useEditorHistory } from '../state/useEditorHistory'\n\nexport type UsePreviewSelectionSyncArgs = {\n iframeRef: RefObject<HTMLIFrameElement | null>\n controllerRef: RefObject<HoverToolbarController | null>\n selectedBlockPath: string | null\n interactMode: boolean\n previewURL: string | undefined\n}\n\n/**\n * Bridges three iframe-side concerns to admin state: live-preview document\n * events, the interact-mode body attribute, and selection sync between the\n * sidebar and the in-iframe hover toolbar.\n */\nexport const usePreviewSelectionSync = ({\n iframeRef,\n controllerRef,\n selectedBlockPath,\n interactMode,\n previewURL,\n}: UsePreviewSelectionSyncArgs): void => {\n const { mostRecentUpdate } = useDocumentEvents()\n const { id } = useDocumentInfo()\n\n const previewOrigin = useMemo(() => {\n if (!previewURL) return null\n try {\n return new URL(previewURL, window.location.origin).origin\n } catch {\n return null\n }\n }, [previewURL])\n\n useEffect(() => {\n if (!mostRecentUpdate || !previewOrigin) return\n if (id != null && mostRecentUpdate.id !== id) return\n iframeRef.current?.contentWindow?.postMessage(\n { type: 'payload-document-event' },\n previewOrigin,\n )\n }, [iframeRef, mostRecentUpdate, id, previewOrigin])\n\n const [allFields] = useAllFormFields()\n const selectedBlockId = useMemo<string | null>(() => {\n if (!selectedBlockPath) return null\n const v = allFields[`${selectedBlockPath}.id`]?.value\n return typeof v === 'string' ? v : null\n }, [allFields, selectedBlockPath])\n\n // Toggle the iframe body attribute that gates hover/active CSS + clicks.\n useEffect(() => {\n const iframe = iframeRef.current\n if (!iframe) return\n const doc = getSameOriginDocument(iframe)\n if (!doc) return\n if (interactMode) doc.body.setAttribute(INTERACT_BODY_ATTR, '')\n else doc.body.removeAttribute(INTERACT_BODY_ATTR)\n }, [iframeRef, interactMode])\n\n const { mutationToken } = useEditorHistory()\n useEffect(() => {\n const controller = controllerRef.current\n if (!controller) return\n if (!selectedBlockId || interactMode) {\n controller.deselect()\n return\n }\n // Defer one frame so the iframe DOM has settled after a mutation.\n const view = iframeRef.current?.contentWindow\n const raf = view?.requestAnimationFrame(() => controller.select(selectedBlockId))\n return () => {\n if (raf !== undefined) view?.cancelAnimationFrame(raf)\n }\n }, [iframeRef, controllerRef, selectedBlockId, mutationToken, interactMode])\n}\n"],"names":["useEffect","useMemo","useAllFormFields","useDocumentEvents","useDocumentInfo","INTERACT_BODY_ATTR","getSameOriginDocument","useEditorHistory","usePreviewSelectionSync","iframeRef","controllerRef","selectedBlockPath","interactMode","previewURL","mostRecentUpdate","id","previewOrigin","URL","window","location","origin","current","contentWindow","postMessage","type","allFields","selectedBlockId","v","value","iframe","doc","body","setAttribute","removeAttribute","mutationToken","controller","deselect","view","raf","requestAnimationFrame","select","undefined","cancelAnimationFrame"],"mappings":"AAAA;AAEA,SAASA,SAAS,EAAEC,OAAO,QAAwB,QAAO;AAC1D,SAASC,gBAAgB,EAAEC,iBAAiB,EAAEC,eAAe,QAAQ,iBAAgB;AAErF,SAASC,kBAAkB,QAAQ,uBAAsB;AACzD,SAASC,qBAAqB,QAAQ,qBAAoB;AAC1D,SAASC,gBAAgB,QAAQ,4BAA2B;AAU5D;;;;CAIC,GACD,OAAO,MAAMC,0BAA0B,CAAC,EACtCC,SAAS,EACTC,aAAa,EACbC,iBAAiB,EACjBC,YAAY,EACZC,UAAU,EACkB;IAC5B,MAAM,EAAEC,gBAAgB,EAAE,GAAGX;IAC7B,MAAM,EAAEY,EAAE,EAAE,GAAGX;IAEf,MAAMY,gBAAgBf,QAAQ;QAC5B,IAAI,CAACY,YAAY,OAAO;QACxB,IAAI;YACF,OAAO,IAAII,IAAIJ,YAAYK,OAAOC,QAAQ,CAACC,MAAM,EAAEA,MAAM;QAC3D,EAAE,OAAM;YACN,OAAO;QACT;IACF,GAAG;QAACP;KAAW;IAEfb,UAAU;QACR,IAAI,CAACc,oBAAoB,CAACE,eAAe;QACzC,IAAID,MAAM,QAAQD,iBAAiBC,EAAE,KAAKA,IAAI;QAC9CN,UAAUY,OAAO,EAAEC,eAAeC,YAChC;YAAEC,MAAM;QAAyB,GACjCR;IAEJ,GAAG;QAACP;QAAWK;QAAkBC;QAAIC;KAAc;IAEnD,MAAM,CAACS,UAAU,GAAGvB;IACpB,MAAMwB,kBAAkBzB,QAAuB;QAC7C,IAAI,CAACU,mBAAmB,OAAO;QAC/B,MAAMgB,IAAIF,SAAS,CAAC,GAAGd,kBAAkB,GAAG,CAAC,CAAC,EAAEiB;QAChD,OAAO,OAAOD,MAAM,WAAWA,IAAI;IACrC,GAAG;QAACF;QAAWd;KAAkB;IAEjC,yEAAyE;IACzEX,UAAU;QACR,MAAM6B,SAASpB,UAAUY,OAAO;QAChC,IAAI,CAACQ,QAAQ;QACb,MAAMC,MAAMxB,sBAAsBuB;QAClC,IAAI,CAACC,KAAK;QACV,IAAIlB,cAAckB,IAAIC,IAAI,CAACC,YAAY,CAAC3B,oBAAoB;aACvDyB,IAAIC,IAAI,CAACE,eAAe,CAAC5B;IAChC,GAAG;QAACI;QAAWG;KAAa;IAE5B,MAAM,EAAEsB,aAAa,EAAE,GAAG3B;IAC1BP,UAAU;QACR,MAAMmC,aAAazB,cAAcW,OAAO;QACxC,IAAI,CAACc,YAAY;QACjB,IAAI,CAACT,mBAAmBd,cAAc;YACpCuB,WAAWC,QAAQ;YACnB;QACF;QACA,kEAAkE;QAClE,MAAMC,OAAO5B,UAAUY,OAAO,EAAEC;QAChC,MAAMgB,MAAMD,MAAME,sBAAsB,IAAMJ,WAAWK,MAAM,CAACd;QAChE,OAAO;YACL,IAAIY,QAAQG,WAAWJ,MAAMK,qBAAqBJ;QACpD;IACF,GAAG;QAAC7B;QAAWC;QAAegB;QAAiBQ;QAAetB;KAAa;AAC7E,EAAC"}
@@ -0,0 +1,17 @@
1
+ import { type RefObject } from 'react';
2
+ import { HoverToolbarController } from '../preview/HoverToolbarController';
3
+ import type { BlockActionMessage } from '../preview/protocol';
4
+ import type { PreviewBindingSettings } from './usePreviewBinding';
5
+ export type UsePreviewSettingsSyncArgs = {
6
+ iframeRef: RefObject<HTMLIFrameElement | null>;
7
+ controllerRef: RefObject<HoverToolbarController | null>;
8
+ isBoundRef: RefObject<boolean>;
9
+ settings: PreviewBindingSettings;
10
+ onBlockAction: (id: string, action: BlockActionMessage['action']) => void;
11
+ };
12
+ /**
13
+ * Pushes setting changes (hover colors, toolbar visibility/position) into
14
+ * the iframe without recreating the controller — that would lose the
15
+ * current selection.
16
+ */
17
+ export declare const usePreviewSettingsSync: ({ iframeRef, controllerRef, isBoundRef, settings, onBlockAction, }: UsePreviewSettingsSyncArgs) => void;
@@ -0,0 +1,55 @@
1
+ 'use client';
2
+ import { useEffect } from 'react';
3
+ import { HoverToolbarController } from '../preview/HoverToolbarController';
4
+ import { ACTIVE_CLASS, ACTIVE_SELECTOR } from '../internal/dom';
5
+ import { TOOLBAR_ID, setHoverVars } from '../preview/hover-css';
6
+ import { getSameOriginDocument } from '../internal/iframe';
7
+ /**
8
+ * Pushes setting changes (hover colors, toolbar visibility/position) into
9
+ * the iframe without recreating the controller — that would lose the
10
+ * current selection.
11
+ */ export const usePreviewSettingsSync = ({ iframeRef, controllerRef, isBoundRef, settings, onBlockAction })=>{
12
+ const { hoverColorTopLevel, hoverColorNested, hoverOutlineWidth, showHoverToolbar, hoverToolbarPosition } = settings;
13
+ useEffect(()=>{
14
+ if (!isBoundRef.current) return;
15
+ const iframe = iframeRef.current;
16
+ if (!iframe) return;
17
+ const doc = getSameOriginDocument(iframe);
18
+ if (!doc) return;
19
+ setHoverVars(doc, {
20
+ topColor: hoverColorTopLevel,
21
+ nestedColor: hoverColorNested,
22
+ outlineWidth: hoverOutlineWidth
23
+ });
24
+ if (showHoverToolbar) {
25
+ const next = {
26
+ position: hoverToolbarPosition,
27
+ outlineWidth: hoverOutlineWidth,
28
+ onAction: onBlockAction
29
+ };
30
+ if (controllerRef.current) {
31
+ controllerRef.current.update(next);
32
+ } else {
33
+ controllerRef.current = new HoverToolbarController(doc, next);
34
+ }
35
+ } else if (controllerRef.current) {
36
+ controllerRef.current.destroy();
37
+ controllerRef.current = null;
38
+ // Defensive: drop residue if styles outlived a previous controller.
39
+ doc.getElementById(TOOLBAR_ID)?.remove();
40
+ doc.querySelectorAll(ACTIVE_SELECTOR).forEach((el)=>el.classList.remove(ACTIVE_CLASS));
41
+ }
42
+ }, [
43
+ iframeRef,
44
+ controllerRef,
45
+ isBoundRef,
46
+ hoverColorTopLevel,
47
+ hoverColorNested,
48
+ hoverOutlineWidth,
49
+ showHoverToolbar,
50
+ hoverToolbarPosition,
51
+ onBlockAction
52
+ ]);
53
+ };
54
+
55
+ //# sourceMappingURL=usePreviewSettingsSync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/usePreviewSettingsSync.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, type RefObject } from 'react'\nimport { HoverToolbarController, type HoverToolbarOptions } from '../preview/HoverToolbarController'\nimport { ACTIVE_CLASS, ACTIVE_SELECTOR } from '../internal/dom'\nimport { TOOLBAR_ID, setHoverVars } from '../preview/hover-css'\nimport type { BlockActionMessage } from '../preview/protocol'\nimport { getSameOriginDocument } from '../internal/iframe'\nimport type { PreviewBindingSettings } from './usePreviewBinding'\n\nexport type UsePreviewSettingsSyncArgs = {\n iframeRef: RefObject<HTMLIFrameElement | null>\n controllerRef: RefObject<HoverToolbarController | null>\n isBoundRef: RefObject<boolean>\n settings: PreviewBindingSettings\n onBlockAction: (id: string, action: BlockActionMessage['action']) => void\n}\n\n/**\n * Pushes setting changes (hover colors, toolbar visibility/position) into\n * the iframe without recreating the controller — that would lose the\n * current selection.\n */\nexport const usePreviewSettingsSync = ({\n iframeRef,\n controllerRef,\n isBoundRef,\n settings,\n onBlockAction,\n}: UsePreviewSettingsSyncArgs): void => {\n const {\n hoverColorTopLevel,\n hoverColorNested,\n hoverOutlineWidth,\n showHoverToolbar,\n hoverToolbarPosition,\n } = settings\n\n useEffect(() => {\n if (!isBoundRef.current) return\n const iframe = iframeRef.current\n if (!iframe) return\n const doc = getSameOriginDocument(iframe)\n if (!doc) return\n\n setHoverVars(doc, {\n topColor: hoverColorTopLevel,\n nestedColor: hoverColorNested,\n outlineWidth: hoverOutlineWidth,\n })\n\n if (showHoverToolbar) {\n const next: HoverToolbarOptions = {\n position: hoverToolbarPosition,\n outlineWidth: hoverOutlineWidth,\n onAction: onBlockAction,\n }\n if (controllerRef.current) {\n controllerRef.current.update(next)\n } else {\n controllerRef.current = new HoverToolbarController(doc, next)\n }\n } else if (controllerRef.current) {\n controllerRef.current.destroy()\n controllerRef.current = null\n // Defensive: drop residue if styles outlived a previous controller.\n doc.getElementById(TOOLBAR_ID)?.remove()\n doc.querySelectorAll(ACTIVE_SELECTOR).forEach((el) => el.classList.remove(ACTIVE_CLASS))\n }\n }, [\n iframeRef,\n controllerRef,\n isBoundRef,\n hoverColorTopLevel,\n hoverColorNested,\n hoverOutlineWidth,\n showHoverToolbar,\n hoverToolbarPosition,\n onBlockAction,\n ])\n}\n"],"names":["useEffect","HoverToolbarController","ACTIVE_CLASS","ACTIVE_SELECTOR","TOOLBAR_ID","setHoverVars","getSameOriginDocument","usePreviewSettingsSync","iframeRef","controllerRef","isBoundRef","settings","onBlockAction","hoverColorTopLevel","hoverColorNested","hoverOutlineWidth","showHoverToolbar","hoverToolbarPosition","current","iframe","doc","topColor","nestedColor","outlineWidth","next","position","onAction","update","destroy","getElementById","remove","querySelectorAll","forEach","el","classList"],"mappings":"AAAA;AAEA,SAASA,SAAS,QAAwB,QAAO;AACjD,SAASC,sBAAsB,QAAkC,oCAAmC;AACpG,SAASC,YAAY,EAAEC,eAAe,QAAQ,kBAAiB;AAC/D,SAASC,UAAU,EAAEC,YAAY,QAAQ,uBAAsB;AAE/D,SAASC,qBAAqB,QAAQ,qBAAoB;AAW1D;;;;CAIC,GACD,OAAO,MAAMC,yBAAyB,CAAC,EACrCC,SAAS,EACTC,aAAa,EACbC,UAAU,EACVC,QAAQ,EACRC,aAAa,EACc;IAC3B,MAAM,EACJC,kBAAkB,EAClBC,gBAAgB,EAChBC,iBAAiB,EACjBC,gBAAgB,EAChBC,oBAAoB,EACrB,GAAGN;IAEJX,UAAU;QACR,IAAI,CAACU,WAAWQ,OAAO,EAAE;QACzB,MAAMC,SAASX,UAAUU,OAAO;QAChC,IAAI,CAACC,QAAQ;QACb,MAAMC,MAAMd,sBAAsBa;QAClC,IAAI,CAACC,KAAK;QAEVf,aAAae,KAAK;YAChBC,UAAUR;YACVS,aAAaR;YACbS,cAAcR;QAChB;QAEA,IAAIC,kBAAkB;YACpB,MAAMQ,OAA4B;gBAChCC,UAAUR;gBACVM,cAAcR;gBACdW,UAAUd;YACZ;YACA,IAAIH,cAAcS,OAAO,EAAE;gBACzBT,cAAcS,OAAO,CAACS,MAAM,CAACH;YAC/B,OAAO;gBACLf,cAAcS,OAAO,GAAG,IAAIjB,uBAAuBmB,KAAKI;YAC1D;QACF,OAAO,IAAIf,cAAcS,OAAO,EAAE;YAChCT,cAAcS,OAAO,CAACU,OAAO;YAC7BnB,cAAcS,OAAO,GAAG;YACxB,oEAAoE;YACpEE,IAAIS,cAAc,CAACzB,aAAa0B;YAChCV,IAAIW,gBAAgB,CAAC5B,iBAAiB6B,OAAO,CAAC,CAACC,KAAOA,GAAGC,SAAS,CAACJ,MAAM,CAAC5B;QAC5E;IACF,GAAG;QACDM;QACAC;QACAC;QACAG;QACAC;QACAC;QACAC;QACAC;QACAL;KACD;AACH,EAAC"}
@@ -0,0 +1,8 @@
1
+ import React from 'react';
2
+ export type UseSidebarResizeReturn = {
3
+ sidebarWidth: number;
4
+ isResizing: boolean;
5
+ onResizeStart: (e: React.MouseEvent<HTMLDivElement>) => void;
6
+ onResizeKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void;
7
+ };
8
+ export declare const useSidebarResize: (sidebarPosition: "left" | "right") => UseSidebarResizeReturn;
@@ -0,0 +1,101 @@
1
+ 'use client';
2
+ import React, { useCallback, useEffect, useRef, useState } from 'react';
3
+ import { DEFAULT_SIDEBAR_WIDTH, MAX_SIDEBAR_WIDTH, MIN_SIDEBAR_WIDTH, SIDEBAR_KEYBOARD_STEP_PX, SIDEBAR_KEYBOARD_STEP_LARGE_PX, STORAGE_DEBOUNCE_MS } from '../internal/constants';
4
+ import { useBetterEditorConfig } from '../providers/BetterEditorConfigProvider';
5
+ import { readNumber, writeString } from '../internal/storage';
6
+ const clampSidebar = (n)=>Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, n));
7
+ export const useSidebarResize = (sidebarPosition)=>{
8
+ const { storageKeys } = useBetterEditorConfig();
9
+ const [sidebarWidth, setSidebarWidth] = useState(()=>readNumber(storageKeys.sidebarWidth, DEFAULT_SIDEBAR_WIDTH, clampSidebar));
10
+ const [isResizing, setIsResizing] = useState(false);
11
+ const widthRef = useRef(sidebarWidth);
12
+ widthRef.current = sidebarWidth;
13
+ const positionRef = useRef(sidebarPosition);
14
+ positionRef.current = sidebarPosition;
15
+ const dragCleanupRef = useRef(null);
16
+ const writeTimerRef = useRef(null);
17
+ // Debounce storage writes — drag fires 60×/s while keyboard nudges fire
18
+ // per keystroke. Either way we only want the final value persisted.
19
+ const hydratedRef = useRef(false);
20
+ useEffect(()=>{
21
+ if (!hydratedRef.current) {
22
+ hydratedRef.current = true;
23
+ return;
24
+ }
25
+ if (writeTimerRef.current) clearTimeout(writeTimerRef.current);
26
+ writeTimerRef.current = setTimeout(()=>{
27
+ writeString(storageKeys.sidebarWidth, String(sidebarWidth));
28
+ writeTimerRef.current = null;
29
+ }, STORAGE_DEBOUNCE_MS);
30
+ return ()=>{
31
+ if (writeTimerRef.current) clearTimeout(writeTimerRef.current);
32
+ };
33
+ }, [
34
+ sidebarWidth,
35
+ storageKeys.sidebarWidth
36
+ ]);
37
+ // Release listeners + body styles + flush pending storage write on unmount.
38
+ useEffect(()=>()=>{
39
+ dragCleanupRef.current?.();
40
+ if (writeTimerRef.current) {
41
+ clearTimeout(writeTimerRef.current);
42
+ writeString(storageKeys.sidebarWidth, String(widthRef.current));
43
+ }
44
+ }, [
45
+ storageKeys.sidebarWidth
46
+ ]);
47
+ const onResizeStart = useCallback((e)=>{
48
+ e.preventDefault();
49
+ const startX = e.clientX;
50
+ const startWidth = widthRef.current;
51
+ const direction = positionRef.current === 'right' ? -1 : 1;
52
+ setIsResizing(true);
53
+ document.body.style.cursor = 'col-resize';
54
+ document.body.style.userSelect = 'none';
55
+ const onMove = (ev)=>{
56
+ setSidebarWidth(clampSidebar(startWidth + (ev.clientX - startX) * direction));
57
+ };
58
+ const cleanup = ()=>{
59
+ window.removeEventListener('mousemove', onMove);
60
+ window.removeEventListener('mouseup', cleanup);
61
+ document.body.style.cursor = '';
62
+ document.body.style.userSelect = '';
63
+ setIsResizing(false);
64
+ dragCleanupRef.current = null;
65
+ };
66
+ dragCleanupRef.current = cleanup;
67
+ window.addEventListener('mousemove', onMove);
68
+ window.addEventListener('mouseup', cleanup);
69
+ }, []);
70
+ // Arrow keys nudge the handle for keyboard users (WCAG 2.1 separator
71
+ // semantics). Direction matches the mouse drag — Right when the sidebar
72
+ // sits on the right grows the sidebar inward.
73
+ const onResizeKeyDown = useCallback((e)=>{
74
+ const direction = positionRef.current === 'right' ? -1 : 1;
75
+ let delta = 0;
76
+ if (e.key === 'ArrowLeft') delta = -SIDEBAR_KEYBOARD_STEP_PX * direction;
77
+ else if (e.key === 'ArrowRight') delta = SIDEBAR_KEYBOARD_STEP_PX * direction;
78
+ else if (e.key === 'PageUp') delta = -SIDEBAR_KEYBOARD_STEP_LARGE_PX * direction;
79
+ else if (e.key === 'PageDown') delta = SIDEBAR_KEYBOARD_STEP_LARGE_PX * direction;
80
+ else if (e.key === 'Home') {
81
+ e.preventDefault();
82
+ setSidebarWidth(MIN_SIDEBAR_WIDTH);
83
+ return;
84
+ } else if (e.key === 'End') {
85
+ e.preventDefault();
86
+ setSidebarWidth(MAX_SIDEBAR_WIDTH);
87
+ return;
88
+ }
89
+ if (delta === 0) return;
90
+ e.preventDefault();
91
+ setSidebarWidth((w)=>clampSidebar(w + delta));
92
+ }, []);
93
+ return {
94
+ sidebarWidth,
95
+ isResizing,
96
+ onResizeStart,
97
+ onResizeKeyDown
98
+ };
99
+ };
100
+
101
+ //# sourceMappingURL=useSidebarResize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/useSidebarResize.ts"],"sourcesContent":["'use client'\n\nimport React, { useCallback, useEffect, useRef, useState } from 'react'\nimport {\n DEFAULT_SIDEBAR_WIDTH,\n MAX_SIDEBAR_WIDTH,\n MIN_SIDEBAR_WIDTH,\n SIDEBAR_KEYBOARD_STEP_PX,\n SIDEBAR_KEYBOARD_STEP_LARGE_PX,\n STORAGE_DEBOUNCE_MS,\n} from '../internal/constants'\nimport { useBetterEditorConfig } from '../providers/BetterEditorConfigProvider'\nimport { readNumber, writeString } from '../internal/storage'\n\nconst clampSidebar = (n: number): number =>\n Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, n))\n\nexport type UseSidebarResizeReturn = {\n sidebarWidth: number\n isResizing: boolean\n onResizeStart: (e: React.MouseEvent<HTMLDivElement>) => void\n onResizeKeyDown: (e: React.KeyboardEvent<HTMLDivElement>) => void\n}\n\nexport const useSidebarResize = (\n sidebarPosition: 'left' | 'right',\n): UseSidebarResizeReturn => {\n const { storageKeys } = useBetterEditorConfig()\n const [sidebarWidth, setSidebarWidth] = useState<number>(() =>\n readNumber(storageKeys.sidebarWidth, DEFAULT_SIDEBAR_WIDTH, clampSidebar),\n )\n const [isResizing, setIsResizing] = useState(false)\n\n const widthRef = useRef(sidebarWidth)\n widthRef.current = sidebarWidth\n\n const positionRef = useRef(sidebarPosition)\n positionRef.current = sidebarPosition\n\n const dragCleanupRef = useRef<(() => void) | null>(null)\n const writeTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null)\n\n // Debounce storage writes — drag fires 60×/s while keyboard nudges fire\n // per keystroke. Either way we only want the final value persisted.\n const hydratedRef = useRef(false)\n useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n return\n }\n if (writeTimerRef.current) clearTimeout(writeTimerRef.current)\n writeTimerRef.current = setTimeout(() => {\n writeString(storageKeys.sidebarWidth, String(sidebarWidth))\n writeTimerRef.current = null\n }, STORAGE_DEBOUNCE_MS)\n return () => {\n if (writeTimerRef.current) clearTimeout(writeTimerRef.current)\n }\n }, [sidebarWidth, storageKeys.sidebarWidth])\n\n // Release listeners + body styles + flush pending storage write on unmount.\n useEffect(\n () => () => {\n dragCleanupRef.current?.()\n if (writeTimerRef.current) {\n clearTimeout(writeTimerRef.current)\n writeString(storageKeys.sidebarWidth, String(widthRef.current))\n }\n },\n [storageKeys.sidebarWidth],\n )\n\n const onResizeStart = useCallback((e: React.MouseEvent<HTMLDivElement>) => {\n e.preventDefault()\n const startX = e.clientX\n const startWidth = widthRef.current\n const direction = positionRef.current === 'right' ? -1 : 1\n\n setIsResizing(true)\n document.body.style.cursor = 'col-resize'\n document.body.style.userSelect = 'none'\n\n const onMove = (ev: MouseEvent) => {\n setSidebarWidth(clampSidebar(startWidth + (ev.clientX - startX) * direction))\n }\n const cleanup = () => {\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', cleanup)\n document.body.style.cursor = ''\n document.body.style.userSelect = ''\n setIsResizing(false)\n dragCleanupRef.current = null\n }\n dragCleanupRef.current = cleanup\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', cleanup)\n }, [])\n\n // Arrow keys nudge the handle for keyboard users (WCAG 2.1 separator\n // semantics). Direction matches the mouse drag — Right when the sidebar\n // sits on the right grows the sidebar inward.\n const onResizeKeyDown = useCallback((e: React.KeyboardEvent<HTMLDivElement>) => {\n const direction = positionRef.current === 'right' ? -1 : 1\n let delta = 0\n if (e.key === 'ArrowLeft') delta = -SIDEBAR_KEYBOARD_STEP_PX * direction\n else if (e.key === 'ArrowRight') delta = SIDEBAR_KEYBOARD_STEP_PX * direction\n else if (e.key === 'PageUp') delta = -SIDEBAR_KEYBOARD_STEP_LARGE_PX * direction\n else if (e.key === 'PageDown') delta = SIDEBAR_KEYBOARD_STEP_LARGE_PX * direction\n else if (e.key === 'Home') {\n e.preventDefault()\n setSidebarWidth(MIN_SIDEBAR_WIDTH)\n return\n } else if (e.key === 'End') {\n e.preventDefault()\n setSidebarWidth(MAX_SIDEBAR_WIDTH)\n return\n }\n if (delta === 0) return\n e.preventDefault()\n setSidebarWidth((w) => clampSidebar(w + delta))\n }, [])\n\n return { sidebarWidth, isResizing, onResizeStart, onResizeKeyDown }\n}\n"],"names":["React","useCallback","useEffect","useRef","useState","DEFAULT_SIDEBAR_WIDTH","MAX_SIDEBAR_WIDTH","MIN_SIDEBAR_WIDTH","SIDEBAR_KEYBOARD_STEP_PX","SIDEBAR_KEYBOARD_STEP_LARGE_PX","STORAGE_DEBOUNCE_MS","useBetterEditorConfig","readNumber","writeString","clampSidebar","n","Math","min","max","useSidebarResize","sidebarPosition","storageKeys","sidebarWidth","setSidebarWidth","isResizing","setIsResizing","widthRef","current","positionRef","dragCleanupRef","writeTimerRef","hydratedRef","clearTimeout","setTimeout","String","onResizeStart","e","preventDefault","startX","clientX","startWidth","direction","document","body","style","cursor","userSelect","onMove","ev","cleanup","window","removeEventListener","addEventListener","onResizeKeyDown","delta","key","w"],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ import type { Viewport } from '../admin/ViewportToggle';
2
+ import type { BetterEditorSettings } from '../state/useBetterEditorSettings';
3
+ export type UseViewportStateReturn = {
4
+ viewport: Viewport;
5
+ setViewport: React.Dispatch<React.SetStateAction<Viewport>>;
6
+ responsiveWidth: number;
7
+ setResponsiveWidth: React.Dispatch<React.SetStateAction<number>>;
8
+ viewportWidth: number | null;
9
+ };
10
+ export declare const useViewportState: (settings: BetterEditorSettings) => UseViewportStateReturn;
@@ -0,0 +1,44 @@
1
+ 'use client';
2
+ import { useEffect, useRef, useState } from 'react';
3
+ import { clampViewport } from '../internal/limits';
4
+ import { DEFAULT_RESPONSIVE_WIDTH } from '../internal/constants';
5
+ import { useBetterEditorConfig } from '../providers/BetterEditorConfigProvider';
6
+ import { readNumber, writeString } from '../internal/storage';
7
+ const resolveWidth = (viewport, settings, responsiveWidth)=>{
8
+ switch(viewport){
9
+ case 'tablet':
10
+ return settings.tabletWidth;
11
+ case 'mobile':
12
+ return settings.mobileWidth;
13
+ case 'responsive':
14
+ return responsiveWidth;
15
+ case 'desktop':
16
+ return null;
17
+ }
18
+ };
19
+ export const useViewportState = (settings)=>{
20
+ const { storageKeys } = useBetterEditorConfig();
21
+ const [viewport, setViewport] = useState('desktop');
22
+ const [responsiveWidth, setResponsiveWidth] = useState(()=>readNumber(storageKeys.responsiveWidth, DEFAULT_RESPONSIVE_WIDTH, clampViewport));
23
+ // Skip persisting the value we just hydrated from storage.
24
+ const hydratedRef = useRef(false);
25
+ useEffect(()=>{
26
+ if (!hydratedRef.current) {
27
+ hydratedRef.current = true;
28
+ return;
29
+ }
30
+ writeString(storageKeys.responsiveWidth, String(responsiveWidth));
31
+ }, [
32
+ responsiveWidth,
33
+ storageKeys.responsiveWidth
34
+ ]);
35
+ return {
36
+ viewport,
37
+ setViewport,
38
+ responsiveWidth,
39
+ setResponsiveWidth,
40
+ viewportWidth: resolveWidth(viewport, settings, responsiveWidth)
41
+ };
42
+ };
43
+
44
+ //# sourceMappingURL=useViewportState.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/hooks/useViewportState.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, useRef, useState } from 'react'\nimport type { Viewport } from '../admin/ViewportToggle'\nimport type { BetterEditorSettings } from '../state/useBetterEditorSettings'\nimport { clampViewport } from '../internal/limits'\nimport { DEFAULT_RESPONSIVE_WIDTH } from '../internal/constants'\nimport { useBetterEditorConfig } from '../providers/BetterEditorConfigProvider'\nimport { readNumber, writeString } from '../internal/storage'\n\nexport type UseViewportStateReturn = {\n viewport: Viewport\n setViewport: React.Dispatch<React.SetStateAction<Viewport>>\n responsiveWidth: number\n setResponsiveWidth: React.Dispatch<React.SetStateAction<number>>\n viewportWidth: number | null\n}\n\nconst resolveWidth = (\n viewport: Viewport,\n settings: BetterEditorSettings,\n responsiveWidth: number,\n): number | null => {\n switch (viewport) {\n case 'tablet':\n return settings.tabletWidth\n case 'mobile':\n return settings.mobileWidth\n case 'responsive':\n return responsiveWidth\n case 'desktop':\n return null\n }\n}\n\nexport const useViewportState = (settings: BetterEditorSettings): UseViewportStateReturn => {\n const { storageKeys } = useBetterEditorConfig()\n const [viewport, setViewport] = useState<Viewport>('desktop')\n const [responsiveWidth, setResponsiveWidth] = useState<number>(() =>\n readNumber(storageKeys.responsiveWidth, DEFAULT_RESPONSIVE_WIDTH, clampViewport),\n )\n\n // Skip persisting the value we just hydrated from storage.\n const hydratedRef = useRef(false)\n useEffect(() => {\n if (!hydratedRef.current) {\n hydratedRef.current = true\n return\n }\n writeString(storageKeys.responsiveWidth, String(responsiveWidth))\n }, [responsiveWidth, storageKeys.responsiveWidth])\n\n return {\n viewport,\n setViewport,\n responsiveWidth,\n setResponsiveWidth,\n viewportWidth: resolveWidth(viewport, settings, responsiveWidth),\n }\n}\n"],"names":["useEffect","useRef","useState","clampViewport","DEFAULT_RESPONSIVE_WIDTH","useBetterEditorConfig","readNumber","writeString","resolveWidth","viewport","settings","responsiveWidth","tabletWidth","mobileWidth","useViewportState","storageKeys","setViewport","setResponsiveWidth","hydratedRef","current","String","viewportWidth"],"mappings":"AAAA;AAEA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AAGnD,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,wBAAwB,QAAQ,wBAAuB;AAChE,SAASC,qBAAqB,QAAQ,0CAAyC;AAC/E,SAASC,UAAU,EAAEC,WAAW,QAAQ,sBAAqB;AAU7D,MAAMC,eAAe,CACnBC,UACAC,UACAC;IAEA,OAAQF;QACN,KAAK;YACH,OAAOC,SAASE,WAAW;QAC7B,KAAK;YACH,OAAOF,SAASG,WAAW;QAC7B,KAAK;YACH,OAAOF;QACT,KAAK;YACH,OAAO;IACX;AACF;AAEA,OAAO,MAAMG,mBAAmB,CAACJ;IAC/B,MAAM,EAAEK,WAAW,EAAE,GAAGV;IACxB,MAAM,CAACI,UAAUO,YAAY,GAAGd,SAAmB;IACnD,MAAM,CAACS,iBAAiBM,mBAAmB,GAAGf,SAAiB,IAC7DI,WAAWS,YAAYJ,eAAe,EAAEP,0BAA0BD;IAGpE,2DAA2D;IAC3D,MAAMe,cAAcjB,OAAO;IAC3BD,UAAU;QACR,IAAI,CAACkB,YAAYC,OAAO,EAAE;YACxBD,YAAYC,OAAO,GAAG;YACtB;QACF;QACAZ,YAAYQ,YAAYJ,eAAe,EAAES,OAAOT;IAClD,GAAG;QAACA;QAAiBI,YAAYJ,eAAe;KAAC;IAEjD,OAAO;QACLF;QACAO;QACAL;QACAM;QACAI,eAAeb,aAAaC,UAAUC,UAAUC;IAClD;AACF,EAAC"}
@@ -0,0 +1,25 @@
1
+ import type { Config } from 'payload';
2
+ import type { BetterEditorConfig } from './types';
3
+ export type { BetterEditorConfig };
4
+ export type { BetterEditorSettings, HoverToolbarPosition } from './state/useBetterEditorSettings';
5
+ export type { SidebarPosition } from './internal/constants';
6
+ export { BETTER_EDITOR_SETTINGS_SLUG } from './global';
7
+ /** Plugin signature — handy for typing plugin lists in consumer code. */
8
+ export type BetterEditorPlugin = (config: Config) => Config;
9
+ export { VERSION } from './version';
10
+ /**
11
+ * Payload CMS plugin factory for the Better Editor overlay. Adds an
12
+ * "Open Better Editor" toggle to the configured collections / globals
13
+ * and registers a `BetterEditorSettings` global with editor-wide options.
14
+ *
15
+ * @example
16
+ * import { betterEditor } from 'payload-better-editor'
17
+ *
18
+ * export default buildConfig({
19
+ * plugins: [betterEditor({ collections: ['pages'] })],
20
+ * // ...
21
+ * })
22
+ *
23
+ * @see {@link BetterEditorConfig} for all options.
24
+ */
25
+ export declare const betterEditor: (pluginOptions?: BetterEditorConfig) => BetterEditorPlugin;
package/dist/index.js ADDED
@@ -0,0 +1,104 @@
1
+ import { BETTER_EDITOR_SETTINGS_BANNER_FIELD, betterEditorSettingsGlobal } from './global';
2
+ export { BETTER_EDITOR_SETTINGS_SLUG } from './global';
3
+ export { VERSION } from './version';
4
+ const DEFAULT_BLOCKS_FIELD = 'layout';
5
+ const TOGGLE_COMPONENT_PATH = 'payload-better-editor/client#LiveEditorToggle';
6
+ const isDev = process.env.NODE_ENV !== 'production';
7
+ const hasBlocksField = (fields, name)=>Array.isArray(fields) && fields.some((f)=>'name' in f && f.name === name);
8
+ const withToggleInjected = (entity, slot, clientProps)=>{
9
+ const admin = {
10
+ ...entity.admin ?? {}
11
+ };
12
+ const components = {
13
+ ...admin.components ?? {}
14
+ };
15
+ const target = {
16
+ ...components[slot] ?? {}
17
+ };
18
+ const before = target.beforeDocumentControls ?? [];
19
+ return {
20
+ ...entity,
21
+ admin: {
22
+ ...admin,
23
+ components: {
24
+ ...components,
25
+ [slot]: {
26
+ ...target,
27
+ beforeDocumentControls: [
28
+ ...before,
29
+ {
30
+ path: TOGGLE_COMPONENT_PATH,
31
+ clientProps
32
+ }
33
+ ]
34
+ }
35
+ }
36
+ }
37
+ };
38
+ };
39
+ const warnMissingBlocksField = (kind, slug, blocksField)=>{
40
+ console.warn(`[better-editor] ${kind} "${slug}" has no top-level field named "${blocksField}" — the sidebar Blocks tab will be empty. Set \`blocksField\` to the actual blocks field name.`);
41
+ };
42
+ /**
43
+ * Payload CMS plugin factory for the Better Editor overlay. Adds an
44
+ * "Open Better Editor" toggle to the configured collections / globals
45
+ * and registers a `BetterEditorSettings` global with editor-wide options.
46
+ *
47
+ * @example
48
+ * import { betterEditor } from 'payload-better-editor'
49
+ *
50
+ * export default buildConfig({
51
+ * plugins: [betterEditor({ collections: ['pages'] })],
52
+ * // ...
53
+ * })
54
+ *
55
+ * @see {@link BetterEditorConfig} for all options.
56
+ */ export const betterEditor = (pluginOptions)=>(config)=>{
57
+ if (pluginOptions?.disabled) return config;
58
+ const collectionSlugs = new Set(pluginOptions?.collections ?? []);
59
+ const globalSlugs = new Set(pluginOptions?.globals ?? []);
60
+ const blocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD;
61
+ const clientProps = {
62
+ blocksField,
63
+ adminPortalSelector: pluginOptions?.adminPortalSelector,
64
+ storageNamespace: pluginOptions?.storageNamespace
65
+ };
66
+ const showBanner = pluginOptions?.showSettingsBanner !== false;
67
+ const settingsGlobal = showBanner ? betterEditorSettingsGlobal : {
68
+ ...betterEditorSettingsGlobal,
69
+ fields: betterEditorSettingsGlobal.fields.filter((f)=>!('name' in f && f.name === BETTER_EDITOR_SETTINGS_BANNER_FIELD))
70
+ };
71
+ const existingGlobals = config.globals ?? [];
72
+ const hasSettingsGlobal = existingGlobals.some((g)=>g.slug === settingsGlobal.slug);
73
+ config.globals = hasSettingsGlobal ? existingGlobals : [
74
+ ...existingGlobals,
75
+ settingsGlobal
76
+ ];
77
+ if (collectionSlugs.size === 0 && globalSlugs.size === 0) {
78
+ if (isDev) {
79
+ console.warn('[better-editor] plugin loaded with empty `collections` and `globals` — toggle button will not appear anywhere. Pass `collections: ["pages"]` (or similar) to BetterEditorConfig.');
80
+ }
81
+ return config;
82
+ }
83
+ if (collectionSlugs.size > 0 && config.collections) {
84
+ config.collections = config.collections.map((collection)=>{
85
+ if (!collectionSlugs.has(collection.slug)) return collection;
86
+ if (isDev && !hasBlocksField(collection.fields, blocksField)) {
87
+ warnMissingBlocksField('collection', collection.slug, blocksField);
88
+ }
89
+ return withToggleInjected(collection, 'edit', clientProps);
90
+ });
91
+ }
92
+ if (globalSlugs.size > 0) {
93
+ config.globals = (config.globals ?? []).map((global)=>{
94
+ if (!globalSlugs.has(global.slug)) return global;
95
+ if (isDev && !hasBlocksField(global.fields, blocksField)) {
96
+ warnMissingBlocksField('global', global.slug, blocksField);
97
+ }
98
+ return withToggleInjected(global, 'elements', clientProps);
99
+ });
100
+ }
101
+ return config;
102
+ };
103
+
104
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["import type { CollectionConfig, Config, Field, GlobalConfig } from 'payload'\nimport type { BetterEditorConfig } from './types'\nimport { BETTER_EDITOR_SETTINGS_BANNER_FIELD, betterEditorSettingsGlobal } from './global'\n\nexport type { BetterEditorConfig }\nexport type { BetterEditorSettings, HoverToolbarPosition } from './state/useBetterEditorSettings'\nexport type { SidebarPosition } from './internal/constants'\nexport { BETTER_EDITOR_SETTINGS_SLUG } from './global'\n\n/** Plugin signature — handy for typing plugin lists in consumer code. */\nexport type BetterEditorPlugin = (config: Config) => Config\n\nexport { VERSION } from './version'\n\nconst DEFAULT_BLOCKS_FIELD = 'layout'\nconst TOGGLE_COMPONENT_PATH = 'payload-better-editor/client#LiveEditorToggle'\nconst isDev = process.env.NODE_ENV !== 'production'\n\nconst hasBlocksField = (fields: Field[] | undefined, name: string): boolean =>\n Array.isArray(fields) && fields.some((f) => 'name' in f && f.name === name)\n\ntype ToggleSlot = 'edit' | 'elements'\n\ntype ToggleClientProps = {\n blocksField: string\n adminPortalSelector?: string\n storageNamespace?: string\n}\n\nconst withToggleInjected = <T extends CollectionConfig | GlobalConfig>(\n entity: T,\n slot: ToggleSlot,\n clientProps: ToggleClientProps,\n): T => {\n const admin = { ...(entity.admin ?? {}) } as NonNullable<T['admin']>\n const components = { ...(admin.components ?? {}) } as Record<string, unknown>\n const target = { ...((components[slot] as Record<string, unknown>) ?? {}) }\n const before = (target.beforeDocumentControls as unknown[]) ?? []\n return {\n ...entity,\n admin: {\n ...admin,\n components: {\n ...components,\n [slot]: {\n ...target,\n beforeDocumentControls: [\n ...before,\n { path: TOGGLE_COMPONENT_PATH, clientProps },\n ],\n },\n },\n },\n }\n}\n\nconst warnMissingBlocksField = (kind: 'collection' | 'global', slug: string, blocksField: string) => {\n\n console.warn(\n `[better-editor] ${kind} \"${slug}\" has no top-level field named \"${blocksField}\" — the sidebar Blocks tab will be empty. Set \\`blocksField\\` to the actual blocks field name.`,\n )\n}\n\n/**\n * Payload CMS plugin factory for the Better Editor overlay. Adds an\n * \"Open Better Editor\" toggle to the configured collections / globals\n * and registers a `BetterEditorSettings` global with editor-wide options.\n *\n * @example\n * import { betterEditor } from 'payload-better-editor'\n *\n * export default buildConfig({\n * plugins: [betterEditor({ collections: ['pages'] })],\n * // ...\n * })\n *\n * @see {@link BetterEditorConfig} for all options.\n */\nexport const betterEditor =\n (pluginOptions?: BetterEditorConfig): BetterEditorPlugin =>\n (config: Config): Config => {\n if (pluginOptions?.disabled) return config\n\n const collectionSlugs = new Set(pluginOptions?.collections ?? [])\n const globalSlugs = new Set(pluginOptions?.globals ?? [])\n const blocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD\n const clientProps: ToggleClientProps = {\n blocksField,\n adminPortalSelector: pluginOptions?.adminPortalSelector,\n storageNamespace: pluginOptions?.storageNamespace,\n }\n\n const showBanner = pluginOptions?.showSettingsBanner !== false\n const settingsGlobal: GlobalConfig = showBanner\n ? betterEditorSettingsGlobal\n : {\n ...betterEditorSettingsGlobal,\n fields: betterEditorSettingsGlobal.fields.filter(\n (f) => !('name' in f && f.name === BETTER_EDITOR_SETTINGS_BANNER_FIELD),\n ),\n }\n\n const existingGlobals = config.globals ?? []\n const hasSettingsGlobal = existingGlobals.some((g) => g.slug === settingsGlobal.slug)\n config.globals = hasSettingsGlobal\n ? existingGlobals\n : [...existingGlobals, settingsGlobal]\n\n if (collectionSlugs.size === 0 && globalSlugs.size === 0) {\n if (isDev) {\n\n console.warn(\n '[better-editor] plugin loaded with empty `collections` and `globals` — toggle button will not appear anywhere. Pass `collections: [\"pages\"]` (or similar) to BetterEditorConfig.',\n )\n }\n return config\n }\n\n if (collectionSlugs.size > 0 && config.collections) {\n config.collections = config.collections.map((collection) => {\n if (!collectionSlugs.has(collection.slug)) return collection\n if (isDev && !hasBlocksField(collection.fields, blocksField)) {\n warnMissingBlocksField('collection', collection.slug, blocksField)\n }\n return withToggleInjected(collection, 'edit', clientProps)\n })\n }\n\n if (globalSlugs.size > 0) {\n config.globals = (config.globals ?? []).map((global) => {\n if (!globalSlugs.has(global.slug)) return global\n if (isDev && !hasBlocksField(global.fields, blocksField)) {\n warnMissingBlocksField('global', global.slug, blocksField)\n }\n return withToggleInjected(global, 'elements', clientProps)\n })\n }\n\n return config\n }\n"],"names":["BETTER_EDITOR_SETTINGS_BANNER_FIELD","betterEditorSettingsGlobal","BETTER_EDITOR_SETTINGS_SLUG","VERSION","DEFAULT_BLOCKS_FIELD","TOGGLE_COMPONENT_PATH","isDev","process","env","NODE_ENV","hasBlocksField","fields","name","Array","isArray","some","f","withToggleInjected","entity","slot","clientProps","admin","components","target","before","beforeDocumentControls","path","warnMissingBlocksField","kind","slug","blocksField","console","warn","betterEditor","pluginOptions","config","disabled","collectionSlugs","Set","collections","globalSlugs","globals","adminPortalSelector","storageNamespace","showBanner","showSettingsBanner","settingsGlobal","filter","existingGlobals","hasSettingsGlobal","g","size","map","collection","has","global"],"mappings":"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"}
@@ -0,0 +1,22 @@
1
+ export declare const SIDEBAR_POSITIONS: readonly ["left", "right"];
2
+ export type SidebarPosition = (typeof SIDEBAR_POSITIONS)[number];
3
+ export declare const HOVER_TOOLBAR_POSITIONS: readonly ["top-right", "top-left", "bottom-right", "bottom-left"];
4
+ export type HoverToolbarPosition = (typeof HOVER_TOOLBAR_POSITIONS)[number];
5
+ export declare const DEFAULT_BETTER_EDITOR_SETTINGS: {
6
+ readonly sidebarPosition: SidebarPosition;
7
+ readonly forceFullWidthFields: true;
8
+ readonly tabletWidth: 800;
9
+ readonly mobileWidth: 400;
10
+ readonly hoverColorTopLevel: "#3b82f6";
11
+ readonly hoverColorNested: "#f59e0b";
12
+ readonly hoverOutlineWidth: 2;
13
+ readonly showHoverToolbar: true;
14
+ readonly hoverToolbarPosition: HoverToolbarPosition;
15
+ };
16
+ export declare const DEFAULT_SIDEBAR_WIDTH = 400;
17
+ export declare const MIN_SIDEBAR_WIDTH = 250;
18
+ export declare const MAX_SIDEBAR_WIDTH = 800;
19
+ export declare const DEFAULT_RESPONSIVE_WIDTH = 1024;
20
+ export declare const SIDEBAR_KEYBOARD_STEP_PX = 16;
21
+ export declare const SIDEBAR_KEYBOARD_STEP_LARGE_PX = 64;
22
+ export declare const STORAGE_DEBOUNCE_MS = 250;
@@ -0,0 +1,38 @@
1
+ export const SIDEBAR_POSITIONS = [
2
+ 'left',
3
+ 'right'
4
+ ];
5
+ export const HOVER_TOOLBAR_POSITIONS = [
6
+ 'top-right',
7
+ 'top-left',
8
+ 'bottom-right',
9
+ 'bottom-left'
10
+ ];
11
+ export const DEFAULT_BETTER_EDITOR_SETTINGS = {
12
+ sidebarPosition: 'right',
13
+ forceFullWidthFields: true,
14
+ tabletWidth: 800,
15
+ mobileWidth: 400,
16
+ hoverColorTopLevel: '#3b82f6',
17
+ hoverColorNested: '#f59e0b',
18
+ hoverOutlineWidth: 2,
19
+ showHoverToolbar: true,
20
+ hoverToolbarPosition: 'top-right'
21
+ };
22
+ // Sidebar dimensions (px). Used by the runtime resize hook + the settings
23
+ // global's input limits.
24
+ export const DEFAULT_SIDEBAR_WIDTH = 400;
25
+ export const MIN_SIDEBAR_WIDTH = 250;
26
+ export const MAX_SIDEBAR_WIDTH = 800;
27
+ // Responsive viewport mode default width (px). Initial value when the user
28
+ // switches into "responsive" before they drag the handles.
29
+ export const DEFAULT_RESPONSIVE_WIDTH = 1024;
30
+ // Sidebar resize-handle keyboard nudges (px per keystroke).
31
+ export const SIDEBAR_KEYBOARD_STEP_PX = 16;
32
+ export const SIDEBAR_KEYBOARD_STEP_LARGE_PX = 64;
33
+ // Debounce window for persisting transient UI state to localStorage.
34
+ // Drags fire 60×/s and keyboard nudges fire per keystroke; we only want
35
+ // the final value written.
36
+ export const STORAGE_DEBOUNCE_MS = 250;
37
+
38
+ //# sourceMappingURL=constants.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/internal/constants.ts"],"sourcesContent":["export const SIDEBAR_POSITIONS = ['left', 'right'] as const\nexport type SidebarPosition = (typeof SIDEBAR_POSITIONS)[number]\n\nexport const HOVER_TOOLBAR_POSITIONS = [\n 'top-right',\n 'top-left',\n 'bottom-right',\n 'bottom-left',\n] as const\nexport type HoverToolbarPosition = (typeof HOVER_TOOLBAR_POSITIONS)[number]\n\nexport const DEFAULT_BETTER_EDITOR_SETTINGS = {\n sidebarPosition: 'right' as SidebarPosition,\n forceFullWidthFields: true,\n tabletWidth: 800,\n mobileWidth: 400,\n hoverColorTopLevel: '#3b82f6',\n hoverColorNested: '#f59e0b',\n hoverOutlineWidth: 2,\n showHoverToolbar: true,\n hoverToolbarPosition: 'top-right' as HoverToolbarPosition,\n} as const\n\n// Sidebar dimensions (px). Used by the runtime resize hook + the settings\n// global's input limits.\nexport const DEFAULT_SIDEBAR_WIDTH = 400\nexport const MIN_SIDEBAR_WIDTH = 250\nexport const MAX_SIDEBAR_WIDTH = 800\n\n// Responsive viewport mode default width (px). Initial value when the user\n// switches into \"responsive\" before they drag the handles.\nexport const DEFAULT_RESPONSIVE_WIDTH = 1024\n\n// Sidebar resize-handle keyboard nudges (px per keystroke).\nexport const SIDEBAR_KEYBOARD_STEP_PX = 16\nexport const SIDEBAR_KEYBOARD_STEP_LARGE_PX = 64\n\n// Debounce window for persisting transient UI state to localStorage.\n// Drags fire 60×/s and keyboard nudges fire per keystroke; we only want\n// the final value written.\nexport const STORAGE_DEBOUNCE_MS = 250\n"],"names":["SIDEBAR_POSITIONS","HOVER_TOOLBAR_POSITIONS","DEFAULT_BETTER_EDITOR_SETTINGS","sidebarPosition","forceFullWidthFields","tabletWidth","mobileWidth","hoverColorTopLevel","hoverColorNested","hoverOutlineWidth","showHoverToolbar","hoverToolbarPosition","DEFAULT_SIDEBAR_WIDTH","MIN_SIDEBAR_WIDTH","MAX_SIDEBAR_WIDTH","DEFAULT_RESPONSIVE_WIDTH","SIDEBAR_KEYBOARD_STEP_PX","SIDEBAR_KEYBOARD_STEP_LARGE_PX","STORAGE_DEBOUNCE_MS"],"mappings":"AAAA,OAAO,MAAMA,oBAAoB;IAAC;IAAQ;CAAQ,CAAS;AAG3D,OAAO,MAAMC,0BAA0B;IACrC;IACA;IACA;IACA;CACD,CAAS;AAGV,OAAO,MAAMC,iCAAiC;IAC5CC,iBAAiB;IACjBC,sBAAsB;IACtBC,aAAa;IACbC,aAAa;IACbC,oBAAoB;IACpBC,kBAAkB;IAClBC,mBAAmB;IACnBC,kBAAkB;IAClBC,sBAAsB;AACxB,EAAU;AAEV,0EAA0E;AAC1E,yBAAyB;AACzB,OAAO,MAAMC,wBAAwB,IAAG;AACxC,OAAO,MAAMC,oBAAoB,IAAG;AACpC,OAAO,MAAMC,oBAAoB,IAAG;AAEpC,2EAA2E;AAC3E,2DAA2D;AAC3D,OAAO,MAAMC,2BAA2B,KAAI;AAE5C,4DAA4D;AAC5D,OAAO,MAAMC,2BAA2B,GAAE;AAC1C,OAAO,MAAMC,iCAAiC,GAAE;AAEhD,qEAAqE;AACrE,wEAAwE;AACxE,2BAA2B;AAC3B,OAAO,MAAMC,sBAAsB,IAAG"}
@@ -0,0 +1,4 @@
1
+ export declare const BLOCK_ID_ATTR = "data-better-editor-id";
2
+ export declare const BLOCK_ID_SELECTOR = "[data-better-editor-id]";
3
+ export declare const ACTIVE_CLASS = "better-editor-active";
4
+ export declare const ACTIVE_SELECTOR = ".better-editor-active";