payload-better-editor 1.0.4 → 1.2.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/dist/admin/ErrorBoundary.d.ts +1 -1
- package/dist/admin/LiveEditorOverlay.js +6 -7
- package/dist/admin/LiveEditorOverlay.js.map +1 -1
- package/dist/admin/PreviewFrame.d.ts +3 -3
- package/dist/admin/PreviewFrame.js +4 -6
- package/dist/admin/PreviewFrame.js.map +1 -1
- package/dist/admin/PreviewToolbar.d.ts +3 -3
- package/dist/admin/PreviewToolbar.js +7 -13
- package/dist/admin/PreviewToolbar.js.map +1 -1
- package/dist/admin/ViewportToggle.js.map +1 -1
- package/dist/admin/WidthChip.d.ts +4 -0
- package/dist/admin/WidthChip.js +30 -0
- package/dist/admin/WidthChip.js.map +1 -0
- package/dist/admin/blocks/schema.d.ts +8 -0
- package/dist/admin/blocks/schema.js +13 -2
- package/dist/admin/blocks/schema.js.map +1 -1
- package/dist/admin/icons.d.ts +24 -23
- package/dist/admin/icons.js +488 -30
- package/dist/admin/icons.js.map +1 -1
- package/dist/admin/sidebar/BlockSettingsTab.js +7 -4
- package/dist/admin/sidebar/BlockSettingsTab.js.map +1 -1
- package/dist/admin/sidebar/Sidebar.d.ts +1 -1
- package/dist/admin/sidebar/Sidebar.js +8 -2
- package/dist/admin/sidebar/Sidebar.js.map +1 -1
- package/dist/admin/sidebar/ValidationSummary.d.ts +6 -0
- package/dist/admin/sidebar/ValidationSummary.js +48 -0
- package/dist/admin/sidebar/ValidationSummary.js.map +1 -0
- package/dist/admin/sidebar/validation.d.ts +10 -0
- package/dist/admin/sidebar/validation.js +28 -0
- package/dist/admin/sidebar/validation.js.map +1 -0
- package/dist/hooks/usePreviewHandleDrag.js +9 -20
- package/dist/hooks/usePreviewHandleDrag.js.map +1 -1
- package/dist/hooks/useSidebarResize.js +8 -16
- package/dist/hooks/useSidebarResize.js.map +1 -1
- package/dist/index.js +18 -15
- package/dist/index.js.map +1 -1
- package/dist/internal/drag.d.ts +12 -0
- package/dist/internal/drag.js +46 -0
- package/dist/internal/drag.js.map +1 -0
- package/dist/internal/entities.d.ts +6 -0
- package/dist/internal/entities.js +15 -0
- package/dist/internal/entities.js.map +1 -0
- package/dist/preview/hover-css.d.ts +1 -1
- package/dist/preview/hover-css.js +2 -2
- package/dist/preview/hover-css.js.map +1 -1
- package/dist/styles/sidebar.css +53 -0
- package/dist/types.d.ts +19 -11
- package/dist/types.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +3 -4
- package/dist/version.js.map +1 -1
- package/package.json +4 -3
- package/dist/hooks/useIframeResizeObserver.d.ts +0 -2
- package/dist/hooks/useIframeResizeObserver.js +0 -20
- package/dist/hooks/useIframeResizeObserver.js.map +0 -1
|
@@ -4,6 +4,7 @@ import React, { useEffect, useState } from 'react';
|
|
|
4
4
|
import { DocumentSettingsTab } from './DocumentSettingsTab';
|
|
5
5
|
import { DocumentMetaTab } from './DocumentMetaTab';
|
|
6
6
|
import { BlockSettingsTab } from './BlockSettingsTab';
|
|
7
|
+
import { ValidationSummary } from './ValidationSummary';
|
|
7
8
|
const TABS = [
|
|
8
9
|
{
|
|
9
10
|
key: 'page',
|
|
@@ -33,7 +34,8 @@ const SidebarTab = ({ tabKey, label, active, onSelect })=>/*#__PURE__*/ _jsx("bu
|
|
|
33
34
|
onClick: ()=>onSelect(tabKey),
|
|
34
35
|
children: label
|
|
35
36
|
});
|
|
36
|
-
|
|
37
|
+
// Memoized so resize-drag re-renders of the overlay skip the field tree.
|
|
38
|
+
export const Sidebar = /*#__PURE__*/ React.memo(function Sidebar({ selectedBlockPath, onClearSelection, onSelectPath, forceFullWidthFields, blocksField, addBelowRequestId = 0 }) {
|
|
37
39
|
const [tab, setTab] = useState('page');
|
|
38
40
|
// Auto-jump to the block tab when the iframe selects a new block.
|
|
39
41
|
useEffect(()=>{
|
|
@@ -55,6 +57,10 @@ export const Sidebar = ({ selectedBlockPath, onClearSelection, onSelectPath, for
|
|
|
55
57
|
onSelect: setTab
|
|
56
58
|
}, t.key))
|
|
57
59
|
}),
|
|
60
|
+
/*#__PURE__*/ _jsx(ValidationSummary, {
|
|
61
|
+
blocksField: blocksField,
|
|
62
|
+
onSelectPath: onSelectPath
|
|
63
|
+
}),
|
|
58
64
|
/*#__PURE__*/ _jsxs("div", {
|
|
59
65
|
className: `${ROOT_CLASS}__content`,
|
|
60
66
|
role: "tabpanel",
|
|
@@ -78,7 +84,7 @@ export const Sidebar = ({ selectedBlockPath, onClearSelection, onSelectPath, for
|
|
|
78
84
|
})
|
|
79
85
|
]
|
|
80
86
|
});
|
|
81
|
-
};
|
|
87
|
+
});
|
|
82
88
|
// Off-screen live region announces block-selection changes to screen
|
|
83
89
|
// readers; visible UI updates handle sighted users via the tab switch.
|
|
84
90
|
const SelectionAnnouncer = ({ selectedBlockPath })=>/*#__PURE__*/ _jsx("div", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/admin/sidebar/Sidebar.tsx"],"sourcesContent":["'use client'\n\nimport React, { useEffect, useState } from 'react'\nimport { DocumentSettingsTab } from './DocumentSettingsTab'\nimport { DocumentMetaTab } from './DocumentMetaTab'\nimport { BlockSettingsTab } from './BlockSettingsTab'\n\nexport type SidebarProps = {\n selectedBlockPath: string | null\n onClearSelection: () => void\n onSelectPath: (path: string | null) => void\n forceFullWidthFields: boolean\n blocksField: string\n addBelowRequestId?: number\n}\n\ntype TabKey = 'page' | 'block' | 'settings'\n\nconst TABS: ReadonlyArray<{ key: TabKey; label: string }> = [\n { key: 'page', label: 'Page' },\n { key: 'block', label: 'Blocks' },\n { key: 'settings', label: 'Settings' },\n]\n\nconst ROOT_CLASS = 'better-editor-sidebar'\nconst TAB_CLASS = `${ROOT_CLASS}__tab`\n\nconst tabId = (key: TabKey) => `better-editor-tab-${key}`\nconst panelId = (key: TabKey) => `better-editor-panel-${key}`\n\ntype SidebarTabProps = {\n tabKey: TabKey\n label: string\n active: boolean\n onSelect: (key: TabKey) => void\n}\n\nconst SidebarTab: React.FC<SidebarTabProps> = ({ tabKey, label, active, onSelect }) => (\n <button\n type=\"button\"\n role=\"tab\"\n id={tabId(tabKey)}\n aria-selected={active}\n aria-controls={panelId(tabKey)}\n tabIndex={active ? 0 : -1}\n className={active ? `${TAB_CLASS} ${TAB_CLASS}--active` : TAB_CLASS}\n onClick={() => onSelect(tabKey)}\n >\n {label}\n </button>\n)\n\nexport const Sidebar
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/sidebar/Sidebar.tsx"],"sourcesContent":["'use client'\n\nimport React, { useEffect, useState } from 'react'\nimport { DocumentSettingsTab } from './DocumentSettingsTab'\nimport { DocumentMetaTab } from './DocumentMetaTab'\nimport { BlockSettingsTab } from './BlockSettingsTab'\nimport { ValidationSummary } from './ValidationSummary'\n\nexport type SidebarProps = {\n selectedBlockPath: string | null\n onClearSelection: () => void\n onSelectPath: (path: string | null) => void\n forceFullWidthFields: boolean\n blocksField: string\n addBelowRequestId?: number\n}\n\ntype TabKey = 'page' | 'block' | 'settings'\n\nconst TABS: ReadonlyArray<{ key: TabKey; label: string }> = [\n { key: 'page', label: 'Page' },\n { key: 'block', label: 'Blocks' },\n { key: 'settings', label: 'Settings' },\n]\n\nconst ROOT_CLASS = 'better-editor-sidebar'\nconst TAB_CLASS = `${ROOT_CLASS}__tab`\n\nconst tabId = (key: TabKey) => `better-editor-tab-${key}`\nconst panelId = (key: TabKey) => `better-editor-panel-${key}`\n\ntype SidebarTabProps = {\n tabKey: TabKey\n label: string\n active: boolean\n onSelect: (key: TabKey) => void\n}\n\nconst SidebarTab: React.FC<SidebarTabProps> = ({ tabKey, label, active, onSelect }) => (\n <button\n type=\"button\"\n role=\"tab\"\n id={tabId(tabKey)}\n aria-selected={active}\n aria-controls={panelId(tabKey)}\n tabIndex={active ? 0 : -1}\n className={active ? `${TAB_CLASS} ${TAB_CLASS}--active` : TAB_CLASS}\n onClick={() => onSelect(tabKey)}\n >\n {label}\n </button>\n)\n\n// Memoized so resize-drag re-renders of the overlay skip the field tree.\nexport const Sidebar = React.memo(function Sidebar({\n selectedBlockPath,\n onClearSelection,\n onSelectPath,\n forceFullWidthFields,\n blocksField,\n addBelowRequestId = 0,\n}: SidebarProps) {\n const [tab, setTab] = useState<TabKey>('page')\n\n // Auto-jump to the block tab when the iframe selects a new block.\n useEffect(() => {\n if (selectedBlockPath) setTab('block')\n }, [selectedBlockPath])\n\n const className = forceFullWidthFields\n ? `${ROOT_CLASS} ${ROOT_CLASS}--force-full-width`\n : ROOT_CLASS\n\n return (\n <div className={className}>\n <div role=\"tablist\" className={`${ROOT_CLASS}__tabs`}>\n {TABS.map((t) => (\n <SidebarTab\n key={t.key}\n tabKey={t.key}\n label={t.label}\n active={tab === t.key}\n onSelect={setTab}\n />\n ))}\n </div>\n\n <ValidationSummary blocksField={blocksField} onSelectPath={onSelectPath} />\n\n <div\n className={`${ROOT_CLASS}__content`}\n role=\"tabpanel\"\n id={panelId(tab)}\n aria-labelledby={tabId(tab)}\n tabIndex={0}\n >\n {tab === 'page' && <DocumentSettingsTab />}\n {tab === 'block' && (\n <BlockSettingsTab\n selectedBlockPath={selectedBlockPath}\n onClearSelection={onClearSelection}\n onSelectPath={onSelectPath}\n blocksField={blocksField}\n addBelowRequestId={addBelowRequestId}\n />\n )}\n {tab === 'settings' && <DocumentMetaTab />}\n </div>\n\n <SelectionAnnouncer selectedBlockPath={selectedBlockPath} />\n </div>\n )\n})\n\n// Off-screen live region announces block-selection changes to screen\n// readers; visible UI updates handle sighted users via the tab switch.\nconst SelectionAnnouncer: React.FC<{ selectedBlockPath: string | null }> = ({\n selectedBlockPath,\n}) => (\n <div role=\"status\" aria-live=\"polite\" aria-atomic=\"true\" className=\"better-editor-sr-only\">\n {selectedBlockPath ? `Block selected: ${selectedBlockPath}` : 'No block selected'}\n </div>\n)\n"],"names":["React","useEffect","useState","DocumentSettingsTab","DocumentMetaTab","BlockSettingsTab","ValidationSummary","TABS","key","label","ROOT_CLASS","TAB_CLASS","tabId","panelId","SidebarTab","tabKey","active","onSelect","button","type","role","id","aria-selected","aria-controls","tabIndex","className","onClick","Sidebar","memo","selectedBlockPath","onClearSelection","onSelectPath","forceFullWidthFields","blocksField","addBelowRequestId","tab","setTab","div","map","t","aria-labelledby","SelectionAnnouncer","aria-live","aria-atomic"],"mappings":"AAAA;;AAEA,OAAOA,SAASC,SAAS,EAAEC,QAAQ,QAAQ,QAAO;AAClD,SAASC,mBAAmB,QAAQ,wBAAuB;AAC3D,SAASC,eAAe,QAAQ,oBAAmB;AACnD,SAASC,gBAAgB,QAAQ,qBAAoB;AACrD,SAASC,iBAAiB,QAAQ,sBAAqB;AAavD,MAAMC,OAAsD;IAC1D;QAAEC,KAAK;QAAQC,OAAO;IAAO;IAC7B;QAAED,KAAK;QAASC,OAAO;IAAS;IAChC;QAAED,KAAK;QAAYC,OAAO;IAAW;CACtC;AAED,MAAMC,aAAa;AACnB,MAAMC,YAAY,GAAGD,WAAW,KAAK,CAAC;AAEtC,MAAME,QAAQ,CAACJ,MAAgB,CAAC,kBAAkB,EAAEA,KAAK;AACzD,MAAMK,UAAU,CAACL,MAAgB,CAAC,oBAAoB,EAAEA,KAAK;AAS7D,MAAMM,aAAwC,CAAC,EAAEC,MAAM,EAAEN,KAAK,EAAEO,MAAM,EAAEC,QAAQ,EAAE,iBAChF,KAACC;QACCC,MAAK;QACLC,MAAK;QACLC,IAAIT,MAAMG;QACVO,iBAAeN;QACfO,iBAAeV,QAAQE;QACvBS,UAAUR,SAAS,IAAI,CAAC;QACxBS,WAAWT,SAAS,GAAGL,UAAU,CAAC,EAAEA,UAAU,QAAQ,CAAC,GAAGA;QAC1De,SAAS,IAAMT,SAASF;kBAEvBN;;AAIL,yEAAyE;AACzE,OAAO,MAAMkB,wBAAU3B,MAAM4B,IAAI,CAAC,SAASD,QAAQ,EACjDE,iBAAiB,EACjBC,gBAAgB,EAChBC,YAAY,EACZC,oBAAoB,EACpBC,WAAW,EACXC,oBAAoB,CAAC,EACR;IACb,MAAM,CAACC,KAAKC,OAAO,GAAGlC,SAAiB;IAEvC,kEAAkE;IAClED,UAAU;QACR,IAAI4B,mBAAmBO,OAAO;IAChC,GAAG;QAACP;KAAkB;IAEtB,MAAMJ,YAAYO,uBACd,GAAGtB,WAAW,CAAC,EAAEA,WAAW,kBAAkB,CAAC,GAC/CA;IAEJ,qBACE,MAAC2B;QAAIZ,WAAWA;;0BACd,KAACY;gBAAIjB,MAAK;gBAAUK,WAAW,GAAGf,WAAW,MAAM,CAAC;0BACjDH,KAAK+B,GAAG,CAAC,CAACC,kBACT,KAACzB;wBAECC,QAAQwB,EAAE/B,GAAG;wBACbC,OAAO8B,EAAE9B,KAAK;wBACdO,QAAQmB,QAAQI,EAAE/B,GAAG;wBACrBS,UAAUmB;uBAJLG,EAAE/B,GAAG;;0BAShB,KAACF;gBAAkB2B,aAAaA;gBAAaF,cAAcA;;0BAE3D,MAACM;gBACCZ,WAAW,GAAGf,WAAW,SAAS,CAAC;gBACnCU,MAAK;gBACLC,IAAIR,QAAQsB;gBACZK,mBAAiB5B,MAAMuB;gBACvBX,UAAU;;oBAETW,QAAQ,wBAAU,KAAChC;oBACnBgC,QAAQ,yBACP,KAAC9B;wBACCwB,mBAAmBA;wBACnBC,kBAAkBA;wBAClBC,cAAcA;wBACdE,aAAaA;wBACbC,mBAAmBA;;oBAGtBC,QAAQ,4BAAc,KAAC/B;;;0BAG1B,KAACqC;gBAAmBZ,mBAAmBA;;;;AAG7C,GAAE;AAEF,qEAAqE;AACrE,uEAAuE;AACvE,MAAMY,qBAAqE,CAAC,EAC1EZ,iBAAiB,EAClB,iBACC,KAACQ;QAAIjB,MAAK;QAASsB,aAAU;QAASC,eAAY;QAAOlB,WAAU;kBAChEI,oBAAoB,CAAC,gBAAgB,EAAEA,mBAAmB,GAAG"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
3
|
+
import React, { useMemo } from 'react';
|
|
4
|
+
import { useAllFormFields } from '@payloadcms/ui';
|
|
5
|
+
import { collectFieldErrors } from './validation';
|
|
6
|
+
// Sidebar banner listing invalid fields across all blocks.
|
|
7
|
+
export const ValidationSummary = ({ blocksField, onSelectPath })=>{
|
|
8
|
+
const [fields] = useAllFormFields();
|
|
9
|
+
const errors = useMemo(()=>collectFieldErrors(fields, blocksField), [
|
|
10
|
+
fields,
|
|
11
|
+
blocksField
|
|
12
|
+
]);
|
|
13
|
+
if (errors.length === 0) return null;
|
|
14
|
+
return /*#__PURE__*/ _jsxs("div", {
|
|
15
|
+
className: "better-editor-sidebar__errors",
|
|
16
|
+
role: "region",
|
|
17
|
+
"aria-label": "Validation errors",
|
|
18
|
+
children: [
|
|
19
|
+
/*#__PURE__*/ _jsx("p", {
|
|
20
|
+
className: "better-editor-sidebar__errors-title",
|
|
21
|
+
children: errors.length === 1 ? '1 field needs attention' : `${errors.length} fields need attention`
|
|
22
|
+
}),
|
|
23
|
+
/*#__PURE__*/ _jsx("ul", {
|
|
24
|
+
className: "better-editor-sidebar__errors-list",
|
|
25
|
+
children: errors.map((error)=>/*#__PURE__*/ _jsxs("li", {
|
|
26
|
+
className: "better-editor-sidebar__errors-item",
|
|
27
|
+
children: [
|
|
28
|
+
error.blockPath ? /*#__PURE__*/ _jsx("button", {
|
|
29
|
+
type: "button",
|
|
30
|
+
className: "better-editor-sidebar__errors-jump",
|
|
31
|
+
onClick: ()=>onSelectPath(error.blockPath),
|
|
32
|
+
children: error.label
|
|
33
|
+
}) : /*#__PURE__*/ _jsx("span", {
|
|
34
|
+
className: "better-editor-sidebar__errors-label",
|
|
35
|
+
children: error.label
|
|
36
|
+
}),
|
|
37
|
+
/*#__PURE__*/ _jsx("span", {
|
|
38
|
+
className: "better-editor-sidebar__errors-message",
|
|
39
|
+
children: error.message
|
|
40
|
+
})
|
|
41
|
+
]
|
|
42
|
+
}, error.path))
|
|
43
|
+
})
|
|
44
|
+
]
|
|
45
|
+
});
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//# sourceMappingURL=ValidationSummary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/sidebar/ValidationSummary.tsx"],"sourcesContent":["'use client'\n\nimport React, { useMemo } from 'react'\nimport { useAllFormFields } from '@payloadcms/ui'\nimport type { FormState } from 'payload'\nimport { collectFieldErrors } from './validation'\n\nexport type ValidationSummaryProps = {\n blocksField: string\n onSelectPath: (path: string | null) => void\n}\n\n// Sidebar banner listing invalid fields across all blocks.\nexport const ValidationSummary: React.FC<ValidationSummaryProps> = ({\n blocksField,\n onSelectPath,\n}) => {\n const [fields] = useAllFormFields()\n const errors = useMemo(\n () => collectFieldErrors(fields as FormState, blocksField),\n [fields, blocksField],\n )\n\n if (errors.length === 0) return null\n\n return (\n <div className=\"better-editor-sidebar__errors\" role=\"region\" aria-label=\"Validation errors\">\n <p className=\"better-editor-sidebar__errors-title\">\n {errors.length === 1 ? '1 field needs attention' : `${errors.length} fields need attention`}\n </p>\n <ul className=\"better-editor-sidebar__errors-list\">\n {errors.map((error) => (\n <li key={error.path} className=\"better-editor-sidebar__errors-item\">\n {error.blockPath ? (\n <button\n type=\"button\"\n className=\"better-editor-sidebar__errors-jump\"\n onClick={() => onSelectPath(error.blockPath)}\n >\n {error.label}\n </button>\n ) : (\n <span className=\"better-editor-sidebar__errors-label\">{error.label}</span>\n )}\n <span className=\"better-editor-sidebar__errors-message\">{error.message}</span>\n </li>\n ))}\n </ul>\n </div>\n )\n}\n"],"names":["React","useMemo","useAllFormFields","collectFieldErrors","ValidationSummary","blocksField","onSelectPath","fields","errors","length","div","className","role","aria-label","p","ul","map","error","li","blockPath","button","type","onClick","label","span","message","path"],"mappings":"AAAA;;AAEA,OAAOA,SAASC,OAAO,QAAQ,QAAO;AACtC,SAASC,gBAAgB,QAAQ,iBAAgB;AAEjD,SAASC,kBAAkB,QAAQ,eAAc;AAOjD,2DAA2D;AAC3D,OAAO,MAAMC,oBAAsD,CAAC,EAClEC,WAAW,EACXC,YAAY,EACb;IACC,MAAM,CAACC,OAAO,GAAGL;IACjB,MAAMM,SAASP,QACb,IAAME,mBAAmBI,QAAqBF,cAC9C;QAACE;QAAQF;KAAY;IAGvB,IAAIG,OAAOC,MAAM,KAAK,GAAG,OAAO;IAEhC,qBACE,MAACC;QAAIC,WAAU;QAAgCC,MAAK;QAASC,cAAW;;0BACtE,KAACC;gBAAEH,WAAU;0BACVH,OAAOC,MAAM,KAAK,IAAI,4BAA4B,GAAGD,OAAOC,MAAM,CAAC,sBAAsB,CAAC;;0BAE7F,KAACM;gBAAGJ,WAAU;0BACXH,OAAOQ,GAAG,CAAC,CAACC,sBACX,MAACC;wBAAoBP,WAAU;;4BAC5BM,MAAME,SAAS,iBACd,KAACC;gCACCC,MAAK;gCACLV,WAAU;gCACVW,SAAS,IAAMhB,aAAaW,MAAME,SAAS;0CAE1CF,MAAMM,KAAK;+CAGd,KAACC;gCAAKb,WAAU;0CAAuCM,MAAMM,KAAK;;0CAEpE,KAACC;gCAAKb,WAAU;0CAAyCM,MAAMQ,OAAO;;;uBAZ/DR,MAAMS,IAAI;;;;AAkB7B,EAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { FormState } from 'payload';
|
|
2
|
+
export type FieldError = {
|
|
3
|
+
path: string;
|
|
4
|
+
label: string;
|
|
5
|
+
message: string;
|
|
6
|
+
/** Top-level block path to select on click, or null for non-block fields. */
|
|
7
|
+
blockPath: string | null;
|
|
8
|
+
};
|
|
9
|
+
export declare const toBlockPath: (fieldPath: string, blocksField: string) => string | null;
|
|
10
|
+
export declare const collectFieldErrors: (fields: FormState, blocksField: string) => FieldError[];
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const lastSegment = (path)=>{
|
|
2
|
+
const parts = path.split('.');
|
|
3
|
+
return parts[parts.length - 1] || path;
|
|
4
|
+
};
|
|
5
|
+
// Top-level block (`layout.2`) a field path belongs to, or null if outside the blocks field.
|
|
6
|
+
export const toBlockPath = (fieldPath, blocksField)=>{
|
|
7
|
+
if (!fieldPath.startsWith(`${blocksField}.`)) return null;
|
|
8
|
+
const index = fieldPath.slice(blocksField.length + 1).split('.')[0];
|
|
9
|
+
return /^\d+$/.test(index) ? `${blocksField}.${index}` : null;
|
|
10
|
+
};
|
|
11
|
+
// Require a message: leaf errors carry one, containers/groups don't — keeps
|
|
12
|
+
// spurious parent rows out.
|
|
13
|
+
export const collectFieldErrors = (fields, blocksField)=>{
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const [path, field] of Object.entries(fields)){
|
|
16
|
+
if (field.valid === false && typeof field.errorMessage === 'string') {
|
|
17
|
+
out.push({
|
|
18
|
+
path,
|
|
19
|
+
label: lastSegment(path),
|
|
20
|
+
message: field.errorMessage,
|
|
21
|
+
blockPath: toBlockPath(path, blocksField)
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../src/admin/sidebar/validation.ts"],"sourcesContent":["import type { FormState } from 'payload'\n\nexport type FieldError = {\n path: string\n label: string\n message: string\n /** Top-level block path to select on click, or null for non-block fields. */\n blockPath: string | null\n}\n\nconst lastSegment = (path: string): string => {\n const parts = path.split('.')\n return parts[parts.length - 1] || path\n}\n\n// Top-level block (`layout.2`) a field path belongs to, or null if outside the blocks field.\nexport const toBlockPath = (fieldPath: string, blocksField: string): string | null => {\n if (!fieldPath.startsWith(`${blocksField}.`)) return null\n const index = fieldPath.slice(blocksField.length + 1).split('.')[0]\n return /^\\d+$/.test(index) ? `${blocksField}.${index}` : null\n}\n\n// Require a message: leaf errors carry one, containers/groups don't — keeps\n// spurious parent rows out.\nexport const collectFieldErrors = (fields: FormState, blocksField: string): FieldError[] => {\n const out: FieldError[] = []\n for (const [path, field] of Object.entries(fields)) {\n if (field.valid === false && typeof field.errorMessage === 'string') {\n out.push({\n path,\n label: lastSegment(path),\n message: field.errorMessage,\n blockPath: toBlockPath(path, blocksField),\n })\n }\n }\n return out\n}\n"],"names":["lastSegment","path","parts","split","length","toBlockPath","fieldPath","blocksField","startsWith","index","slice","test","collectFieldErrors","fields","out","field","Object","entries","valid","errorMessage","push","label","message","blockPath"],"mappings":"AAUA,MAAMA,cAAc,CAACC;IACnB,MAAMC,QAAQD,KAAKE,KAAK,CAAC;IACzB,OAAOD,KAAK,CAACA,MAAME,MAAM,GAAG,EAAE,IAAIH;AACpC;AAEA,6FAA6F;AAC7F,OAAO,MAAMI,cAAc,CAACC,WAAmBC;IAC7C,IAAI,CAACD,UAAUE,UAAU,CAAC,GAAGD,YAAY,CAAC,CAAC,GAAG,OAAO;IACrD,MAAME,QAAQH,UAAUI,KAAK,CAACH,YAAYH,MAAM,GAAG,GAAGD,KAAK,CAAC,IAAI,CAAC,EAAE;IACnE,OAAO,QAAQQ,IAAI,CAACF,SAAS,GAAGF,YAAY,CAAC,EAAEE,OAAO,GAAG;AAC3D,EAAC;AAED,4EAA4E;AAC5E,4BAA4B;AAC5B,OAAO,MAAMG,qBAAqB,CAACC,QAAmBN;IACpD,MAAMO,MAAoB,EAAE;IAC5B,KAAK,MAAM,CAACb,MAAMc,MAAM,IAAIC,OAAOC,OAAO,CAACJ,QAAS;QAClD,IAAIE,MAAMG,KAAK,KAAK,SAAS,OAAOH,MAAMI,YAAY,KAAK,UAAU;YACnEL,IAAIM,IAAI,CAAC;gBACPnB;gBACAoB,OAAOrB,YAAYC;gBACnBqB,SAASP,MAAMI,YAAY;gBAC3BI,WAAWlB,YAAYJ,MAAMM;YAC/B;QACF;IACF;IACA,OAAOO;AACT,EAAC"}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
'use client';
|
|
2
2
|
import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
3
3
|
import { clampViewport } from '../internal/limits';
|
|
4
|
+
import { startHorizontalDrag } from '../internal/drag';
|
|
4
5
|
export const usePreviewHandleDrag = ({ resizable, viewportWidth, onResize })=>{
|
|
5
6
|
const [isResizing, setIsResizing] = useState(false);
|
|
6
|
-
// Track in-flight drag so unmount mid-drag can release body styles + listeners.
|
|
7
7
|
const dragCleanupRef = useRef(null);
|
|
8
8
|
const isMountedRef = useRef(true);
|
|
9
9
|
useEffect(()=>{
|
|
@@ -18,27 +18,16 @@ export const usePreviewHandleDrag = ({ resizable, viewportWidth, onResize })=>{
|
|
|
18
18
|
e.preventDefault();
|
|
19
19
|
const startX = e.clientX;
|
|
20
20
|
const startWidth = viewportWidth;
|
|
21
|
-
//
|
|
22
|
-
// the width by 2N. Right handle: positive delta increases width.
|
|
21
|
+
// Centered iframe grows by 2px per dragged edge px.
|
|
23
22
|
const dir = side === 'right' ? 2 : -2;
|
|
24
23
|
setIsResizing(true);
|
|
25
|
-
|
|
26
|
-
onResize(clampViewport(startWidth + (
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
document.body.style.userSelect = '';
|
|
33
|
-
if (isMountedRef.current) setIsResizing(false);
|
|
34
|
-
dragCleanupRef.current = null;
|
|
35
|
-
};
|
|
36
|
-
const onUp = ()=>cleanup();
|
|
37
|
-
dragCleanupRef.current = cleanup;
|
|
38
|
-
document.body.style.cursor = 'ew-resize';
|
|
39
|
-
document.body.style.userSelect = 'none';
|
|
40
|
-
window.addEventListener('mousemove', onMove);
|
|
41
|
-
window.addEventListener('mouseup', onUp);
|
|
24
|
+
dragCleanupRef.current = startHorizontalDrag('ew-resize', {
|
|
25
|
+
onUpdate: (clientX)=>onResize(clampViewport(startWidth + (clientX - startX) * dir)),
|
|
26
|
+
onEnd: ()=>{
|
|
27
|
+
if (isMountedRef.current) setIsResizing(false);
|
|
28
|
+
dragCleanupRef.current = null;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
42
31
|
}, [
|
|
43
32
|
resizable,
|
|
44
33
|
onResize,
|
|
@@ -1 +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
|
|
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'\nimport { startHorizontalDrag } from '../internal/drag'\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 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 // Centered iframe grows by 2px per dragged edge px.\n const dir = side === 'right' ? 2 : -2\n setIsResizing(true)\n dragCleanupRef.current = startHorizontalDrag('ew-resize', {\n onUpdate: (clientX) => onResize(clampViewport(startWidth + (clientX - startX) * dir)),\n onEnd: () => {\n if (isMountedRef.current) setIsResizing(false)\n dragCleanupRef.current = null\n },\n })\n },\n [resizable, onResize, viewportWidth],\n )\n\n return { isResizing, onHandleMouseDown }\n}\n"],"names":["React","useCallback","useEffect","useRef","useState","clampViewport","startHorizontalDrag","usePreviewHandleDrag","resizable","viewportWidth","onResize","isResizing","setIsResizing","dragCleanupRef","isMountedRef","current","onHandleMouseDown","side","e","preventDefault","startX","clientX","startWidth","dir","onUpdate","onEnd"],"mappings":"AAAA;AAEA,OAAOA,SAASC,WAAW,EAAEC,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAO;AACvE,SAASC,aAAa,QAAQ,qBAAoB;AAClD,SAASC,mBAAmB,QAAQ,mBAAkB;AAatD,OAAO,MAAMC,uBAAuB,CAAC,EACnCC,SAAS,EACTC,aAAa,EACbC,QAAQ,EACoB;IAC5B,MAAM,CAACC,YAAYC,cAAc,GAAGR,SAAS;IAE7C,MAAMS,iBAAiBV,OAA4B;IACnD,MAAMW,eAAeX,OAAO;IAC5BD,UAAU;QACRY,aAAaC,OAAO,GAAG;QACvB,OAAO;YACLD,aAAaC,OAAO,GAAG;YACvBF,eAAeE,OAAO;QACxB;IACF,GAAG,EAAE;IAEL,MAAMC,oBAAoBf,YACxB,CAACgB,OAA2B,CAACC;YAC3B,IAAI,CAACV,aAAa,CAACE,YAAY,CAACD,eAAe;YAC/CS,EAAEC,cAAc;YAChB,MAAMC,SAASF,EAAEG,OAAO;YACxB,MAAMC,aAAab;YACnB,oDAAoD;YACpD,MAAMc,MAAMN,SAAS,UAAU,IAAI,CAAC;YACpCL,cAAc;YACdC,eAAeE,OAAO,GAAGT,oBAAoB,aAAa;gBACxDkB,UAAU,CAACH,UAAYX,SAASL,cAAciB,aAAa,AAACD,CAAAA,UAAUD,MAAK,IAAKG;gBAChFE,OAAO;oBACL,IAAIX,aAAaC,OAAO,EAAEH,cAAc;oBACxCC,eAAeE,OAAO,GAAG;gBAC3B;YACF;QACF,GACA;QAACP;QAAWE;QAAUD;KAAc;IAGtC,OAAO;QAAEE;QAAYK;IAAkB;AACzC,EAAC"}
|
|
@@ -3,6 +3,7 @@ import React, { useCallback, useEffect, useRef, useState } from 'react';
|
|
|
3
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
4
|
import { useBetterEditorConfig } from '../providers/BetterEditorConfigProvider';
|
|
5
5
|
import { readNumber, writeString } from '../internal/storage';
|
|
6
|
+
import { startHorizontalDrag } from '../internal/drag';
|
|
6
7
|
const clampSidebar = (n)=>Math.min(MAX_SIDEBAR_WIDTH, Math.max(MIN_SIDEBAR_WIDTH, n));
|
|
7
8
|
export const useSidebarResize = (sidebarPosition)=>{
|
|
8
9
|
const { storageKeys } = useBetterEditorConfig();
|
|
@@ -50,22 +51,13 @@ export const useSidebarResize = (sidebarPosition)=>{
|
|
|
50
51
|
const startWidth = widthRef.current;
|
|
51
52
|
const direction = positionRef.current === 'right' ? -1 : 1;
|
|
52
53
|
setIsResizing(true);
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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);
|
|
54
|
+
dragCleanupRef.current = startHorizontalDrag('col-resize', {
|
|
55
|
+
onUpdate: (clientX)=>setSidebarWidth(clampSidebar(startWidth + (clientX - startX) * direction)),
|
|
56
|
+
onEnd: ()=>{
|
|
57
|
+
setIsResizing(false);
|
|
58
|
+
dragCleanupRef.current = null;
|
|
59
|
+
}
|
|
60
|
+
});
|
|
69
61
|
}, []);
|
|
70
62
|
// Arrow keys nudge the handle for keyboard users (WCAG 2.1 separator
|
|
71
63
|
// semantics). Direction matches the mouse drag — Right when the sidebar
|
|
@@ -1 +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
|
|
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'\nimport { startHorizontalDrag } from '../internal/drag'\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 setIsResizing(true)\n dragCleanupRef.current = startHorizontalDrag('col-resize', {\n onUpdate: (clientX) => setSidebarWidth(clampSidebar(startWidth + (clientX - startX) * direction)),\n onEnd: () => {\n setIsResizing(false)\n dragCleanupRef.current = null\n },\n })\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","startHorizontalDrag","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","onUpdate","onEnd","onResizeKeyDown","delta","key","w"],"mappings":"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"}
|
package/dist/index.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { BETTER_EDITOR_SETTINGS_BANNER_FIELD, betterEditorSettingsGlobal } from './global';
|
|
2
|
+
import { normalizeEntities } from './internal/entities';
|
|
2
3
|
export { BETTER_EDITOR_SETTINGS_SLUG } from './global';
|
|
3
4
|
export { VERSION } from './version';
|
|
4
5
|
const DEFAULT_BLOCKS_FIELD = 'layout';
|
|
@@ -72,14 +73,14 @@ const warnMissingBlocksField = (kind, slug, blocksField)=>{
|
|
|
72
73
|
* @see {@link BetterEditorConfig} for all options.
|
|
73
74
|
*/ export const betterEditor = (pluginOptions)=>(config)=>{
|
|
74
75
|
if (pluginOptions?.disabled) return config;
|
|
75
|
-
const
|
|
76
|
-
const
|
|
77
|
-
const
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
76
|
+
const defaultBlocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD;
|
|
77
|
+
const collectionMap = normalizeEntities(pluginOptions?.collections, defaultBlocksField);
|
|
78
|
+
const globalMap = normalizeEntities(pluginOptions?.globals, defaultBlocksField);
|
|
79
|
+
const toggleClientProps = (blocksField)=>({
|
|
80
|
+
blocksField,
|
|
81
|
+
adminPortalSelector: pluginOptions?.adminPortalSelector,
|
|
82
|
+
storageNamespace: pluginOptions?.storageNamespace
|
|
83
|
+
});
|
|
83
84
|
const showBanner = pluginOptions?.showSettingsBanner !== false;
|
|
84
85
|
const settingsGlobal = showBanner ? betterEditorSettingsGlobal : {
|
|
85
86
|
...betterEditorSettingsGlobal,
|
|
@@ -91,28 +92,30 @@ const warnMissingBlocksField = (kind, slug, blocksField)=>{
|
|
|
91
92
|
...existingGlobals,
|
|
92
93
|
settingsGlobal
|
|
93
94
|
];
|
|
94
|
-
if (
|
|
95
|
+
if (collectionMap.size === 0 && globalMap.size === 0) {
|
|
95
96
|
if (isDev) {
|
|
96
97
|
console.warn('[better-editor] plugin loaded with empty `collections` and `globals` — toggle button will not appear anywhere. Pass `collections: ["pages"]` (or similar) to BetterEditorConfig.');
|
|
97
98
|
}
|
|
98
99
|
return config;
|
|
99
100
|
}
|
|
100
|
-
if (
|
|
101
|
+
if (collectionMap.size > 0 && config.collections) {
|
|
101
102
|
config.collections = config.collections.map((collection)=>{
|
|
102
|
-
|
|
103
|
+
const blocksField = collectionMap.get(collection.slug);
|
|
104
|
+
if (blocksField === undefined) return collection;
|
|
103
105
|
if (isDev && !hasBlocksField(collection.fields, blocksField)) {
|
|
104
106
|
warnMissingBlocksField('collection', collection.slug, blocksField);
|
|
105
107
|
}
|
|
106
|
-
return withToggleInjected(collection, 'edit',
|
|
108
|
+
return withToggleInjected(collection, 'edit', toggleClientProps(blocksField));
|
|
107
109
|
});
|
|
108
110
|
}
|
|
109
|
-
if (
|
|
111
|
+
if (globalMap.size > 0) {
|
|
110
112
|
config.globals = (config.globals ?? []).map((global)=>{
|
|
111
|
-
|
|
113
|
+
const blocksField = globalMap.get(global.slug);
|
|
114
|
+
if (blocksField === undefined) return global;
|
|
112
115
|
if (isDev && !hasBlocksField(global.fields, blocksField)) {
|
|
113
116
|
warnMissingBlocksField('global', global.slug, blocksField);
|
|
114
117
|
}
|
|
115
|
-
return withToggleInjected(global, 'elements',
|
|
118
|
+
return withToggleInjected(global, 'elements', toggleClientProps(blocksField));
|
|
116
119
|
});
|
|
117
120
|
}
|
|
118
121
|
return config;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +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\n/**\n * Checks whether a field with the given `name` exists at the document's\n * top-level data path. Recurses into presentational containers that do\n * not introduce a path segment (`tabs` without a name, `row`, `collapsible`)\n * but stops at `group` and named tabs, since those namespace their children.\n */\nconst hasBlocksField = (fields: Field[] | undefined, name: string): boolean => {\n if (!Array.isArray(fields)) return false\n return fields.some((field) => {\n if ('name' in field && field.name === name) return true\n if (field.type === 'row' || field.type === 'collapsible') {\n return hasBlocksField(field.fields, name)\n }\n if (field.type === 'tabs') {\n return field.tabs.some((tab) =>\n 'name' in tab && tab.name ? false : hasBlocksField(tab.fields, name),\n )\n }\n return false\n })\n}\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
|
|
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'\nimport { normalizeEntities } from './internal/entities'\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\n/**\n * Checks whether a field with the given `name` exists at the document's\n * top-level data path. Recurses into presentational containers that do\n * not introduce a path segment (`tabs` without a name, `row`, `collapsible`)\n * but stops at `group` and named tabs, since those namespace their children.\n */\nconst hasBlocksField = (fields: Field[] | undefined, name: string): boolean => {\n if (!Array.isArray(fields)) return false\n return fields.some((field) => {\n if ('name' in field && field.name === name) return true\n if (field.type === 'row' || field.type === 'collapsible') {\n return hasBlocksField(field.fields, name)\n }\n if (field.type === 'tabs') {\n return field.tabs.some((tab) =>\n 'name' in tab && tab.name ? false : hasBlocksField(tab.fields, name),\n )\n }\n return false\n })\n}\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 defaultBlocksField = pluginOptions?.blocksField || DEFAULT_BLOCKS_FIELD\n const collectionMap = normalizeEntities(pluginOptions?.collections, defaultBlocksField)\n const globalMap = normalizeEntities(pluginOptions?.globals, defaultBlocksField)\n const toggleClientProps = (blocksField: string): 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 (collectionMap.size === 0 && globalMap.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 (collectionMap.size > 0 && config.collections) {\n config.collections = config.collections.map((collection) => {\n const blocksField = collectionMap.get(collection.slug)\n if (blocksField === undefined) return collection\n if (isDev && !hasBlocksField(collection.fields, blocksField)) {\n warnMissingBlocksField('collection', collection.slug, blocksField)\n }\n return withToggleInjected(collection, 'edit', toggleClientProps(blocksField))\n })\n }\n\n if (globalMap.size > 0) {\n config.globals = (config.globals ?? []).map((global) => {\n const blocksField = globalMap.get(global.slug)\n if (blocksField === undefined) return global\n if (isDev && !hasBlocksField(global.fields, blocksField)) {\n warnMissingBlocksField('global', global.slug, blocksField)\n }\n return withToggleInjected(global, 'elements', toggleClientProps(blocksField))\n })\n }\n\n return config\n }\n"],"names":["BETTER_EDITOR_SETTINGS_BANNER_FIELD","betterEditorSettingsGlobal","normalizeEntities","BETTER_EDITOR_SETTINGS_SLUG","VERSION","DEFAULT_BLOCKS_FIELD","TOGGLE_COMPONENT_PATH","isDev","process","env","NODE_ENV","hasBlocksField","fields","name","Array","isArray","some","field","type","tabs","tab","withToggleInjected","entity","slot","clientProps","admin","components","target","before","beforeDocumentControls","path","warnMissingBlocksField","kind","slug","blocksField","console","warn","betterEditor","pluginOptions","config","disabled","defaultBlocksField","collectionMap","collections","globalMap","globals","toggleClientProps","adminPortalSelector","storageNamespace","showBanner","showSettingsBanner","settingsGlobal","filter","f","existingGlobals","hasSettingsGlobal","g","size","map","collection","get","undefined","global"],"mappings":"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"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export type CoalescedDragHandlers = {
|
|
2
|
+
/** Runs at most once per animation frame with the latest pointer clientX. */
|
|
3
|
+
onUpdate: (clientX: number) => void;
|
|
4
|
+
/** Runs once when the drag ends or is torn down, after the final flush. */
|
|
5
|
+
onEnd?: () => void;
|
|
6
|
+
};
|
|
7
|
+
/**
|
|
8
|
+
* Horizontal pointer drag: window mousemove/mouseup listeners that coalesce
|
|
9
|
+
* moves into one onUpdate per frame and flush the final position on release.
|
|
10
|
+
* Sets the body cursor + disables text selection. Returns a function to end it.
|
|
11
|
+
*/
|
|
12
|
+
export declare const startHorizontalDrag: (cursor: string, { onUpdate, onEnd }: CoalescedDragHandlers) => (() => void);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Horizontal pointer drag: window mousemove/mouseup listeners that coalesce
|
|
3
|
+
* moves into one onUpdate per frame and flush the final position on release.
|
|
4
|
+
* Sets the body cursor + disables text selection. Returns a function to end it.
|
|
5
|
+
*/ export const startHorizontalDrag = (cursor, { onUpdate, onEnd })=>{
|
|
6
|
+
let rafId = 0;
|
|
7
|
+
let pendingX = 0;
|
|
8
|
+
let hasPending = false;
|
|
9
|
+
const flush = ()=>{
|
|
10
|
+
if (!hasPending) return;
|
|
11
|
+
hasPending = false;
|
|
12
|
+
onUpdate(pendingX);
|
|
13
|
+
};
|
|
14
|
+
const onMove = (e)=>{
|
|
15
|
+
pendingX = e.clientX;
|
|
16
|
+
hasPending = true;
|
|
17
|
+
if (!rafId) {
|
|
18
|
+
rafId = window.requestAnimationFrame(()=>{
|
|
19
|
+
rafId = 0;
|
|
20
|
+
flush();
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
let ended = false;
|
|
25
|
+
const end = ()=>{
|
|
26
|
+
if (ended) return;
|
|
27
|
+
ended = true;
|
|
28
|
+
window.removeEventListener('mousemove', onMove);
|
|
29
|
+
window.removeEventListener('mouseup', end);
|
|
30
|
+
if (rafId) {
|
|
31
|
+
window.cancelAnimationFrame(rafId);
|
|
32
|
+
rafId = 0;
|
|
33
|
+
}
|
|
34
|
+
flush();
|
|
35
|
+
document.body.style.cursor = '';
|
|
36
|
+
document.body.style.userSelect = '';
|
|
37
|
+
onEnd?.();
|
|
38
|
+
};
|
|
39
|
+
document.body.style.cursor = cursor;
|
|
40
|
+
document.body.style.userSelect = 'none';
|
|
41
|
+
window.addEventListener('mousemove', onMove);
|
|
42
|
+
window.addEventListener('mouseup', end);
|
|
43
|
+
return end;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
//# sourceMappingURL=drag.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/drag.ts"],"sourcesContent":["export type CoalescedDragHandlers = {\n /** Runs at most once per animation frame with the latest pointer clientX. */\n onUpdate: (clientX: number) => void\n /** Runs once when the drag ends or is torn down, after the final flush. */\n onEnd?: () => void\n}\n\n/**\n * Horizontal pointer drag: window mousemove/mouseup listeners that coalesce\n * moves into one onUpdate per frame and flush the final position on release.\n * Sets the body cursor + disables text selection. Returns a function to end it.\n */\nexport const startHorizontalDrag = (\n cursor: string,\n { onUpdate, onEnd }: CoalescedDragHandlers,\n): (() => void) => {\n let rafId = 0\n let pendingX = 0\n let hasPending = false\n\n const flush = (): void => {\n if (!hasPending) return\n hasPending = false\n onUpdate(pendingX)\n }\n\n const onMove = (e: MouseEvent): void => {\n pendingX = e.clientX\n hasPending = true\n if (!rafId) {\n rafId = window.requestAnimationFrame(() => {\n rafId = 0\n flush()\n })\n }\n }\n\n let ended = false\n const end = (): void => {\n if (ended) return\n ended = true\n window.removeEventListener('mousemove', onMove)\n window.removeEventListener('mouseup', end)\n if (rafId) {\n window.cancelAnimationFrame(rafId)\n rafId = 0\n }\n flush()\n document.body.style.cursor = ''\n document.body.style.userSelect = ''\n onEnd?.()\n }\n\n document.body.style.cursor = cursor\n document.body.style.userSelect = 'none'\n window.addEventListener('mousemove', onMove)\n window.addEventListener('mouseup', end)\n\n return end\n}\n"],"names":["startHorizontalDrag","cursor","onUpdate","onEnd","rafId","pendingX","hasPending","flush","onMove","e","clientX","window","requestAnimationFrame","ended","end","removeEventListener","cancelAnimationFrame","document","body","style","userSelect","addEventListener"],"mappings":"AAOA;;;;CAIC,GACD,OAAO,MAAMA,sBAAsB,CACjCC,QACA,EAAEC,QAAQ,EAAEC,KAAK,EAAyB;IAE1C,IAAIC,QAAQ;IACZ,IAAIC,WAAW;IACf,IAAIC,aAAa;IAEjB,MAAMC,QAAQ;QACZ,IAAI,CAACD,YAAY;QACjBA,aAAa;QACbJ,SAASG;IACX;IAEA,MAAMG,SAAS,CAACC;QACdJ,WAAWI,EAAEC,OAAO;QACpBJ,aAAa;QACb,IAAI,CAACF,OAAO;YACVA,QAAQO,OAAOC,qBAAqB,CAAC;gBACnCR,QAAQ;gBACRG;YACF;QACF;IACF;IAEA,IAAIM,QAAQ;IACZ,MAAMC,MAAM;QACV,IAAID,OAAO;QACXA,QAAQ;QACRF,OAAOI,mBAAmB,CAAC,aAAaP;QACxCG,OAAOI,mBAAmB,CAAC,WAAWD;QACtC,IAAIV,OAAO;YACTO,OAAOK,oBAAoB,CAACZ;YAC5BA,QAAQ;QACV;QACAG;QACAU,SAASC,IAAI,CAACC,KAAK,CAAClB,MAAM,GAAG;QAC7BgB,SAASC,IAAI,CAACC,KAAK,CAACC,UAAU,GAAG;QACjCjB;IACF;IAEAc,SAASC,IAAI,CAACC,KAAK,CAAClB,MAAM,GAAGA;IAC7BgB,SAASC,IAAI,CAACC,KAAK,CAACC,UAAU,GAAG;IACjCT,OAAOU,gBAAgB,CAAC,aAAab;IACrCG,OAAOU,gBAAgB,CAAC,WAAWP;IAEnC,OAAOA;AACT,EAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Normalizes the `collections`/`globals` plugin option (a slug list OR a
|
|
2
|
+
* slug → options record) into a slug → blocksField map, applying the default
|
|
3
|
+
* where unset. */
|
|
4
|
+
export declare const normalizeEntities: (option: string[] | Partial<Record<string, {
|
|
5
|
+
blocksField?: string;
|
|
6
|
+
}>> | undefined, defaultBlocksField: string) => Map<string, string>;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/** Normalizes the `collections`/`globals` plugin option (a slug list OR a
|
|
2
|
+
* slug → options record) into a slug → blocksField map, applying the default
|
|
3
|
+
* where unset. */ export const normalizeEntities = (option, defaultBlocksField)=>{
|
|
4
|
+
const map = new Map();
|
|
5
|
+
if (Array.isArray(option)) {
|
|
6
|
+
for (const slug of option)map.set(slug, defaultBlocksField);
|
|
7
|
+
} else if (option) {
|
|
8
|
+
for (const [slug, cfg] of Object.entries(option)){
|
|
9
|
+
map.set(slug, cfg?.blocksField || defaultBlocksField);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
return map;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//# sourceMappingURL=entities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/internal/entities.ts"],"sourcesContent":["/** Normalizes the `collections`/`globals` plugin option (a slug list OR a\n * slug → options record) into a slug → blocksField map, applying the default\n * where unset. */\nexport const normalizeEntities = (\n option: string[] | Partial<Record<string, { blocksField?: string }>> | undefined,\n defaultBlocksField: string,\n): Map<string, string> => {\n const map = new Map<string, string>()\n if (Array.isArray(option)) {\n for (const slug of option) map.set(slug, defaultBlocksField)\n } else if (option) {\n for (const [slug, cfg] of Object.entries(option)) {\n map.set(slug, cfg?.blocksField || defaultBlocksField)\n }\n }\n return map\n}\n"],"names":["normalizeEntities","option","defaultBlocksField","map","Map","Array","isArray","slug","set","cfg","Object","entries","blocksField"],"mappings":"AAAA;;gBAEgB,GAChB,OAAO,MAAMA,oBAAoB,CAC/BC,QACAC;IAEA,MAAMC,MAAM,IAAIC;IAChB,IAAIC,MAAMC,OAAO,CAACL,SAAS;QACzB,KAAK,MAAMM,QAAQN,OAAQE,IAAIK,GAAG,CAACD,MAAML;IAC3C,OAAO,IAAID,QAAQ;QACjB,KAAK,MAAM,CAACM,MAAME,IAAI,IAAIC,OAAOC,OAAO,CAACV,QAAS;YAChDE,IAAIK,GAAG,CAACD,MAAME,KAAKG,eAAeV;QACpC;IACF;IACA,OAAOC;AACT,EAAC"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export declare const HOVER_STYLE_ID = "better-editor-hover-style";
|
|
2
2
|
export declare const TOOLBAR_ID = "better-editor-block-toolbar";
|
|
3
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
|
|
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 box-shadow: inset 0 0 0 100vmax 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 box-shadow: inset 0 0 0 100vmax 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
5
|
export type HoverVars = {
|
|
6
6
|
topColor: string;
|
|
7
7
|
nestedColor: string;
|
|
@@ -14,12 +14,12 @@ export const HOVER_CSS = `
|
|
|
14
14
|
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {
|
|
15
15
|
outline: var(${VAR_OUTLINE_WIDTH}) solid var(${VAR_TOP});
|
|
16
16
|
outline-offset: calc(-1 * var(${VAR_OUTLINE_WIDTH}) - 1px);
|
|
17
|
-
|
|
17
|
+
box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(${VAR_TOP}) 10%, transparent);
|
|
18
18
|
}
|
|
19
19
|
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}]:hover,
|
|
20
20
|
body:not([${INTERACT_BODY_ATTR}]) [${BLOCK_ID_ATTR}] [${BLOCK_ID_ATTR}].${ACTIVE_CLASS} {
|
|
21
21
|
outline-color: var(${VAR_NESTED});
|
|
22
|
-
|
|
22
|
+
box-shadow: inset 0 0 0 100vmax color-mix(in srgb, var(${VAR_NESTED}) 10%, transparent);
|
|
23
23
|
}
|
|
24
24
|
body[${INTERACT_BODY_ATTR}] #${TOOLBAR_ID} { display: none; }
|
|
25
25
|
|