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.
- package/LICENSE +21 -0
- package/README.md +57 -0
- package/dist/admin/ErrorBoundary.d.ts +17 -0
- package/dist/admin/ErrorBoundary.js +62 -0
- package/dist/admin/ErrorBoundary.js.map +1 -0
- package/dist/admin/LiveEditorOverlay.d.ts +12 -0
- package/dist/admin/LiveEditorOverlay.js +160 -0
- package/dist/admin/LiveEditorOverlay.js.map +1 -0
- package/dist/admin/LiveEditorToggle.d.ts +7 -0
- package/dist/admin/LiveEditorToggle.js +84 -0
- package/dist/admin/LiveEditorToggle.js.map +1 -0
- package/dist/admin/PreviewFrame.d.ts +22 -0
- package/dist/admin/PreviewFrame.js +137 -0
- package/dist/admin/PreviewFrame.js.map +1 -0
- package/dist/admin/PreviewToolbar.d.ts +16 -0
- package/dist/admin/PreviewToolbar.js +90 -0
- package/dist/admin/PreviewToolbar.js.map +1 -0
- package/dist/admin/SettingsBanner.d.ts +3 -0
- package/dist/admin/SettingsBanner.js +105 -0
- package/dist/admin/SettingsBanner.js.map +1 -0
- package/dist/admin/ViewportToggle.d.ts +7 -0
- package/dist/admin/ViewportToggle.js +79 -0
- package/dist/admin/ViewportToggle.js.map +1 -0
- package/dist/admin/blocks/AddBlockDrawer.d.ts +9 -0
- package/dist/admin/blocks/AddBlockDrawer.js +16 -0
- package/dist/admin/blocks/AddBlockDrawer.js.map +1 -0
- package/dist/admin/blocks/BlockActionsToolbar.d.ts +15 -0
- package/dist/admin/blocks/BlockActionsToolbar.js +102 -0
- package/dist/admin/blocks/BlockActionsToolbar.js.map +1 -0
- package/dist/admin/blocks/BlockEmptyState.d.ts +6 -0
- package/dist/admin/blocks/BlockEmptyState.js +26 -0
- package/dist/admin/blocks/BlockEmptyState.js.map +1 -0
- package/dist/admin/blocks/BlockHeader.d.ts +7 -0
- package/dist/admin/blocks/BlockHeader.js +32 -0
- package/dist/admin/blocks/BlockHeader.js.map +1 -0
- package/dist/admin/blocks/schema.d.ts +19 -0
- package/dist/admin/blocks/schema.js +80 -0
- package/dist/admin/blocks/schema.js.map +1 -0
- package/dist/admin/blocks/useBlockActions.d.ts +24 -0
- package/dist/admin/blocks/useBlockActions.js +100 -0
- package/dist/admin/blocks/useBlockActions.js.map +1 -0
- package/dist/admin/icons.d.ts +24 -0
- package/dist/admin/icons.js +36 -0
- package/dist/admin/icons.js.map +1 -0
- package/dist/admin/sidebar/BlockSettingsTab.d.ts +10 -0
- package/dist/admin/sidebar/BlockSettingsTab.js +153 -0
- package/dist/admin/sidebar/BlockSettingsTab.js.map +1 -0
- package/dist/admin/sidebar/DocumentFieldsTab.d.ts +8 -0
- package/dist/admin/sidebar/DocumentFieldsTab.js +38 -0
- package/dist/admin/sidebar/DocumentFieldsTab.js.map +1 -0
- package/dist/admin/sidebar/DocumentMetaTab.d.ts +2 -0
- package/dist/admin/sidebar/DocumentMetaTab.js +11 -0
- package/dist/admin/sidebar/DocumentMetaTab.js.map +1 -0
- package/dist/admin/sidebar/DocumentSettingsTab.d.ts +2 -0
- package/dist/admin/sidebar/DocumentSettingsTab.js +48 -0
- package/dist/admin/sidebar/DocumentSettingsTab.js.map +1 -0
- package/dist/admin/sidebar/Sidebar.d.ts +10 -0
- package/dist/admin/sidebar/Sidebar.js +92 -0
- package/dist/admin/sidebar/Sidebar.js.map +1 -0
- package/dist/client.d.ts +34 -0
- package/dist/client.js +30 -0
- package/dist/client.js.map +1 -0
- package/dist/global.d.ts +4 -0
- package/dist/global.js +200 -0
- package/dist/global.js.map +1 -0
- package/dist/hooks/useAddBlockDrawer.d.ts +14 -0
- package/dist/hooks/useAddBlockDrawer.js +26 -0
- package/dist/hooks/useAddBlockDrawer.js.map +1 -0
- package/dist/hooks/useBlockActionMessages.d.ts +8 -0
- package/dist/hooks/useBlockActionMessages.js +107 -0
- package/dist/hooks/useBlockActionMessages.js.map +1 -0
- package/dist/hooks/useDocConfig.d.ts +6 -0
- package/dist/hooks/useDocConfig.js +18 -0
- package/dist/hooks/useDocConfig.js.map +1 -0
- package/dist/hooks/useFocusTrap.d.ts +2 -0
- package/dist/hooks/useFocusTrap.js +84 -0
- package/dist/hooks/useFocusTrap.js.map +1 -0
- package/dist/hooks/useFullscreenOverlay.d.ts +2 -0
- package/dist/hooks/useFullscreenOverlay.js +30 -0
- package/dist/hooks/useFullscreenOverlay.js.map +1 -0
- package/dist/hooks/useIframeResizeObserver.d.ts +2 -0
- package/dist/hooks/useIframeResizeObserver.js +20 -0
- package/dist/hooks/useIframeResizeObserver.js.map +1 -0
- package/dist/hooks/useLatestRef.d.ts +6 -0
- package/dist/hooks/useLatestRef.js +12 -0
- package/dist/hooks/useLatestRef.js.map +1 -0
- package/dist/hooks/useMainWrapperPortal.d.ts +1 -0
- package/dist/hooks/useMainWrapperPortal.js +64 -0
- package/dist/hooks/useMainWrapperPortal.js.map +1 -0
- package/dist/hooks/useOverlayKeyboard.d.ts +6 -0
- package/dist/hooks/useOverlayKeyboard.js +43 -0
- package/dist/hooks/useOverlayKeyboard.js.map +1 -0
- package/dist/hooks/usePreviewBinding.d.ts +28 -0
- package/dist/hooks/usePreviewBinding.js +108 -0
- package/dist/hooks/usePreviewBinding.js.map +1 -0
- package/dist/hooks/usePreviewHandleDrag.d.ts +11 -0
- package/dist/hooks/usePreviewHandleDrag.js +53 -0
- package/dist/hooks/usePreviewHandleDrag.js.map +1 -0
- package/dist/hooks/usePreviewSelectionSync.d.ts +15 -0
- package/dist/hooks/usePreviewSelectionSync.js +80 -0
- package/dist/hooks/usePreviewSelectionSync.js.map +1 -0
- package/dist/hooks/usePreviewSettingsSync.d.ts +17 -0
- package/dist/hooks/usePreviewSettingsSync.js +55 -0
- package/dist/hooks/usePreviewSettingsSync.js.map +1 -0
- package/dist/hooks/useSidebarResize.d.ts +8 -0
- package/dist/hooks/useSidebarResize.js +101 -0
- package/dist/hooks/useSidebarResize.js.map +1 -0
- package/dist/hooks/useViewportState.d.ts +10 -0
- package/dist/hooks/useViewportState.js +44 -0
- package/dist/hooks/useViewportState.js.map +1 -0
- package/dist/index.d.ts +25 -0
- package/dist/index.js +104 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/constants.d.ts +22 -0
- package/dist/internal/constants.js +38 -0
- package/dist/internal/constants.js.map +1 -0
- package/dist/internal/dom.d.ts +4 -0
- package/dist/internal/dom.js +6 -0
- package/dist/internal/dom.js.map +1 -0
- package/dist/internal/iframe.d.ts +5 -0
- package/dist/internal/iframe.js +12 -0
- package/dist/internal/iframe.js.map +1 -0
- package/dist/internal/limits.d.ts +9 -0
- package/dist/internal/limits.js +11 -0
- package/dist/internal/limits.js.map +1 -0
- package/dist/internal/path.d.ts +5 -0
- package/dist/internal/path.js +12 -0
- package/dist/internal/path.js.map +1 -0
- package/dist/internal/postmessage.d.ts +3 -0
- package/dist/internal/postmessage.js +21 -0
- package/dist/internal/postmessage.js.map +1 -0
- package/dist/internal/storage-keys.d.ts +8 -0
- package/dist/internal/storage-keys.js +9 -0
- package/dist/internal/storage-keys.js.map +1 -0
- package/dist/internal/storage.d.ts +2 -0
- package/dist/internal/storage.js +20 -0
- package/dist/internal/storage.js.map +1 -0
- package/dist/preview/HoverToolbar.d.ts +8 -0
- package/dist/preview/HoverToolbar.js +48 -0
- package/dist/preview/HoverToolbar.js.map +1 -0
- package/dist/preview/HoverToolbarController.d.ts +31 -0
- package/dist/preview/HoverToolbarController.js +160 -0
- package/dist/preview/HoverToolbarController.js.map +1 -0
- package/dist/preview/hover-css.d.ts +11 -0
- package/dist/preview/hover-css.js +94 -0
- package/dist/preview/hover-css.js.map +1 -0
- package/dist/preview/installClickToFocus.d.ts +6 -0
- package/dist/preview/installClickToFocus.js +21 -0
- package/dist/preview/installClickToFocus.js.map +1 -0
- package/dist/preview/installHoverStyles.d.ts +2 -0
- package/dist/preview/installHoverStyles.js +15 -0
- package/dist/preview/installHoverStyles.js.map +1 -0
- package/dist/preview/protocol.d.ts +11 -0
- package/dist/preview/protocol.js +19 -0
- package/dist/preview/protocol.js.map +1 -0
- package/dist/preview/toolbar-position.d.ts +20 -0
- package/dist/preview/toolbar-position.js +22 -0
- package/dist/preview/toolbar-position.js.map +1 -0
- package/dist/providers/BetterEditorConfigProvider.d.ts +14 -0
- package/dist/providers/BetterEditorConfigProvider.js +26 -0
- package/dist/providers/BetterEditorConfigProvider.js.map +1 -0
- package/dist/providers/OverlayProviders.d.ts +8 -0
- package/dist/providers/OverlayProviders.js +22 -0
- package/dist/providers/OverlayProviders.js.map +1 -0
- package/dist/state/useBetterEditorSettings.d.ts +18 -0
- package/dist/state/useBetterEditorSettings.js +65 -0
- package/dist/state/useBetterEditorSettings.js.map +1 -0
- package/dist/state/useEditorHistory.d.ts +16 -0
- package/dist/state/useEditorHistory.js +157 -0
- package/dist/state/useEditorHistory.js.map +1 -0
- package/dist/styles/blocks-tab.css +163 -0
- package/dist/styles/overlay.css +133 -0
- package/dist/styles/preview.css +211 -0
- package/dist/styles/settings-banner.css +73 -0
- package/dist/styles/sidebar.css +88 -0
- package/dist/types.d.ts +41 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/dist/version.js.map +1 -0
- 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"}
|
package/dist/index.d.ts
ADDED
|
@@ -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"}
|