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/internal/dom.ts"],"sourcesContent":["export const BLOCK_ID_ATTR = 'data-better-editor-id'\nexport const BLOCK_ID_SELECTOR = `[${BLOCK_ID_ATTR}]`\nexport const ACTIVE_CLASS = 'better-editor-active'\nexport const ACTIVE_SELECTOR = `.${ACTIVE_CLASS}`\n"],"names":["BLOCK_ID_ATTR","BLOCK_ID_SELECTOR","ACTIVE_CLASS","ACTIVE_SELECTOR"],"mappings":"AAAA,OAAO,MAAMA,gBAAgB,wBAAuB;AACpD,OAAO,MAAMC,oBAAoB,CAAC,CAAC,EAAED,cAAc,CAAC,CAAC,CAAA;AACrD,OAAO,MAAME,eAAe,uBAAsB;AAClD,OAAO,MAAMC,kBAAkB,CAAC,CAAC,EAAED,cAAc,CAAA"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads `iframe.contentDocument` defensively. Throws cross-origin —
|
|
3
|
+
* returning `null` lets callers bail out without try/catch boilerplate.
|
|
4
|
+
*/ export const getSameOriginDocument = (iframe)=>{
|
|
5
|
+
try {
|
|
6
|
+
return iframe.contentDocument;
|
|
7
|
+
} catch {
|
|
8
|
+
return null;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=iframe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/iframe.ts"],"sourcesContent":["/**\n * Reads `iframe.contentDocument` defensively. Throws cross-origin —\n * returning `null` lets callers bail out without try/catch boilerplate.\n */\nexport const getSameOriginDocument = (iframe: HTMLIFrameElement): Document | null => {\n try {\n return iframe.contentDocument\n } catch {\n return null\n }\n}\n"],"names":["getSameOriginDocument","iframe","contentDocument"],"mappings":"AAAA;;;CAGC,GACD,OAAO,MAAMA,wBAAwB,CAACC;IACpC,IAAI;QACF,OAAOA,OAAOC,eAAe;IAC/B,EAAE,OAAM;QACN,OAAO;IACT;AACF,EAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export declare const VIEWPORT_MIN = 240;
|
|
2
|
+
export declare const VIEWPORT_MAX = 2400;
|
|
3
|
+
export declare const clampViewport: (n: number) => number;
|
|
4
|
+
export declare const TABLET_WIDTH_MIN = 320;
|
|
5
|
+
export declare const TABLET_WIDTH_MAX = 1600;
|
|
6
|
+
export declare const MOBILE_WIDTH_MIN = 240;
|
|
7
|
+
export declare const MOBILE_WIDTH_MAX = 800;
|
|
8
|
+
export declare const HOVER_OUTLINE_MIN = 1;
|
|
9
|
+
export declare const HOVER_OUTLINE_MAX = 5;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export const VIEWPORT_MIN = 240;
|
|
2
|
+
export const VIEWPORT_MAX = 2400;
|
|
3
|
+
export const clampViewport = (n)=>Math.min(VIEWPORT_MAX, Math.max(VIEWPORT_MIN, n));
|
|
4
|
+
export const TABLET_WIDTH_MIN = 320;
|
|
5
|
+
export const TABLET_WIDTH_MAX = 1600;
|
|
6
|
+
export const MOBILE_WIDTH_MIN = 240;
|
|
7
|
+
export const MOBILE_WIDTH_MAX = 800;
|
|
8
|
+
export const HOVER_OUTLINE_MIN = 1;
|
|
9
|
+
export const HOVER_OUTLINE_MAX = 5;
|
|
10
|
+
|
|
11
|
+
//# sourceMappingURL=limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/limits.ts"],"sourcesContent":["export const VIEWPORT_MIN = 240\nexport const VIEWPORT_MAX = 2400\n\nexport const clampViewport = (n: number): number =>\n Math.min(VIEWPORT_MAX, Math.max(VIEWPORT_MIN, n))\n\nexport const TABLET_WIDTH_MIN = 320\nexport const TABLET_WIDTH_MAX = 1600\nexport const MOBILE_WIDTH_MIN = 240\nexport const MOBILE_WIDTH_MAX = 800\nexport const HOVER_OUTLINE_MIN = 1\nexport const HOVER_OUTLINE_MAX = 5\n"],"names":["VIEWPORT_MIN","VIEWPORT_MAX","clampViewport","n","Math","min","max","TABLET_WIDTH_MIN","TABLET_WIDTH_MAX","MOBILE_WIDTH_MIN","MOBILE_WIDTH_MAX","HOVER_OUTLINE_MIN","HOVER_OUTLINE_MAX"],"mappings":"AAAA,OAAO,MAAMA,eAAe,IAAG;AAC/B,OAAO,MAAMC,eAAe,KAAI;AAEhC,OAAO,MAAMC,gBAAgB,CAACC,IAC5BC,KAAKC,GAAG,CAACJ,cAAcG,KAAKE,GAAG,CAACN,cAAcG,IAAG;AAEnD,OAAO,MAAMI,mBAAmB,IAAG;AACnC,OAAO,MAAMC,mBAAmB,KAAI;AACpC,OAAO,MAAMC,mBAAmB,IAAG;AACnC,OAAO,MAAMC,mBAAmB,IAAG;AACnC,OAAO,MAAMC,oBAAoB,EAAC;AAClC,OAAO,MAAMC,oBAAoB,EAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const splitFieldPath = (path)=>{
|
|
2
|
+
const lastDot = path.lastIndexOf('.');
|
|
3
|
+
if (lastDot < 0) return null;
|
|
4
|
+
const tail = path.slice(lastDot + 1);
|
|
5
|
+
if (!/^\d+$/.test(tail)) return null;
|
|
6
|
+
return {
|
|
7
|
+
parent: path.slice(0, lastDot),
|
|
8
|
+
index: Number(tail)
|
|
9
|
+
};
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//# sourceMappingURL=path.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/path.ts"],"sourcesContent":["export type SplitFieldPath = { parent: string; index: number } | null\n\nexport const splitFieldPath = (path: string): SplitFieldPath => {\n const lastDot = path.lastIndexOf('.')\n if (lastDot < 0) return null\n const tail = path.slice(lastDot + 1)\n if (!/^\\d+$/.test(tail)) return null\n return { parent: path.slice(0, lastDot), index: Number(tail) }\n}\n"],"names":["splitFieldPath","path","lastDot","lastIndexOf","tail","slice","test","parent","index","Number"],"mappings":"AAEA,OAAO,MAAMA,iBAAiB,CAACC;IAC7B,MAAMC,UAAUD,KAAKE,WAAW,CAAC;IACjC,IAAID,UAAU,GAAG,OAAO;IACxB,MAAME,OAAOH,KAAKI,KAAK,CAACH,UAAU;IAClC,IAAI,CAAC,QAAQI,IAAI,CAACF,OAAO,OAAO;IAChC,OAAO;QAAEG,QAAQN,KAAKI,KAAK,CAAC,GAAGH;QAAUM,OAAOC,OAAOL;IAAM;AAC/D,EAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { isParentInboundMessage } from '../preview/protocol';
|
|
2
|
+
// Same-origin only: parent and iframe live under one Payload host. Any
|
|
3
|
+
// message from a different origin is dropped before reaching `handler`.
|
|
4
|
+
export const postToParent = (message)=>{
|
|
5
|
+
window.parent.postMessage(message, window.location.origin);
|
|
6
|
+
};
|
|
7
|
+
// Click + hover handlers are installed on the iframe document but their
|
|
8
|
+
// closures run in the parent JS context, so postToParent is effectively a
|
|
9
|
+
// same-window self-post (window.parent === window when the admin is
|
|
10
|
+
// top-level). The origin + shape checks below are the actual gate.
|
|
11
|
+
export const listenForParentInbound = (handler)=>{
|
|
12
|
+
const onMessage = (e)=>{
|
|
13
|
+
if (e.origin !== window.location.origin) return;
|
|
14
|
+
if (!isParentInboundMessage(e.data)) return;
|
|
15
|
+
handler(e.data);
|
|
16
|
+
};
|
|
17
|
+
window.addEventListener('message', onMessage);
|
|
18
|
+
return ()=>window.removeEventListener('message', onMessage);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=postmessage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/postmessage.ts"],"sourcesContent":["import type { ParentInboundMessage } from '../preview/protocol'\nimport { isParentInboundMessage } from '../preview/protocol'\n\n// Same-origin only: parent and iframe live under one Payload host. Any\n// message from a different origin is dropped before reaching `handler`.\nexport const postToParent = (message: ParentInboundMessage): void => {\n window.parent.postMessage(message, window.location.origin)\n}\n\n// Click + hover handlers are installed on the iframe document but their\n// closures run in the parent JS context, so postToParent is effectively a\n// same-window self-post (window.parent === window when the admin is\n// top-level). The origin + shape checks below are the actual gate.\nexport const listenForParentInbound = (\n handler: (msg: ParentInboundMessage) => void,\n): (() => void) => {\n const onMessage = (e: MessageEvent) => {\n if (e.origin !== window.location.origin) return\n if (!isParentInboundMessage(e.data)) return\n handler(e.data)\n }\n window.addEventListener('message', onMessage)\n return () => window.removeEventListener('message', onMessage)\n}\n"],"names":["isParentInboundMessage","postToParent","message","window","parent","postMessage","location","origin","listenForParentInbound","handler","onMessage","e","data","addEventListener","removeEventListener"],"mappings":"AACA,SAASA,sBAAsB,QAAQ,sBAAqB;AAE5D,uEAAuE;AACvE,wEAAwE;AACxE,OAAO,MAAMC,eAAe,CAACC;IAC3BC,OAAOC,MAAM,CAACC,WAAW,CAACH,SAASC,OAAOG,QAAQ,CAACC,MAAM;AAC3D,EAAC;AAED,wEAAwE;AACxE,0EAA0E;AAC1E,oEAAoE;AACpE,mEAAmE;AACnE,OAAO,MAAMC,yBAAyB,CACpCC;IAEA,MAAMC,YAAY,CAACC;QACjB,IAAIA,EAAEJ,MAAM,KAAKJ,OAAOG,QAAQ,CAACC,MAAM,EAAE;QACzC,IAAI,CAACP,uBAAuBW,EAAEC,IAAI,GAAG;QACrCH,QAAQE,EAAEC,IAAI;IAChB;IACAT,OAAOU,gBAAgB,CAAC,WAAWH;IACnC,OAAO,IAAMP,OAAOW,mBAAmB,CAAC,WAAWJ;AACrD,EAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export declare const DEFAULT_STORAGE_NAMESPACE = "better-editor";
|
|
2
|
+
export type StorageKeys = {
|
|
3
|
+
sidebarWidth: string;
|
|
4
|
+
responsiveWidth: string;
|
|
5
|
+
togglePreference: (collectionSlug?: string, globalSlug?: string) => string;
|
|
6
|
+
};
|
|
7
|
+
export declare const buildStorageKeys: (namespace?: string) => StorageKeys;
|
|
8
|
+
export declare const DEFAULT_STORAGE_KEYS: StorageKeys;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const DEFAULT_STORAGE_NAMESPACE = 'better-editor';
|
|
2
|
+
export const buildStorageKeys = (namespace = DEFAULT_STORAGE_NAMESPACE)=>({
|
|
3
|
+
sidebarWidth: `${namespace}:sidebar-width`,
|
|
4
|
+
responsiveWidth: `${namespace}:responsive-width`,
|
|
5
|
+
togglePreference: (collectionSlug, globalSlug)=>`${namespace}:${collectionSlug ? `collection-${collectionSlug}` : `global-${globalSlug ?? 'unknown'}`}`
|
|
6
|
+
});
|
|
7
|
+
export const DEFAULT_STORAGE_KEYS = buildStorageKeys();
|
|
8
|
+
|
|
9
|
+
//# sourceMappingURL=storage-keys.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/storage-keys.ts"],"sourcesContent":["export const DEFAULT_STORAGE_NAMESPACE = 'better-editor'\n\nexport type StorageKeys = {\n sidebarWidth: string\n responsiveWidth: string\n togglePreference: (collectionSlug?: string, globalSlug?: string) => string\n}\n\nexport const buildStorageKeys = (namespace: string = DEFAULT_STORAGE_NAMESPACE): StorageKeys => ({\n sidebarWidth: `${namespace}:sidebar-width`,\n responsiveWidth: `${namespace}:responsive-width`,\n togglePreference: (collectionSlug, globalSlug) =>\n `${namespace}:${collectionSlug ? `collection-${collectionSlug}` : `global-${globalSlug ?? 'unknown'}`}`,\n})\n\nexport const DEFAULT_STORAGE_KEYS = buildStorageKeys()\n"],"names":["DEFAULT_STORAGE_NAMESPACE","buildStorageKeys","namespace","sidebarWidth","responsiveWidth","togglePreference","collectionSlug","globalSlug","DEFAULT_STORAGE_KEYS"],"mappings":"AAAA,OAAO,MAAMA,4BAA4B,gBAAe;AAQxD,OAAO,MAAMC,mBAAmB,CAACC,YAAoBF,yBAAyB,GAAmB,CAAA;QAC/FG,cAAc,GAAGD,UAAU,cAAc,CAAC;QAC1CE,iBAAiB,GAAGF,UAAU,iBAAiB,CAAC;QAChDG,kBAAkB,CAACC,gBAAgBC,aACjC,GAAGL,UAAU,CAAC,EAAEI,iBAAiB,CAAC,WAAW,EAAEA,gBAAgB,GAAG,CAAC,OAAO,EAAEC,cAAc,WAAW,EAAE;IAC3G,CAAA,EAAE;AAEF,OAAO,MAAMC,uBAAuBP,mBAAkB"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export const readNumber = (key, fallback, clamp)=>{
|
|
2
|
+
if (typeof window === 'undefined') return fallback;
|
|
3
|
+
try {
|
|
4
|
+
const raw = window.localStorage.getItem(key);
|
|
5
|
+
const parsed = raw == null ? NaN : Number(raw);
|
|
6
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
7
|
+
return clamp ? clamp(parsed) : parsed;
|
|
8
|
+
} catch {
|
|
9
|
+
return fallback;
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
export const writeString = (key, value)=>{
|
|
13
|
+
if (typeof window === 'undefined') return;
|
|
14
|
+
try {
|
|
15
|
+
window.localStorage.setItem(key, value);
|
|
16
|
+
} catch {
|
|
17
|
+
/* storage unavailable / quota exceeded */ }
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/storage.ts"],"sourcesContent":["export const readNumber = (key: string, fallback: number, clamp?: (n: number) => number): number => {\n if (typeof window === 'undefined') return fallback\n try {\n const raw = window.localStorage.getItem(key)\n const parsed = raw == null ? NaN : Number(raw)\n if (!Number.isFinite(parsed)) return fallback\n return clamp ? clamp(parsed) : parsed\n } catch {\n return fallback\n }\n}\n\nexport const writeString = (key: string, value: string): void => {\n if (typeof window === 'undefined') return\n try {\n window.localStorage.setItem(key, value)\n } catch {\n /* storage unavailable / quota exceeded */\n }\n}\n"],"names":["readNumber","key","fallback","clamp","window","raw","localStorage","getItem","parsed","NaN","Number","isFinite","writeString","value","setItem"],"mappings":"AAAA,OAAO,MAAMA,aAAa,CAACC,KAAaC,UAAkBC;IACxD,IAAI,OAAOC,WAAW,aAAa,OAAOF;IAC1C,IAAI;QACF,MAAMG,MAAMD,OAAOE,YAAY,CAACC,OAAO,CAACN;QACxC,MAAMO,SAASH,OAAO,OAAOI,MAAMC,OAAOL;QAC1C,IAAI,CAACK,OAAOC,QAAQ,CAACH,SAAS,OAAON;QACrC,OAAOC,QAAQA,MAAMK,UAAUA;IACjC,EAAE,OAAM;QACN,OAAON;IACT;AACF,EAAC;AAED,OAAO,MAAMU,cAAc,CAACX,KAAaY;IACvC,IAAI,OAAOT,WAAW,aAAa;IACnC,IAAI;QACFA,OAAOE,YAAY,CAACQ,OAAO,CAACb,KAAKY;IACnC,EAAE,OAAM;IACN,wCAAwC,GAC1C;AACF,EAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import type { BlockActionMessage } from './protocol';
|
|
3
|
+
type Action = BlockActionMessage['action'];
|
|
4
|
+
export type HoverToolbarProps = {
|
|
5
|
+
onAction: (action: Action) => void;
|
|
6
|
+
};
|
|
7
|
+
export declare const HoverToolbar: React.FC<HoverToolbarProps>;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
|
|
3
|
+
import React from 'react';
|
|
4
|
+
import { ChevronDown, ChevronUp, CopyIcon, PlusIcon, TrashIcon } from '../admin/icons';
|
|
5
|
+
const BUTTONS = [
|
|
6
|
+
{
|
|
7
|
+
action: 'move-up',
|
|
8
|
+
Icon: ChevronUp,
|
|
9
|
+
label: 'Move up'
|
|
10
|
+
},
|
|
11
|
+
{
|
|
12
|
+
action: 'move-down',
|
|
13
|
+
Icon: ChevronDown,
|
|
14
|
+
label: 'Move down'
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
action: 'duplicate',
|
|
18
|
+
Icon: CopyIcon,
|
|
19
|
+
label: 'Duplicate'
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
action: 'add',
|
|
23
|
+
Icon: PlusIcon,
|
|
24
|
+
label: 'Add block below'
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
action: 'delete',
|
|
28
|
+
Icon: TrashIcon,
|
|
29
|
+
label: 'Delete'
|
|
30
|
+
}
|
|
31
|
+
];
|
|
32
|
+
export const HoverToolbar = ({ onAction })=>/*#__PURE__*/ _jsx(_Fragment, {
|
|
33
|
+
children: BUTTONS.map(({ action, Icon, label })=>/*#__PURE__*/ _jsx("button", {
|
|
34
|
+
type: "button",
|
|
35
|
+
"aria-label": label,
|
|
36
|
+
title: label,
|
|
37
|
+
"data-action": action,
|
|
38
|
+
onClick: (e)=>{
|
|
39
|
+
e.stopPropagation();
|
|
40
|
+
onAction(action);
|
|
41
|
+
},
|
|
42
|
+
children: /*#__PURE__*/ _jsx(Icon, {
|
|
43
|
+
size: 14
|
|
44
|
+
})
|
|
45
|
+
}, action))
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=HoverToolbar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/preview/HoverToolbar.tsx"],"sourcesContent":["'use client'\n\nimport React from 'react'\nimport { ChevronDown, ChevronUp, CopyIcon, PlusIcon, TrashIcon } from '../admin/icons'\nimport type { BlockActionMessage } from './protocol'\n\ntype Action = BlockActionMessage['action']\n\nconst BUTTONS: ReadonlyArray<{\n action: Action\n Icon: React.ComponentType<{ size?: number }>\n label: string\n}> = [\n { action: 'move-up', Icon: ChevronUp, label: 'Move up' },\n { action: 'move-down', Icon: ChevronDown, label: 'Move down' },\n { action: 'duplicate', Icon: CopyIcon, label: 'Duplicate' },\n { action: 'add', Icon: PlusIcon, label: 'Add block below' },\n { action: 'delete', Icon: TrashIcon, label: 'Delete' },\n]\n\nexport type HoverToolbarProps = {\n onAction: (action: Action) => void\n}\n\nexport const HoverToolbar: React.FC<HoverToolbarProps> = ({ onAction }) => (\n <>\n {BUTTONS.map(({ action, Icon, label }) => (\n <button\n key={action}\n type=\"button\"\n aria-label={label}\n title={label}\n data-action={action}\n onClick={(e) => {\n e.stopPropagation()\n onAction(action)\n }}\n >\n <Icon size={14} />\n </button>\n ))}\n </>\n)\n"],"names":["React","ChevronDown","ChevronUp","CopyIcon","PlusIcon","TrashIcon","BUTTONS","action","Icon","label","HoverToolbar","onAction","map","button","type","aria-label","title","data-action","onClick","e","stopPropagation","size"],"mappings":"AAAA;;AAEA,OAAOA,WAAW,QAAO;AACzB,SAASC,WAAW,EAAEC,SAAS,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,SAAS,QAAQ,iBAAgB;AAKtF,MAAMC,UAID;IACH;QAAEC,QAAQ;QAAWC,MAAMN;QAAWO,OAAO;IAAU;IACvD;QAAEF,QAAQ;QAAaC,MAAMP;QAAaQ,OAAO;IAAY;IAC7D;QAAEF,QAAQ;QAAaC,MAAML;QAAUM,OAAO;IAAY;IAC1D;QAAEF,QAAQ;QAAOC,MAAMJ;QAAUK,OAAO;IAAkB;IAC1D;QAAEF,QAAQ;QAAUC,MAAMH;QAAWI,OAAO;IAAS;CACtD;AAMD,OAAO,MAAMC,eAA4C,CAAC,EAAEC,QAAQ,EAAE,iBACpE;kBACGL,QAAQM,GAAG,CAAC,CAAC,EAAEL,MAAM,EAAEC,IAAI,EAAEC,KAAK,EAAE,iBACnC,KAACI;gBAECC,MAAK;gBACLC,cAAYN;gBACZO,OAAOP;gBACPQ,eAAaV;gBACbW,SAAS,CAACC;oBACRA,EAAEC,eAAe;oBACjBT,SAASJ;gBACX;0BAEA,cAAA,KAACC;oBAAKa,MAAM;;eAVPd;OAcZ"}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import type { HoverToolbarPosition } from '../internal/constants';
|
|
2
|
+
import type { BlockActionMessage } from './protocol';
|
|
3
|
+
export type HoverToolbarOptions = {
|
|
4
|
+
position: HoverToolbarPosition;
|
|
5
|
+
outlineWidth: number;
|
|
6
|
+
onAction: (id: string, action: BlockActionMessage['action']) => void;
|
|
7
|
+
};
|
|
8
|
+
export declare class HoverToolbarController {
|
|
9
|
+
private readonly doc;
|
|
10
|
+
private opts;
|
|
11
|
+
private readonly toolbar;
|
|
12
|
+
private readonly root;
|
|
13
|
+
private destroyed;
|
|
14
|
+
private currentBlockId;
|
|
15
|
+
private currentBlockEl;
|
|
16
|
+
private activeChain;
|
|
17
|
+
private positionRaf;
|
|
18
|
+
private observerRaf;
|
|
19
|
+
private readonly onScroll;
|
|
20
|
+
private readonly observer;
|
|
21
|
+
constructor(doc: Document, opts: HoverToolbarOptions);
|
|
22
|
+
private scheduleReselect;
|
|
23
|
+
update(opts: HoverToolbarOptions): void;
|
|
24
|
+
select(id: string): void;
|
|
25
|
+
deselect(): void;
|
|
26
|
+
destroy(): void;
|
|
27
|
+
private scheduleReposition;
|
|
28
|
+
private positionToolbar;
|
|
29
|
+
private clearActive;
|
|
30
|
+
private markActiveChain;
|
|
31
|
+
}
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
import { ACTIVE_CLASS, BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom';
|
|
4
|
+
import { TOOLBAR_ID } from './hover-css';
|
|
5
|
+
import { HoverToolbar } from './HoverToolbar';
|
|
6
|
+
import { calculateToolbarPosition } from './toolbar-position';
|
|
7
|
+
const FALLBACK_TB_WIDTH = 120;
|
|
8
|
+
const FALLBACK_TB_HEIGHT = 32;
|
|
9
|
+
export class HoverToolbarController {
|
|
10
|
+
doc;
|
|
11
|
+
opts;
|
|
12
|
+
toolbar;
|
|
13
|
+
root;
|
|
14
|
+
destroyed = false;
|
|
15
|
+
currentBlockId = null;
|
|
16
|
+
currentBlockEl = null;
|
|
17
|
+
activeChain = [];
|
|
18
|
+
positionRaf = 0;
|
|
19
|
+
observerRaf = 0;
|
|
20
|
+
onScroll;
|
|
21
|
+
observer;
|
|
22
|
+
constructor(doc, opts){
|
|
23
|
+
this.doc = doc;
|
|
24
|
+
this.opts = opts;
|
|
25
|
+
doc.getElementById(TOOLBAR_ID)?.remove();
|
|
26
|
+
const toolbar = doc.createElement('div');
|
|
27
|
+
toolbar.id = TOOLBAR_ID;
|
|
28
|
+
doc.body.appendChild(toolbar);
|
|
29
|
+
this.toolbar = toolbar;
|
|
30
|
+
this.root = createRoot(toolbar);
|
|
31
|
+
this.root.render(React.createElement(HoverToolbar, {
|
|
32
|
+
onAction: (action)=>{
|
|
33
|
+
if (this.currentBlockId) this.opts.onAction(this.currentBlockId, action);
|
|
34
|
+
}
|
|
35
|
+
}));
|
|
36
|
+
this.onScroll = ()=>this.scheduleReposition();
|
|
37
|
+
doc.defaultView?.addEventListener('scroll', this.onScroll, true);
|
|
38
|
+
this.observer = new MutationObserver(()=>{
|
|
39
|
+
if (this.destroyed || !this.currentBlockId) return;
|
|
40
|
+
// Coalesce mutation bursts into a single re-select on the next paint.
|
|
41
|
+
// Re-using positionRaf avoids the previous two-tier RAF pyramid.
|
|
42
|
+
this.scheduleReselect();
|
|
43
|
+
});
|
|
44
|
+
this.observer.observe(doc.body, {
|
|
45
|
+
childList: true,
|
|
46
|
+
subtree: true
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
scheduleReselect() {
|
|
50
|
+
const view = this.doc.defaultView;
|
|
51
|
+
if (!view || this.observerRaf) return;
|
|
52
|
+
this.observerRaf = view.requestAnimationFrame(()=>{
|
|
53
|
+
this.observerRaf = 0;
|
|
54
|
+
if (this.destroyed || !this.currentBlockId) return;
|
|
55
|
+
this.select(this.currentBlockId);
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
update(opts) {
|
|
59
|
+
this.opts = opts;
|
|
60
|
+
if (this.currentBlockEl) this.scheduleReposition();
|
|
61
|
+
}
|
|
62
|
+
select(id) {
|
|
63
|
+
if (this.destroyed) return;
|
|
64
|
+
const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/["\\]/g, '\\$&');
|
|
65
|
+
const el = this.doc.querySelector(`[${BLOCK_ID_ATTR}="${escaped}"]`);
|
|
66
|
+
if (!el) {
|
|
67
|
+
// Block not in DOM (yet) — keep id but hide the toolbar; a later
|
|
68
|
+
// select(id) call after the iframe re-render will resolve it.
|
|
69
|
+
this.currentBlockId = id;
|
|
70
|
+
this.currentBlockEl = null;
|
|
71
|
+
this.clearActive();
|
|
72
|
+
this.toolbar.classList.remove('is-visible');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
this.currentBlockId = id;
|
|
76
|
+
this.currentBlockEl = el;
|
|
77
|
+
this.markActiveChain(el);
|
|
78
|
+
const isNested = this.activeChain.length > 1;
|
|
79
|
+
this.toolbar.dataset.nested = isNested ? '1' : '0';
|
|
80
|
+
this.toolbar.classList.add('is-visible');
|
|
81
|
+
this.scheduleReposition();
|
|
82
|
+
}
|
|
83
|
+
deselect() {
|
|
84
|
+
if (this.destroyed) return;
|
|
85
|
+
if (!this.currentBlockId) return;
|
|
86
|
+
this.clearActive();
|
|
87
|
+
this.currentBlockId = null;
|
|
88
|
+
this.currentBlockEl = null;
|
|
89
|
+
this.toolbar.classList.remove('is-visible');
|
|
90
|
+
}
|
|
91
|
+
destroy() {
|
|
92
|
+
if (this.destroyed) return;
|
|
93
|
+
this.destroyed = true;
|
|
94
|
+
this.observer.disconnect();
|
|
95
|
+
const view = this.doc.defaultView;
|
|
96
|
+
view?.removeEventListener('scroll', this.onScroll, true);
|
|
97
|
+
if (this.positionRaf) view?.cancelAnimationFrame(this.positionRaf);
|
|
98
|
+
if (this.observerRaf) view?.cancelAnimationFrame(this.observerRaf);
|
|
99
|
+
this.positionRaf = 0;
|
|
100
|
+
this.observerRaf = 0;
|
|
101
|
+
this.clearActive();
|
|
102
|
+
this.currentBlockId = null;
|
|
103
|
+
this.currentBlockEl = null;
|
|
104
|
+
// Defer unmount — React 19 throws if it lands synchronously mid-render of
|
|
105
|
+
// another tree; StrictMode also double-invokes us, so only remove the
|
|
106
|
+
// toolbar node if we still own it.
|
|
107
|
+
const { root, toolbar } = this;
|
|
108
|
+
queueMicrotask(()=>{
|
|
109
|
+
try {
|
|
110
|
+
root.unmount();
|
|
111
|
+
} catch {}
|
|
112
|
+
if (toolbar.isConnected) toolbar.remove();
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
scheduleReposition() {
|
|
116
|
+
const view = this.doc.defaultView;
|
|
117
|
+
if (!view) {
|
|
118
|
+
this.positionToolbar();
|
|
119
|
+
return;
|
|
120
|
+
}
|
|
121
|
+
if (this.positionRaf) view.cancelAnimationFrame(this.positionRaf);
|
|
122
|
+
this.positionRaf = view.requestAnimationFrame(()=>{
|
|
123
|
+
this.positionRaf = 0;
|
|
124
|
+
this.positionToolbar();
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
positionToolbar() {
|
|
128
|
+
const el = this.currentBlockEl;
|
|
129
|
+
if (!el || !el.isConnected) return;
|
|
130
|
+
const view = this.doc.defaultView;
|
|
131
|
+
if (!view) return;
|
|
132
|
+
const { top, left } = calculateToolbarPosition(el.getBoundingClientRect(), {
|
|
133
|
+
width: this.toolbar.offsetWidth || FALLBACK_TB_WIDTH,
|
|
134
|
+
height: this.toolbar.offsetHeight || FALLBACK_TB_HEIGHT
|
|
135
|
+
}, {
|
|
136
|
+
scrollX: view.scrollX,
|
|
137
|
+
scrollY: view.scrollY
|
|
138
|
+
}, this.opts.position, this.opts.outlineWidth);
|
|
139
|
+
const { style } = this.toolbar;
|
|
140
|
+
style.top = `${top}px`;
|
|
141
|
+
style.left = `${left}px`;
|
|
142
|
+
style.right = 'auto';
|
|
143
|
+
}
|
|
144
|
+
// Only clears the chain we marked, avoiding a full-document scan.
|
|
145
|
+
clearActive() {
|
|
146
|
+
for (const node of this.activeChain)node.classList.remove(ACTIVE_CLASS);
|
|
147
|
+
this.activeChain = [];
|
|
148
|
+
}
|
|
149
|
+
markActiveChain(el) {
|
|
150
|
+
this.clearActive();
|
|
151
|
+
const chain = [];
|
|
152
|
+
for(let cur = el; cur; cur = cur.parentElement?.closest(BLOCK_ID_SELECTOR) ?? null){
|
|
153
|
+
cur.classList.add(ACTIVE_CLASS);
|
|
154
|
+
chain.push(cur);
|
|
155
|
+
}
|
|
156
|
+
this.activeChain = chain;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
//# sourceMappingURL=HoverToolbarController.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/preview/HoverToolbarController.ts"],"sourcesContent":["import React from 'react'\nimport { createRoot, type Root } from 'react-dom/client'\nimport type { HoverToolbarPosition } from '../internal/constants'\nimport { ACTIVE_CLASS, BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom'\nimport type { BlockActionMessage } from './protocol'\nimport { TOOLBAR_ID } from './hover-css'\nimport { HoverToolbar } from './HoverToolbar'\nimport { calculateToolbarPosition } from './toolbar-position'\n\nexport type HoverToolbarOptions = {\n position: HoverToolbarPosition\n outlineWidth: number\n onAction: (id: string, action: BlockActionMessage['action']) => void\n}\n\nconst FALLBACK_TB_WIDTH = 120\nconst FALLBACK_TB_HEIGHT = 32\n\nexport class HoverToolbarController {\n private readonly doc: Document\n private opts: HoverToolbarOptions\n private readonly toolbar: HTMLDivElement\n private readonly root: Root\n private destroyed = false\n private currentBlockId: string | null = null\n private currentBlockEl: HTMLElement | null = null\n private activeChain: HTMLElement[] = []\n private positionRaf = 0\n private observerRaf = 0\n private readonly onScroll: () => void\n private readonly observer: MutationObserver\n\n constructor(doc: Document, opts: HoverToolbarOptions) {\n this.doc = doc\n this.opts = opts\n\n doc.getElementById(TOOLBAR_ID)?.remove()\n const toolbar = doc.createElement('div')\n toolbar.id = TOOLBAR_ID\n doc.body.appendChild(toolbar)\n this.toolbar = toolbar\n\n this.root = createRoot(toolbar)\n this.root.render(\n React.createElement(HoverToolbar, {\n onAction: (action) => {\n if (this.currentBlockId) this.opts.onAction(this.currentBlockId, action)\n },\n }),\n )\n\n this.onScroll = () => this.scheduleReposition()\n doc.defaultView?.addEventListener('scroll', this.onScroll, true)\n\n this.observer = new MutationObserver(() => {\n if (this.destroyed || !this.currentBlockId) return\n // Coalesce mutation bursts into a single re-select on the next paint.\n // Re-using positionRaf avoids the previous two-tier RAF pyramid.\n this.scheduleReselect()\n })\n this.observer.observe(doc.body, { childList: true, subtree: true })\n }\n\n private scheduleReselect(): void {\n const view = this.doc.defaultView\n if (!view || this.observerRaf) return\n this.observerRaf = view.requestAnimationFrame(() => {\n this.observerRaf = 0\n if (this.destroyed || !this.currentBlockId) return\n this.select(this.currentBlockId)\n })\n }\n\n update(opts: HoverToolbarOptions): void {\n this.opts = opts\n if (this.currentBlockEl) this.scheduleReposition()\n }\n\n select(id: string): void {\n if (this.destroyed) return\n const escaped = typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(id) : id.replace(/[\"\\\\]/g, '\\\\$&')\n const el = this.doc.querySelector<HTMLElement>(`[${BLOCK_ID_ATTR}=\"${escaped}\"]`)\n if (!el) {\n // Block not in DOM (yet) — keep id but hide the toolbar; a later\n // select(id) call after the iframe re-render will resolve it.\n this.currentBlockId = id\n this.currentBlockEl = null\n this.clearActive()\n this.toolbar.classList.remove('is-visible')\n return\n }\n this.currentBlockId = id\n this.currentBlockEl = el\n this.markActiveChain(el)\n const isNested = this.activeChain.length > 1\n this.toolbar.dataset.nested = isNested ? '1' : '0'\n this.toolbar.classList.add('is-visible')\n this.scheduleReposition()\n }\n\n deselect(): void {\n if (this.destroyed) return\n if (!this.currentBlockId) return\n this.clearActive()\n this.currentBlockId = null\n this.currentBlockEl = null\n this.toolbar.classList.remove('is-visible')\n }\n\n destroy(): void {\n if (this.destroyed) return\n this.destroyed = true\n this.observer.disconnect()\n const view = this.doc.defaultView\n view?.removeEventListener('scroll', this.onScroll, true)\n if (this.positionRaf) view?.cancelAnimationFrame(this.positionRaf)\n if (this.observerRaf) view?.cancelAnimationFrame(this.observerRaf)\n this.positionRaf = 0\n this.observerRaf = 0\n this.clearActive()\n this.currentBlockId = null\n this.currentBlockEl = null\n // Defer unmount — React 19 throws if it lands synchronously mid-render of\n // another tree; StrictMode also double-invokes us, so only remove the\n // toolbar node if we still own it.\n const { root, toolbar } = this\n queueMicrotask(() => {\n try { root.unmount() } catch { /* already unmounted */ }\n if (toolbar.isConnected) toolbar.remove()\n })\n }\n\n private scheduleReposition(): void {\n const view = this.doc.defaultView\n if (!view) {\n this.positionToolbar()\n return\n }\n if (this.positionRaf) view.cancelAnimationFrame(this.positionRaf)\n this.positionRaf = view.requestAnimationFrame(() => {\n this.positionRaf = 0\n this.positionToolbar()\n })\n }\n\n private positionToolbar(): void {\n const el = this.currentBlockEl\n if (!el || !el.isConnected) return\n const view = this.doc.defaultView\n if (!view) return\n const { top, left } = calculateToolbarPosition(\n el.getBoundingClientRect(),\n {\n width: this.toolbar.offsetWidth || FALLBACK_TB_WIDTH,\n height: this.toolbar.offsetHeight || FALLBACK_TB_HEIGHT,\n },\n { scrollX: view.scrollX, scrollY: view.scrollY },\n this.opts.position,\n this.opts.outlineWidth,\n )\n const { style } = this.toolbar\n style.top = `${top}px`\n style.left = `${left}px`\n style.right = 'auto'\n }\n\n // Only clears the chain we marked, avoiding a full-document scan.\n private clearActive(): void {\n for (const node of this.activeChain) node.classList.remove(ACTIVE_CLASS)\n this.activeChain = []\n }\n\n private markActiveChain(el: HTMLElement): void {\n this.clearActive()\n const chain: HTMLElement[] = []\n for (\n let cur: HTMLElement | null = el;\n cur;\n cur = cur.parentElement?.closest<HTMLElement>(BLOCK_ID_SELECTOR) ?? null\n ) {\n cur.classList.add(ACTIVE_CLASS)\n chain.push(cur)\n }\n this.activeChain = chain\n }\n}\n"],"names":["React","createRoot","ACTIVE_CLASS","BLOCK_ID_ATTR","BLOCK_ID_SELECTOR","TOOLBAR_ID","HoverToolbar","calculateToolbarPosition","FALLBACK_TB_WIDTH","FALLBACK_TB_HEIGHT","HoverToolbarController","doc","opts","toolbar","root","destroyed","currentBlockId","currentBlockEl","activeChain","positionRaf","observerRaf","onScroll","observer","constructor","getElementById","remove","createElement","id","body","appendChild","render","onAction","action","scheduleReposition","defaultView","addEventListener","MutationObserver","scheduleReselect","observe","childList","subtree","view","requestAnimationFrame","select","update","escaped","CSS","escape","replace","el","querySelector","clearActive","classList","markActiveChain","isNested","length","dataset","nested","add","deselect","destroy","disconnect","removeEventListener","cancelAnimationFrame","queueMicrotask","unmount","isConnected","positionToolbar","top","left","getBoundingClientRect","width","offsetWidth","height","offsetHeight","scrollX","scrollY","position","outlineWidth","style","right","node","chain","cur","parentElement","closest","push"],"mappings":"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"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const HOVER_STYLE_ID = "better-editor-hover-style";
|
|
2
|
+
export declare const TOOLBAR_ID = "better-editor-block-toolbar";
|
|
3
|
+
export declare const INTERACT_BODY_ATTR = "data-bee-interact";
|
|
4
|
+
export declare const HOVER_CSS = "\n body:not([data-bee-interact]) [data-better-editor-id] { cursor: pointer; }\n body:not([data-bee-interact]) [data-better-editor-id]:hover,\n body:not([data-bee-interact]) [data-better-editor-id].better-editor-active {\n outline: var(--bee-outline-width) solid var(--bee-top);\n outline-offset: calc(-1 * var(--bee-outline-width) - 1px);\n background-color: color-mix(in srgb, var(--bee-top) 10%, transparent);\n }\n body:not([data-bee-interact]) [data-better-editor-id] [data-better-editor-id]:hover,\n body:not([data-bee-interact]) [data-better-editor-id] [data-better-editor-id].better-editor-active {\n outline-color: var(--bee-nested);\n background-color: color-mix(in srgb, var(--bee-nested) 10%, transparent);\n }\n body[data-bee-interact] #better-editor-block-toolbar { display: none; }\n\n #better-editor-block-toolbar {\n position: absolute;\n z-index: var(--better-editor-z-toolbar, 2147483647);\n display: none;\n gap: 2px;\n padding: 3px;\n border-radius: 4px;\n background: var(--bee-top);\n color: #fff;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);\n font-family: system-ui, sans-serif;\n }\n #better-editor-block-toolbar[data-nested=\"1\"] { background: var(--bee-nested); }\n #better-editor-block-toolbar.is-visible { display: inline-flex; }\n #better-editor-block-toolbar button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n }\n #better-editor-block-toolbar button:hover { background: rgba(255, 255, 255, 0.18); }\n #better-editor-block-toolbar button[data-action=\"delete\"]:hover { background: rgba(0, 0, 0, 0.25); }\n";
|
|
5
|
+
export type HoverVars = {
|
|
6
|
+
topColor: string;
|
|
7
|
+
nestedColor: string;
|
|
8
|
+
outlineWidth: number;
|
|
9
|
+
};
|
|
10
|
+
export declare const setHoverVars: (doc: Document, vars: HoverVars) => void;
|
|
11
|
+
export declare const clearHoverVars: (doc: Document) => void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { ACTIVE_CLASS, BLOCK_ID_ATTR } from '../internal/dom';
|
|
2
|
+
export const HOVER_STYLE_ID = 'better-editor-hover-style';
|
|
3
|
+
export const TOOLBAR_ID = 'better-editor-block-toolbar';
|
|
4
|
+
// Set on doc.body to disable hover affordances + click-to-focus while
|
|
5
|
+
// the user wants to interact with the consumer page (forms, accordions,
|
|
6
|
+
// links). All hover/active rules below are gated on its absence.
|
|
7
|
+
export const INTERACT_BODY_ATTR = 'data-bee-interact';
|
|
8
|
+
const VAR_TOP = '--bee-top';
|
|
9
|
+
const VAR_NESTED = '--bee-nested';
|
|
10
|
+
const VAR_OUTLINE_WIDTH = '--bee-outline-width';
|
|
11
|
+
export const HOVER_CSS = `
|
|
12
|
+
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] { cursor: pointer; }
|
|
13
|
+
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}]:hover,
|
|
14
|
+
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {
|
|
15
|
+
outline: var(${VAR_OUTLINE_WIDTH}) solid var(${VAR_TOP});
|
|
16
|
+
outline-offset: calc(-1 * var(${VAR_OUTLINE_WIDTH}) - 1px);
|
|
17
|
+
background-color: color-mix(in srgb, var(${VAR_TOP}) 10%, transparent);
|
|
18
|
+
}
|
|
19
|
+
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}]:hover,
|
|
20
|
+
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {
|
|
21
|
+
outline-color: var(${VAR_NESTED});
|
|
22
|
+
background-color: color-mix(in srgb, var(${VAR_NESTED}) 10%, transparent);
|
|
23
|
+
}
|
|
24
|
+
body[${INTERACT_BODY_ATTR}] #${TOOLBAR_ID} { display: none; }
|
|
25
|
+
|
|
26
|
+
#${TOOLBAR_ID} {
|
|
27
|
+
position: absolute;
|
|
28
|
+
z-index: var(--better-editor-z-toolbar, 2147483647);
|
|
29
|
+
display: none;
|
|
30
|
+
gap: 2px;
|
|
31
|
+
padding: 3px;
|
|
32
|
+
border-radius: 4px;
|
|
33
|
+
background: var(${VAR_TOP});
|
|
34
|
+
color: #fff;
|
|
35
|
+
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);
|
|
36
|
+
font-family: system-ui, sans-serif;
|
|
37
|
+
}
|
|
38
|
+
#${TOOLBAR_ID}[data-nested="1"] { background: var(${VAR_NESTED}); }
|
|
39
|
+
#${TOOLBAR_ID}.is-visible { display: inline-flex; }
|
|
40
|
+
#${TOOLBAR_ID} button {
|
|
41
|
+
display: inline-flex;
|
|
42
|
+
align-items: center;
|
|
43
|
+
justify-content: center;
|
|
44
|
+
width: 26px;
|
|
45
|
+
height: 26px;
|
|
46
|
+
padding: 0;
|
|
47
|
+
border: 0;
|
|
48
|
+
border-radius: 3px;
|
|
49
|
+
background: transparent;
|
|
50
|
+
color: inherit;
|
|
51
|
+
cursor: pointer;
|
|
52
|
+
}
|
|
53
|
+
#${TOOLBAR_ID} button:hover { background: rgba(255, 255, 255, 0.18); }
|
|
54
|
+
#${TOOLBAR_ID} button[data-action="delete"]:hover { background: rgba(0, 0, 0, 0.25); }
|
|
55
|
+
`;
|
|
56
|
+
// Restrictive on purpose: these values are written verbatim into a CSS
|
|
57
|
+
// custom property on the preview iframe, so anything that survives the
|
|
58
|
+
// regex still lands inside `outline:` / `background-color:`. The regex
|
|
59
|
+
// rejects newlines and unmatched parens so an injected value can't escape
|
|
60
|
+
// into a separate declaration.
|
|
61
|
+
const COLOR_RE = /^(?:#[0-9a-fA-F]{3,8}|rgba?\([^()\n\r]*\))$/i;
|
|
62
|
+
const isValidColor = (v)=>typeof v === 'string' && COLOR_RE.test(v.trim());
|
|
63
|
+
const isValidOutline = (v)=>typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 50;
|
|
64
|
+
const warnInvalid = (kind, value)=>{
|
|
65
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
66
|
+
console.warn(`[better-editor] ignoring invalid ${kind}:`, value);
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
export const setHoverVars = (doc, vars)=>{
|
|
70
|
+
const root = doc.documentElement;
|
|
71
|
+
if (isValidColor(vars.topColor)) {
|
|
72
|
+
root.style.setProperty(VAR_TOP, vars.topColor);
|
|
73
|
+
} else {
|
|
74
|
+
warnInvalid('topColor', vars.topColor);
|
|
75
|
+
}
|
|
76
|
+
if (isValidColor(vars.nestedColor)) {
|
|
77
|
+
root.style.setProperty(VAR_NESTED, vars.nestedColor);
|
|
78
|
+
} else {
|
|
79
|
+
warnInvalid('nestedColor', vars.nestedColor);
|
|
80
|
+
}
|
|
81
|
+
if (isValidOutline(vars.outlineWidth)) {
|
|
82
|
+
root.style.setProperty(VAR_OUTLINE_WIDTH, `${vars.outlineWidth}px`);
|
|
83
|
+
} else {
|
|
84
|
+
warnInvalid('outlineWidth', vars.outlineWidth);
|
|
85
|
+
}
|
|
86
|
+
};
|
|
87
|
+
export const clearHoverVars = (doc)=>{
|
|
88
|
+
const root = doc.documentElement;
|
|
89
|
+
root.style.removeProperty(VAR_TOP);
|
|
90
|
+
root.style.removeProperty(VAR_NESTED);
|
|
91
|
+
root.style.removeProperty(VAR_OUTLINE_WIDTH);
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
//# sourceMappingURL=hover-css.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/preview/hover-css.ts"],"sourcesContent":["import { ACTIVE_CLASS, BLOCK_ID_ATTR } from '../internal/dom'\n\nexport const HOVER_STYLE_ID = 'better-editor-hover-style'\nexport const TOOLBAR_ID = 'better-editor-block-toolbar'\n// Set on doc.body to disable hover affordances + click-to-focus while\n// the user wants to interact with the consumer page (forms, accordions,\n// links). All hover/active rules below are gated on its absence.\nexport const INTERACT_BODY_ATTR = 'data-bee-interact'\n\nconst VAR_TOP = '--bee-top'\nconst VAR_NESTED = '--bee-nested'\nconst VAR_OUTLINE_WIDTH = '--bee-outline-width'\n\nexport const HOVER_CSS = `\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] { cursor: pointer; }\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}]:hover,\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {\n outline: var(${VAR_OUTLINE_WIDTH}) solid var(${VAR_TOP});\n outline-offset: calc(-1 * var(${VAR_OUTLINE_WIDTH}) - 1px);\n background-color: color-mix(in srgb, var(${VAR_TOP}) 10%, transparent);\n }\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}]:hover,\n body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {\n outline-color: var(${VAR_NESTED});\n background-color: color-mix(in srgb, var(${VAR_NESTED}) 10%, transparent);\n }\n body[${INTERACT_BODY_ATTR}] #${TOOLBAR_ID} { display: none; }\n\n #${TOOLBAR_ID} {\n position: absolute;\n z-index: var(--better-editor-z-toolbar, 2147483647);\n display: none;\n gap: 2px;\n padding: 3px;\n border-radius: 4px;\n background: var(${VAR_TOP});\n color: #fff;\n box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18);\n font-family: system-ui, sans-serif;\n }\n #${TOOLBAR_ID}[data-nested=\"1\"] { background: var(${VAR_NESTED}); }\n #${TOOLBAR_ID}.is-visible { display: inline-flex; }\n #${TOOLBAR_ID} button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 26px;\n height: 26px;\n padding: 0;\n border: 0;\n border-radius: 3px;\n background: transparent;\n color: inherit;\n cursor: pointer;\n }\n #${TOOLBAR_ID} button:hover { background: rgba(255, 255, 255, 0.18); }\n #${TOOLBAR_ID} button[data-action=\"delete\"]:hover { background: rgba(0, 0, 0, 0.25); }\n`\n\nexport type HoverVars = {\n topColor: string\n nestedColor: string\n outlineWidth: number\n}\n\n// Restrictive on purpose: these values are written verbatim into a CSS\n// custom property on the preview iframe, so anything that survives the\n// regex still lands inside `outline:` / `background-color:`. The regex\n// rejects newlines and unmatched parens so an injected value can't escape\n// into a separate declaration.\nconst COLOR_RE = /^(?:#[0-9a-fA-F]{3,8}|rgba?\\([^()\\n\\r]*\\))$/i\n\nconst isValidColor = (v: unknown): v is string =>\n typeof v === 'string' && COLOR_RE.test(v.trim())\n\nconst isValidOutline = (v: unknown): v is number =>\n typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 50\n\nconst warnInvalid = (kind: string, value: unknown): void => {\n if (process.env.NODE_ENV !== 'production') {\n console.warn(`[better-editor] ignoring invalid ${kind}:`, value)\n }\n}\n\nexport const setHoverVars = (doc: Document, vars: HoverVars): void => {\n const root = doc.documentElement\n if (isValidColor(vars.topColor)) {\n root.style.setProperty(VAR_TOP, vars.topColor)\n } else {\n warnInvalid('topColor', vars.topColor)\n }\n if (isValidColor(vars.nestedColor)) {\n root.style.setProperty(VAR_NESTED, vars.nestedColor)\n } else {\n warnInvalid('nestedColor', vars.nestedColor)\n }\n if (isValidOutline(vars.outlineWidth)) {\n root.style.setProperty(VAR_OUTLINE_WIDTH, `${vars.outlineWidth}px`)\n } else {\n warnInvalid('outlineWidth', vars.outlineWidth)\n }\n}\n\nexport const clearHoverVars = (doc: Document): void => {\n const root = doc.documentElement\n root.style.removeProperty(VAR_TOP)\n root.style.removeProperty(VAR_NESTED)\n root.style.removeProperty(VAR_OUTLINE_WIDTH)\n}\n"],"names":["ACTIVE_CLASS","BLOCK_ID_ATTR","HOVER_STYLE_ID","TOOLBAR_ID","INTERACT_BODY_ATTR","VAR_TOP","VAR_NESTED","VAR_OUTLINE_WIDTH","HOVER_CSS","COLOR_RE","isValidColor","v","test","trim","isValidOutline","Number","isFinite","warnInvalid","kind","value","process","env","NODE_ENV","console","warn","setHoverVars","doc","vars","root","documentElement","topColor","style","setProperty","nestedColor","outlineWidth","clearHoverVars","removeProperty"],"mappings":"AAAA,SAASA,YAAY,EAAEC,aAAa,QAAQ,kBAAiB;AAE7D,OAAO,MAAMC,iBAAiB,4BAA2B;AACzD,OAAO,MAAMC,aAAa,8BAA6B;AACvD,sEAAsE;AACtE,wEAAwE;AACxE,iEAAiE;AACjE,OAAO,MAAMC,qBAAqB,oBAAmB;AAErD,MAAMC,UAAU;AAChB,MAAMC,aAAa;AACnB,MAAMC,oBAAoB;AAE1B,OAAO,MAAMC,YAAY,CAAC;YACd,EAAEJ,mBAAmB,IAAI,EAAEH,cAAc;YACzC,EAAEG,mBAAmB,IAAI,EAAEH,cAAc;YACzC,EAAEG,mBAAmB,IAAI,EAAEH,cAAc,EAAE,EAAED,aAAa;iBACrD,EAAEO,kBAAkB,YAAY,EAAEF,QAAQ;kCACzB,EAAEE,kBAAkB;6CACT,EAAEF,QAAQ;;YAE3C,EAAED,mBAAmB,IAAI,EAAEH,cAAc,GAAG,EAAEA,cAAc;YAC5D,EAAEG,mBAAmB,IAAI,EAAEH,cAAc,GAAG,EAAEA,cAAc,EAAE,EAAED,aAAa;uBAClE,EAAEM,WAAW;6CACS,EAAEA,WAAW;;OAEnD,EAAEF,mBAAmB,GAAG,EAAED,WAAW;;GAEzC,EAAEA,WAAW;;;;;;;oBAOI,EAAEE,QAAQ;;;;;GAK3B,EAAEF,WAAW,oCAAoC,EAAEG,WAAW;GAC9D,EAAEH,WAAW;GACb,EAAEA,WAAW;;;;;;;;;;;;;GAab,EAAEA,WAAW;GACb,EAAEA,WAAW;AAChB,CAAC,CAAA;AAQD,uEAAuE;AACvE,uEAAuE;AACvE,uEAAuE;AACvE,0EAA0E;AAC1E,+BAA+B;AAC/B,MAAMM,WAAW;AAEjB,MAAMC,eAAe,CAACC,IACpB,OAAOA,MAAM,YAAYF,SAASG,IAAI,CAACD,EAAEE,IAAI;AAE/C,MAAMC,iBAAiB,CAACH,IACtB,OAAOA,MAAM,YAAYI,OAAOC,QAAQ,CAACL,MAAMA,KAAK,KAAKA,KAAK;AAEhE,MAAMM,cAAc,CAACC,MAAcC;IACjC,IAAIC,QAAQC,GAAG,CAACC,QAAQ,KAAK,cAAc;QACzCC,QAAQC,IAAI,CAAC,CAAC,iCAAiC,EAAEN,KAAK,CAAC,CAAC,EAAEC;IAC5D;AACF;AAEA,OAAO,MAAMM,eAAe,CAACC,KAAeC;IAC1C,MAAMC,OAAOF,IAAIG,eAAe;IAChC,IAAInB,aAAaiB,KAAKG,QAAQ,GAAG;QAC/BF,KAAKG,KAAK,CAACC,WAAW,CAAC3B,SAASsB,KAAKG,QAAQ;IAC/C,OAAO;QACLb,YAAY,YAAYU,KAAKG,QAAQ;IACvC;IACA,IAAIpB,aAAaiB,KAAKM,WAAW,GAAG;QAClCL,KAAKG,KAAK,CAACC,WAAW,CAAC1B,YAAYqB,KAAKM,WAAW;IACrD,OAAO;QACLhB,YAAY,eAAeU,KAAKM,WAAW;IAC7C;IACA,IAAInB,eAAea,KAAKO,YAAY,GAAG;QACrCN,KAAKG,KAAK,CAACC,WAAW,CAACzB,mBAAmB,GAAGoB,KAAKO,YAAY,CAAC,EAAE,CAAC;IACpE,OAAO;QACLjB,YAAY,gBAAgBU,KAAKO,YAAY;IAC/C;AACF,EAAC;AAED,OAAO,MAAMC,iBAAiB,CAACT;IAC7B,MAAME,OAAOF,IAAIG,eAAe;IAChCD,KAAKG,KAAK,CAACK,cAAc,CAAC/B;IAC1BuB,KAAKG,KAAK,CAACK,cAAc,CAAC9B;IAC1BsB,KAAKG,KAAK,CAACK,cAAc,CAAC7B;AAC5B,EAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export type InstallClickToFocusOptions = {
|
|
2
|
+
/** Returning false from this gate lets the click propagate to the
|
|
3
|
+
* consumer page (interact mode) instead of selecting the block. */
|
|
4
|
+
isEnabled?: () => boolean;
|
|
5
|
+
};
|
|
6
|
+
export declare const installClickToFocus: (doc: Document, onFocus: (id: string) => void, options?: InstallClickToFocusOptions) => (() => void);
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom';
|
|
2
|
+
export const installClickToFocus = (doc, onFocus, options = {})=>{
|
|
3
|
+
const onClick = (e)=>{
|
|
4
|
+
if (options.isEnabled && !options.isEnabled()) return;
|
|
5
|
+
// Cross-realm-safe element check — `instanceof` is realm-local.
|
|
6
|
+
const target = e.target;
|
|
7
|
+
if (!target || typeof target.closest !== 'function') return;
|
|
8
|
+
const idEl = target.closest(BLOCK_ID_SELECTOR);
|
|
9
|
+
if (!idEl) return;
|
|
10
|
+
const id = idEl.getAttribute(BLOCK_ID_ATTR);
|
|
11
|
+
if (!id) return;
|
|
12
|
+
// Swallow so consumer-side links/buttons don't fire while the editor is open.
|
|
13
|
+
e.preventDefault();
|
|
14
|
+
e.stopPropagation();
|
|
15
|
+
onFocus(id);
|
|
16
|
+
};
|
|
17
|
+
doc.addEventListener('click', onClick, true);
|
|
18
|
+
return ()=>doc.removeEventListener('click', onClick, true);
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
//# sourceMappingURL=installClickToFocus.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/preview/installClickToFocus.ts"],"sourcesContent":["import { BLOCK_ID_ATTR, BLOCK_ID_SELECTOR } from '../internal/dom'\n\nexport type InstallClickToFocusOptions = {\n /** Returning false from this gate lets the click propagate to the\n * consumer page (interact mode) instead of selecting the block. */\n isEnabled?: () => boolean\n}\n\nexport const installClickToFocus = (\n doc: Document,\n onFocus: (id: string) => void,\n options: InstallClickToFocusOptions = {},\n): (() => void) => {\n const onClick = (e: MouseEvent) => {\n if (options.isEnabled && !options.isEnabled()) return\n // Cross-realm-safe element check — `instanceof` is realm-local.\n const target = e.target as Element | null\n if (!target || typeof target.closest !== 'function') return\n const idEl = target.closest<HTMLElement>(BLOCK_ID_SELECTOR)\n if (!idEl) return\n const id = idEl.getAttribute(BLOCK_ID_ATTR)\n if (!id) return\n // Swallow so consumer-side links/buttons don't fire while the editor is open.\n e.preventDefault()\n e.stopPropagation()\n onFocus(id)\n }\n doc.addEventListener('click', onClick, true)\n return () => doc.removeEventListener('click', onClick, true)\n}\n"],"names":["BLOCK_ID_ATTR","BLOCK_ID_SELECTOR","installClickToFocus","doc","onFocus","options","onClick","e","isEnabled","target","closest","idEl","id","getAttribute","preventDefault","stopPropagation","addEventListener","removeEventListener"],"mappings":"AAAA,SAASA,aAAa,EAAEC,iBAAiB,QAAQ,kBAAiB;AAQlE,OAAO,MAAMC,sBAAsB,CACjCC,KACAC,SACAC,UAAsC,CAAC,CAAC;IAExC,MAAMC,UAAU,CAACC;QACf,IAAIF,QAAQG,SAAS,IAAI,CAACH,QAAQG,SAAS,IAAI;QAC/C,gEAAgE;QAChE,MAAMC,SAASF,EAAEE,MAAM;QACvB,IAAI,CAACA,UAAU,OAAOA,OAAOC,OAAO,KAAK,YAAY;QACrD,MAAMC,OAAOF,OAAOC,OAAO,CAAcT;QACzC,IAAI,CAACU,MAAM;QACX,MAAMC,KAAKD,KAAKE,YAAY,CAACb;QAC7B,IAAI,CAACY,IAAI;QACT,8EAA8E;QAC9EL,EAAEO,cAAc;QAChBP,EAAEQ,eAAe;QACjBX,QAAQQ;IACV;IACAT,IAAIa,gBAAgB,CAAC,SAASV,SAAS;IACvC,OAAO,IAAMH,IAAIc,mBAAmB,CAAC,SAASX,SAAS;AACzD,EAAC"}
|