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.
Files changed (55) hide show
  1. package/dist/admin/ErrorBoundary.d.ts +1 -1
  2. package/dist/admin/LiveEditorOverlay.js +6 -7
  3. package/dist/admin/LiveEditorOverlay.js.map +1 -1
  4. package/dist/admin/PreviewFrame.d.ts +3 -3
  5. package/dist/admin/PreviewFrame.js +4 -6
  6. package/dist/admin/PreviewFrame.js.map +1 -1
  7. package/dist/admin/PreviewToolbar.d.ts +3 -3
  8. package/dist/admin/PreviewToolbar.js +7 -13
  9. package/dist/admin/PreviewToolbar.js.map +1 -1
  10. package/dist/admin/ViewportToggle.js.map +1 -1
  11. package/dist/admin/WidthChip.d.ts +4 -0
  12. package/dist/admin/WidthChip.js +30 -0
  13. package/dist/admin/WidthChip.js.map +1 -0
  14. package/dist/admin/blocks/schema.d.ts +8 -0
  15. package/dist/admin/blocks/schema.js +13 -2
  16. package/dist/admin/blocks/schema.js.map +1 -1
  17. package/dist/admin/icons.d.ts +24 -23
  18. package/dist/admin/icons.js +488 -30
  19. package/dist/admin/icons.js.map +1 -1
  20. package/dist/admin/sidebar/BlockSettingsTab.js +7 -4
  21. package/dist/admin/sidebar/BlockSettingsTab.js.map +1 -1
  22. package/dist/admin/sidebar/Sidebar.d.ts +1 -1
  23. package/dist/admin/sidebar/Sidebar.js +8 -2
  24. package/dist/admin/sidebar/Sidebar.js.map +1 -1
  25. package/dist/admin/sidebar/ValidationSummary.d.ts +6 -0
  26. package/dist/admin/sidebar/ValidationSummary.js +48 -0
  27. package/dist/admin/sidebar/ValidationSummary.js.map +1 -0
  28. package/dist/admin/sidebar/validation.d.ts +10 -0
  29. package/dist/admin/sidebar/validation.js +28 -0
  30. package/dist/admin/sidebar/validation.js.map +1 -0
  31. package/dist/hooks/usePreviewHandleDrag.js +9 -20
  32. package/dist/hooks/usePreviewHandleDrag.js.map +1 -1
  33. package/dist/hooks/useSidebarResize.js +8 -16
  34. package/dist/hooks/useSidebarResize.js.map +1 -1
  35. package/dist/index.js +18 -15
  36. package/dist/index.js.map +1 -1
  37. package/dist/internal/drag.d.ts +12 -0
  38. package/dist/internal/drag.js +46 -0
  39. package/dist/internal/drag.js.map +1 -0
  40. package/dist/internal/entities.d.ts +6 -0
  41. package/dist/internal/entities.js +15 -0
  42. package/dist/internal/entities.js.map +1 -0
  43. package/dist/preview/hover-css.d.ts +1 -1
  44. package/dist/preview/hover-css.js +2 -2
  45. package/dist/preview/hover-css.js.map +1 -1
  46. package/dist/styles/sidebar.css +53 -0
  47. package/dist/types.d.ts +19 -11
  48. package/dist/types.js.map +1 -1
  49. package/dist/version.d.ts +1 -1
  50. package/dist/version.js +3 -4
  51. package/dist/version.js.map +1 -1
  52. package/package.json +4 -3
  53. package/dist/hooks/useIframeResizeObserver.d.ts +0 -2
  54. package/dist/hooks/useIframeResizeObserver.js +0 -20
  55. package/dist/hooks/useIframeResizeObserver.js.map +0 -1
@@ -1 +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"}
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 box-shadow: inset 0 0 0 100vmax 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 box-shadow: inset 0 0 0 100vmax 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;2DACK,EAAEF,QAAQ;;YAEzD,EAAED,mBAAmB,IAAI,EAAEH,cAAc,GAAG,EAAEA,cAAc;YAC5D,EAAEG,mBAAmB,IAAI,EAAEH,cAAc,GAAG,EAAEA,cAAc,EAAE,EAAED,aAAa;uBAClE,EAAEM,WAAW;2DACuB,EAAEA,WAAW;;OAEjE,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"}
@@ -73,6 +73,59 @@
73
73
  border-bottom-color: var(--theme-text);
74
74
  }
75
75
 
76
+ /* Validation summary — surfaces invalid fields across all blocks. */
77
+ .better-editor-sidebar__errors {
78
+ margin: 12px 16px 0;
79
+ padding: 10px 12px;
80
+ border: 1px solid var(--theme-error-250, #f1b0ad);
81
+ background: var(--theme-error-50, #fdeceb);
82
+ border-radius: 4px;
83
+ font-size: 13px;
84
+ line-height: 1.4;
85
+ }
86
+
87
+ .better-editor-sidebar__errors-title {
88
+ margin: 0 0 6px;
89
+ font-weight: 600;
90
+ color: var(--theme-error-600, #b22a22);
91
+ }
92
+
93
+ .better-editor-sidebar__errors-list {
94
+ margin: 0;
95
+ padding: 0;
96
+ list-style: none;
97
+ display: flex;
98
+ flex-direction: column;
99
+ gap: 4px;
100
+ }
101
+
102
+ .better-editor-sidebar__errors-item {
103
+ display: flex;
104
+ flex-wrap: wrap;
105
+ gap: 4px 8px;
106
+ align-items: baseline;
107
+ }
108
+
109
+ .better-editor-sidebar__errors-jump {
110
+ padding: 0;
111
+ border: 0;
112
+ background: none;
113
+ font: inherit;
114
+ font-weight: 600;
115
+ color: var(--theme-error-600, #b22a22);
116
+ text-decoration: underline;
117
+ cursor: pointer;
118
+ }
119
+
120
+ .better-editor-sidebar__errors-label {
121
+ font-weight: 600;
122
+ color: var(--theme-error-600, #b22a22);
123
+ }
124
+
125
+ .better-editor-sidebar__errors-message {
126
+ color: var(--theme-elevation-600, #555);
127
+ }
128
+
76
129
  /* Visually hidden but available to assistive technology — used by the
77
130
  selection live region. Standard SR-only recipe. */
78
131
  .better-editor-sr-only {
package/dist/types.d.ts CHANGED
@@ -1,22 +1,30 @@
1
+ import type { CollectionSlug, GlobalSlug } from 'payload';
2
+ /** Per-entity overrides for a collection/global. */
3
+ export type BetterEditorEntityOptions = {
4
+ /** Blocks-field name for this entity; falls back to the top-level `blocksField`. */
5
+ blocksField?: string;
6
+ };
1
7
  export type BetterEditorConfig = {
2
8
  /** Skip plugin installation entirely (e.g. behind a feature flag). */
3
9
  disabled?: boolean;
4
10
  /**
5
- * Collection slugs that should get the "Open Better Editor" toggle in
6
- * their `beforeDocumentControls`. Each collection must have an
7
- * `admin.livePreview.url` configured for the preview to render.
11
+ * Collections that get the "Open Better Editor" toggle. Either a list of
12
+ * slugs, or a slug → options record for per-collection settings (e.g. a
13
+ * different `blocksField`). Each collection needs `admin.preview` configured
14
+ * for the preview to render and the toggle to appear.
8
15
  */
9
- collections?: string[];
16
+ collections?: string[] | Partial<Record<CollectionSlug, BetterEditorEntityOptions>>;
10
17
  /**
11
- * Global slugs that should get the "Open Better Editor" toggle in
12
- * their `beforeDocumentControls`. Each global must have an
13
- * `admin.livePreview.url` configured for the preview to render.
18
+ * Globals that get the "Open Better Editor" toggle — a list of slugs or a
19
+ * slug options record. Each global needs `admin.preview` configured for the
20
+ * toggle to appear.
14
21
  */
15
- globals?: string[];
22
+ globals?: string[] | Partial<Record<GlobalSlug, BetterEditorEntityOptions>>;
16
23
  /**
17
- * Name of the `blocks` field inside the configured collections/globals
18
- * that the sidebar should target. Top-level only for now; nested paths
19
- * (e.g. `content.layout`) are not supported. Defaults to `'layout'`.
24
+ * Default name of the `blocks` field the sidebar targets. Top-level only;
25
+ * nested paths (e.g. `content.layout`) are not supported. Per-collection
26
+ * overrides via the `collections`/`globals` record take precedence.
27
+ * Defaults to `'layout'`.
20
28
  */
21
29
  blocksField?: string;
22
30
  /**
package/dist/types.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/types.ts"],"sourcesContent":["export type BetterEditorConfig = {\n /** Skip plugin installation entirely (e.g. behind a feature flag). */\n disabled?: boolean\n /**\n * Collection slugs that should get the \"Open Better Editor\" toggle in\n * their `beforeDocumentControls`. Each collection must have an\n * `admin.livePreview.url` configured for the preview to render.\n */\n collections?: string[]\n /**\n * Global slugs that should get the \"Open Better Editor\" toggle in\n * their `beforeDocumentControls`. Each global must have an\n * `admin.livePreview.url` configured for the preview to render.\n */\n globals?: string[]\n /**\n * Name of the `blocks` field inside the configured collections/globals\n * that the sidebar should target. Top-level only for now; nested paths\n * (e.g. `content.layout`) are not supported. Defaults to `'layout'`.\n */\n blocksField?: string\n /**\n * CSS selector for the Payload admin element the overlay portals into.\n * Override only if the default selector breaks against a future Payload\n * version. The plugin falls back to `<main>` and finally `<body>`.\n */\n adminPortalSelector?: string\n /**\n * Prefix for all `localStorage` keys the editor writes (sidebar width,\n * responsive viewport width, …). Set this if multiple Better Editor\n * instances share the same origin and would otherwise collide.\n * Defaults to `'better-editor'`.\n */\n storageNamespace?: string\n /**\n * Show the plugin info banner (version, GitHub links, \"Report a bug\")\n * at the top of the `BetterEditorSettings` global. Set to `false` to\n * hide it for end users. Defaults to `true`.\n */\n showSettingsBanner?: boolean\n}\n"],"names":[],"mappings":"AAAA,WAwCC"}
1
+ {"version":3,"sources":["../src/types.ts"],"sourcesContent":["import type { CollectionSlug, GlobalSlug } from 'payload'\n\n/** Per-entity overrides for a collection/global. */\nexport type BetterEditorEntityOptions = {\n /** Blocks-field name for this entity; falls back to the top-level `blocksField`. */\n blocksField?: string\n}\n\nexport type BetterEditorConfig = {\n /** Skip plugin installation entirely (e.g. behind a feature flag). */\n disabled?: boolean\n /**\n * Collections that get the \"Open Better Editor\" toggle. Either a list of\n * slugs, or a slug → options record for per-collection settings (e.g. a\n * different `blocksField`). Each collection needs `admin.preview` configured\n * for the preview to render and the toggle to appear.\n */\n collections?: string[] | Partial<Record<CollectionSlug, BetterEditorEntityOptions>>\n /**\n * Globals that get the \"Open Better Editor\" toggle — a list of slugs or a\n * slug options record. Each global needs `admin.preview` configured for the\n * toggle to appear.\n */\n globals?: string[] | Partial<Record<GlobalSlug, BetterEditorEntityOptions>>\n /**\n * Default name of the `blocks` field the sidebar targets. Top-level only;\n * nested paths (e.g. `content.layout`) are not supported. Per-collection\n * overrides via the `collections`/`globals` record take precedence.\n * Defaults to `'layout'`.\n */\n blocksField?: string\n /**\n * CSS selector for the Payload admin element the overlay portals into.\n * Override only if the default selector breaks against a future Payload\n * version. The plugin falls back to `<main>` and finally `<body>`.\n */\n adminPortalSelector?: string\n /**\n * Prefix for all `localStorage` keys the editor writes (sidebar width,\n * responsive viewport width, …). Set this if multiple Better Editor\n * instances share the same origin and would otherwise collide.\n * Defaults to `'better-editor'`.\n */\n storageNamespace?: string\n /**\n * Show the plugin info banner (version, GitHub links, \"Report a bug\")\n * at the top of the `BetterEditorSettings` global. Set to `false` to\n * hide it for end users. Defaults to `true`.\n */\n showSettingsBanner?: boolean\n}\n"],"names":[],"mappings":"AAQA,WA0CC"}
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const VERSION = "1.0.4";
1
+ export declare const VERSION = "1.2.0";
package/dist/version.js CHANGED
@@ -1,6 +1,5 @@
1
- // Single source of truth — re-exported from index.ts as `VERSION` and used
2
- // by client components (e.g. the settings footer) without dragging the
3
- // whole server entry into the client bundle.
4
- export const VERSION = '1.0.4';
1
+ // Package version, re-exported as VERSION; read by the settings banner without
2
+ // pulling the server entry into the client bundle.
3
+ export const VERSION = '1.2.0';
5
4
 
6
5
  //# sourceMappingURL=version.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Single source of truth — re-exported from index.ts as `VERSION` and used\n// by client components (e.g. the settings footer) without dragging the\n// whole server entry into the client bundle.\nexport const VERSION = '1.0.4'\n"],"names":["VERSION"],"mappings":"AAAA,2EAA2E;AAC3E,uEAAuE;AACvE,6CAA6C;AAC7C,OAAO,MAAMA,UAAU,QAAO"}
1
+ {"version":3,"sources":["../src/version.ts"],"sourcesContent":["// Package version, re-exported as VERSION; read by the settings banner without\n// pulling the server entry into the client bundle.\nexport const VERSION = '1.2.0'\n"],"names":["VERSION"],"mappings":"AAAA,+EAA+E;AAC/E,mDAAmD;AACnD,OAAO,MAAMA,UAAU,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "payload-better-editor",
3
- "version": "1.0.4",
3
+ "version": "1.2.0",
4
4
  "description": "Block editor plugin for Payload CMS that adds a side-by-side live-preview iframe and sidebar to the edit view.",
5
5
  "license": "MIT",
6
6
  "author": "scorpio-99",
@@ -65,7 +65,6 @@
65
65
  "prepublishOnly": "pnpm clean && pnpm build"
66
66
  },
67
67
  "peerDependencies": {
68
- "lucide-react": ">=0.577.0",
69
68
  "payload": ">=3.81.0",
70
69
  "react": "^19.0.0",
71
70
  "react-dom": "^19.0.0"
@@ -75,13 +74,15 @@
75
74
  "@payloadcms/ui": "3.81.0",
76
75
  "@swc/cli": "0.6.0",
77
76
  "@swc/core": "1.10.7",
77
+ "@testing-library/dom": "^10.4.1",
78
+ "@testing-library/react": "^16.3.0",
78
79
  "@types/node": "22.5.4",
79
80
  "@types/react": "19.2.1",
80
81
  "@types/react-dom": "19.2.1",
81
82
  "copyfiles": "2.4.1",
82
83
  "eslint": "^9.16.0",
83
84
  "eslint-plugin-react-hooks": "^5.2.0",
84
- "lucide-react": "^0.577.0",
85
+ "jsdom": "^26.1.0",
85
86
  "payload": "3.81.0",
86
87
  "react": "19.2.1",
87
88
  "react-dom": "19.2.1",
@@ -1,2 +0,0 @@
1
- import { type RefObject } from 'react';
2
- export declare const useIframeResizeObserver: (iframeRef: RefObject<HTMLIFrameElement | null>, onIframeWidthChange?: (width: number) => void) => void;
@@ -1,20 +0,0 @@
1
- 'use client';
2
- import { useEffect } from 'react';
3
- export const useIframeResizeObserver = (iframeRef, onIframeWidthChange)=>{
4
- useEffect(()=>{
5
- const iframe = iframeRef.current;
6
- if (!iframe || !onIframeWidthChange || typeof ResizeObserver === 'undefined') return;
7
- onIframeWidthChange(iframe.clientWidth);
8
- const ro = new ResizeObserver((entries)=>{
9
- const w = entries[0]?.contentRect?.width;
10
- if (typeof w === 'number') onIframeWidthChange(Math.round(w));
11
- });
12
- ro.observe(iframe);
13
- return ()=>ro.disconnect();
14
- }, [
15
- iframeRef,
16
- onIframeWidthChange
17
- ]);
18
- };
19
-
20
- //# sourceMappingURL=useIframeResizeObserver.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../../src/hooks/useIframeResizeObserver.ts"],"sourcesContent":["'use client'\n\nimport { useEffect, type RefObject } from 'react'\n\nexport const useIframeResizeObserver = (\n iframeRef: RefObject<HTMLIFrameElement | null>,\n onIframeWidthChange?: (width: number) => void,\n): void => {\n useEffect(() => {\n const iframe = iframeRef.current\n if (!iframe || !onIframeWidthChange || typeof ResizeObserver === 'undefined') return\n onIframeWidthChange(iframe.clientWidth)\n const ro = new ResizeObserver((entries) => {\n const w = entries[0]?.contentRect?.width\n if (typeof w === 'number') onIframeWidthChange(Math.round(w))\n })\n ro.observe(iframe)\n return () => ro.disconnect()\n }, [iframeRef, onIframeWidthChange])\n}\n"],"names":["useEffect","useIframeResizeObserver","iframeRef","onIframeWidthChange","iframe","current","ResizeObserver","clientWidth","ro","entries","w","contentRect","width","Math","round","observe","disconnect"],"mappings":"AAAA;AAEA,SAASA,SAAS,QAAwB,QAAO;AAEjD,OAAO,MAAMC,0BAA0B,CACrCC,WACAC;IAEAH,UAAU;QACR,MAAMI,SAASF,UAAUG,OAAO;QAChC,IAAI,CAACD,UAAU,CAACD,uBAAuB,OAAOG,mBAAmB,aAAa;QAC9EH,oBAAoBC,OAAOG,WAAW;QACtC,MAAMC,KAAK,IAAIF,eAAe,CAACG;YAC7B,MAAMC,IAAID,OAAO,CAAC,EAAE,EAAEE,aAAaC;YACnC,IAAI,OAAOF,MAAM,UAAUP,oBAAoBU,KAAKC,KAAK,CAACJ;QAC5D;QACAF,GAAGO,OAAO,CAACX;QACX,OAAO,IAAMI,GAAGQ,UAAU;IAC5B,GAAG;QAACd;QAAWC;KAAoB;AACrC,EAAC"}