@payload-onpage/react 0.2.1 → 0.3.1

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 (218) hide show
  1. package/README.md +6 -0
  2. package/dist/OnPageClient.d.ts.map +1 -1
  3. package/dist/OnPageClient.js +9 -5
  4. package/dist/OnPageClient.js.map +1 -1
  5. package/dist/annotations.d.ts +1 -1
  6. package/dist/annotations.d.ts.map +1 -1
  7. package/dist/context.d.ts +19 -5
  8. package/dist/context.d.ts.map +1 -1
  9. package/dist/context.js +31 -7
  10. package/dist/context.js.map +1 -1
  11. package/dist/dom.d.ts +16 -12
  12. package/dist/dom.d.ts.map +1 -1
  13. package/dist/dom.js +16 -16
  14. package/dist/dom.js.map +1 -1
  15. package/dist/editor/FieldHost.d.ts.map +1 -1
  16. package/dist/editor/FieldHost.js +18 -11
  17. package/dist/editor/FieldHost.js.map +1 -1
  18. package/dist/editor/OnPageEditor.d.ts +15 -8
  19. package/dist/editor/OnPageEditor.d.ts.map +1 -1
  20. package/dist/editor/OnPageEditor.js +73 -103
  21. package/dist/editor/OnPageEditor.js.map +1 -1
  22. package/dist/editor/editTarget.d.ts +28 -0
  23. package/dist/editor/editTarget.d.ts.map +1 -0
  24. package/dist/editor/editTarget.js +20 -0
  25. package/dist/editor/editTarget.js.map +1 -0
  26. package/dist/editor/useClientDocs.d.ts +4 -0
  27. package/dist/editor/useClientDocs.d.ts.map +1 -0
  28. package/dist/editor/useClientDocs.js +16 -0
  29. package/dist/editor/useClientDocs.js.map +1 -0
  30. package/dist/editor/useEditorSession.d.ts +13 -0
  31. package/dist/editor/useEditorSession.d.ts.map +1 -0
  32. package/dist/editor/useEditorSession.js +30 -0
  33. package/dist/editor/useEditorSession.js.map +1 -0
  34. package/dist/editor/useFlash.d.ts +4 -0
  35. package/dist/editor/useFlash.d.ts.map +1 -0
  36. package/dist/editor/useFlash.js +16 -0
  37. package/dist/editor/useFlash.js.map +1 -0
  38. package/dist/editor/useGhosts.d.ts +5 -7
  39. package/dist/editor/useGhosts.d.ts.map +1 -1
  40. package/dist/editor/useGhosts.js.map +1 -1
  41. package/dist/editor/usePageEvents.d.ts.map +1 -1
  42. package/dist/editor/usePageEvents.js +16 -16
  43. package/dist/editor/usePageEvents.js.map +1 -1
  44. package/dist/editor-kit.d.ts +10 -6
  45. package/dist/editor-kit.d.ts.map +1 -1
  46. package/dist/editor-kit.js +8 -5
  47. package/dist/editor-kit.js.map +1 -1
  48. package/dist/editors/CheckboxEditor.d.ts +6 -1
  49. package/dist/editors/CheckboxEditor.d.ts.map +1 -1
  50. package/dist/editors/CheckboxEditor.js +6 -3
  51. package/dist/editors/CheckboxEditor.js.map +1 -1
  52. package/dist/editors/ColorEditor.d.ts +3 -1
  53. package/dist/editors/ColorEditor.d.ts.map +1 -1
  54. package/dist/editors/ColorEditor.js +9 -15
  55. package/dist/editors/ColorEditor.js.map +1 -1
  56. package/dist/editors/DateEditor.d.ts +3 -1
  57. package/dist/editors/DateEditor.d.ts.map +1 -1
  58. package/dist/editors/DateEditor.js +10 -12
  59. package/dist/editors/DateEditor.js.map +1 -1
  60. package/dist/editors/InlineText.d.ts +2 -2
  61. package/dist/editors/InlineText.d.ts.map +1 -1
  62. package/dist/editors/InlineText.js +2 -3
  63. package/dist/editors/InlineText.js.map +1 -1
  64. package/dist/editors/NumberEditor.d.ts +3 -1
  65. package/dist/editors/NumberEditor.d.ts.map +1 -1
  66. package/dist/editors/NumberEditor.js +9 -17
  67. package/dist/editors/NumberEditor.js.map +1 -1
  68. package/dist/editors/RelationshipEditor.d.ts +7 -1
  69. package/dist/editors/RelationshipEditor.d.ts.map +1 -1
  70. package/dist/editors/RelationshipEditor.js +10 -18
  71. package/dist/editors/RelationshipEditor.js.map +1 -1
  72. package/dist/editors/SelectEditor.d.ts +4 -1
  73. package/dist/editors/SelectEditor.d.ts.map +1 -1
  74. package/dist/editors/SelectEditor.js +8 -13
  75. package/dist/editors/SelectEditor.js.map +1 -1
  76. package/dist/editors/UploadEditor.d.ts +7 -1
  77. package/dist/editors/UploadEditor.d.ts.map +1 -1
  78. package/dist/editors/UploadEditor.js +5 -5
  79. package/dist/editors/UploadEditor.js.map +1 -1
  80. package/dist/editors/index.d.ts +0 -1
  81. package/dist/editors/index.d.ts.map +1 -1
  82. package/dist/editors/index.js +56 -16
  83. package/dist/editors/index.js.map +1 -1
  84. package/dist/editors/kit.d.ts +39 -0
  85. package/dist/editors/kit.d.ts.map +1 -0
  86. package/dist/editors/kit.js +44 -0
  87. package/dist/editors/kit.js.map +1 -0
  88. package/dist/editors/useDebouncedSearch.d.ts +10 -6
  89. package/dist/editors/useDebouncedSearch.d.ts.map +1 -1
  90. package/dist/editors/useDebouncedSearch.js +11 -7
  91. package/dist/editors/useDebouncedSearch.js.map +1 -1
  92. package/dist/geometry.d.ts +49 -0
  93. package/dist/geometry.d.ts.map +1 -0
  94. package/dist/geometry.js +48 -0
  95. package/dist/geometry.js.map +1 -0
  96. package/dist/hub.d.ts +4 -4
  97. package/dist/hub.d.ts.map +1 -1
  98. package/dist/hub.js.map +1 -1
  99. package/dist/index.d.ts +3 -3
  100. package/dist/index.d.ts.map +1 -1
  101. package/dist/index.js +2 -2
  102. package/dist/index.js.map +1 -1
  103. package/dist/layout.d.ts.map +1 -1
  104. package/dist/layout.js +4 -2
  105. package/dist/layout.js.map +1 -1
  106. package/dist/locators.d.ts +0 -2
  107. package/dist/locators.d.ts.map +1 -1
  108. package/dist/locators.js +10 -16
  109. package/dist/locators.js.map +1 -1
  110. package/dist/messages.d.ts +15 -2
  111. package/dist/messages.d.ts.map +1 -1
  112. package/dist/messages.js +5 -0
  113. package/dist/messages.js.map +1 -1
  114. package/dist/registry.d.ts +21 -2
  115. package/dist/registry.d.ts.map +1 -1
  116. package/dist/registry.js +31 -6
  117. package/dist/registry.js.map +1 -1
  118. package/dist/rowOps.d.ts +20 -0
  119. package/dist/rowOps.d.ts.map +1 -0
  120. package/dist/rowOps.js +48 -0
  121. package/dist/rowOps.js.map +1 -0
  122. package/dist/sanitize.d.ts +2 -0
  123. package/dist/sanitize.d.ts.map +1 -1
  124. package/dist/sanitize.js +3 -0
  125. package/dist/sanitize.js.map +1 -1
  126. package/dist/settings.d.ts +10 -0
  127. package/dist/settings.d.ts.map +1 -0
  128. package/dist/settings.js +30 -0
  129. package/dist/settings.js.map +1 -0
  130. package/dist/styles.d.ts +1 -5
  131. package/dist/styles.d.ts.map +1 -1
  132. package/dist/styles.js +5 -4
  133. package/dist/styles.js.map +1 -1
  134. package/dist/targets.d.ts +1 -1
  135. package/dist/targets.d.ts.map +1 -1
  136. package/dist/types.d.ts +71 -46
  137. package/dist/types.d.ts.map +1 -1
  138. package/dist/ui/BlockPicker.d.ts.map +1 -1
  139. package/dist/ui/BlockPicker.js +6 -5
  140. package/dist/ui/BlockPicker.js.map +1 -1
  141. package/dist/ui/Dialog.d.ts +0 -5
  142. package/dist/ui/Dialog.d.ts.map +1 -1
  143. package/dist/ui/Dialog.js +10 -6
  144. package/dist/ui/Dialog.js.map +1 -1
  145. package/dist/ui/Dock.d.ts +1 -6
  146. package/dist/ui/Dock.d.ts.map +1 -1
  147. package/dist/ui/Dock.js +46 -28
  148. package/dist/ui/Dock.js.map +1 -1
  149. package/dist/ui/Overlay.d.ts.map +1 -1
  150. package/dist/ui/Overlay.js +39 -37
  151. package/dist/ui/Overlay.js.map +1 -1
  152. package/dist/ui/PreviewToolbar.d.ts +3 -3
  153. package/dist/ui/PreviewToolbar.d.ts.map +1 -1
  154. package/dist/ui/PreviewToolbar.js +4 -6
  155. package/dist/ui/PreviewToolbar.js.map +1 -1
  156. package/dist/ui/SettingsPanel.d.ts +0 -9
  157. package/dist/ui/SettingsPanel.d.ts.map +1 -1
  158. package/dist/ui/SettingsPanel.js +36 -41
  159. package/dist/ui/SettingsPanel.js.map +1 -1
  160. package/dist/ui/ToolbarButton.d.ts +12 -0
  161. package/dist/ui/ToolbarButton.d.ts.map +1 -0
  162. package/dist/ui/ToolbarButton.js +7 -0
  163. package/dist/ui/ToolbarButton.js.map +1 -0
  164. package/dist/useEvent.d.ts +1 -1
  165. package/dist/useEvent.d.ts.map +1 -1
  166. package/dist/useEvent.js +3 -1
  167. package/dist/useEvent.js.map +1 -1
  168. package/dist/useIsClient.d.ts +5 -0
  169. package/dist/useIsClient.d.ts.map +1 -0
  170. package/dist/useIsClient.js +8 -0
  171. package/dist/useIsClient.js.map +1 -0
  172. package/package.json +15 -4
  173. package/src/OnPageClient.tsx +9 -5
  174. package/src/annotations.ts +1 -1
  175. package/src/context.tsx +53 -10
  176. package/src/dom.ts +17 -18
  177. package/src/editor/FieldHost.tsx +26 -11
  178. package/src/editor/OnPageEditor.tsx +103 -160
  179. package/src/editor/editTarget.ts +41 -0
  180. package/src/editor/useClientDocs.ts +19 -0
  181. package/src/editor/useEditorSession.ts +36 -0
  182. package/src/editor/useFlash.ts +18 -0
  183. package/src/editor/useGhosts.ts +5 -7
  184. package/src/editor/usePageEvents.ts +13 -14
  185. package/src/editor-kit.ts +10 -6
  186. package/src/editors/CheckboxEditor.tsx +11 -12
  187. package/src/editors/ColorEditor.tsx +15 -35
  188. package/src/editors/DateEditor.tsx +18 -37
  189. package/src/editors/InlineText.tsx +4 -5
  190. package/src/editors/NumberEditor.tsx +15 -36
  191. package/src/editors/RelationshipEditor.tsx +18 -36
  192. package/src/editors/SelectEditor.tsx +14 -22
  193. package/src/editors/UploadEditor.tsx +6 -6
  194. package/src/editors/index.ts +56 -16
  195. package/src/editors/kit.tsx +97 -0
  196. package/src/editors/useDebouncedSearch.ts +18 -10
  197. package/src/geometry.ts +62 -0
  198. package/src/hub.ts +5 -5
  199. package/src/index.ts +5 -2
  200. package/src/layout.ts +5 -2
  201. package/src/locators.ts +11 -14
  202. package/src/messages.ts +26 -5
  203. package/src/registry.ts +42 -8
  204. package/src/rowOps.ts +64 -0
  205. package/src/sanitize.ts +6 -0
  206. package/src/settings.ts +49 -0
  207. package/src/styles.ts +5 -4
  208. package/src/targets.ts +1 -1
  209. package/src/types.ts +78 -45
  210. package/src/ui/BlockPicker.tsx +6 -7
  211. package/src/ui/Dialog.tsx +9 -5
  212. package/src/ui/Dock.tsx +67 -50
  213. package/src/ui/Overlay.tsx +75 -102
  214. package/src/ui/PreviewToolbar.tsx +7 -15
  215. package/src/ui/SettingsPanel.tsx +84 -81
  216. package/src/ui/ToolbarButton.tsx +36 -0
  217. package/src/useEvent.ts +4 -2
  218. package/src/useIsClient.ts +15 -0
@@ -0,0 +1,5 @@
1
+ /** False during SSR and hydration, true after: for UI that only exists in the browser. */
2
+ export declare const useIsClient: () => boolean;
3
+ /** Adds the editor's stylesheet to the page while mounted. */
4
+ export declare const useEditorStyles: () => void;
5
+ //# sourceMappingURL=useIsClient.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsClient.d.ts","sourceRoot":"","sources":["../src/useIsClient.ts"],"names":[],"mappings":"AAKA,0FAA0F;AAC1F,eAAO,MAAM,WAAW,QAAO,OAK5B,CAAA;AAEH,8DAA8D;AAC9D,eAAO,MAAM,eAAe,QAAO,IAA2C,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { useEffect, useSyncExternalStore } from 'react';
2
+ import { injectStyles } from "./styles.js";
3
+ const noopSubscribe = () => () => { };
4
+ /** False during SSR and hydration, true after: for UI that only exists in the browser. */
5
+ export const useIsClient = () => useSyncExternalStore(noopSubscribe, () => true, () => false);
6
+ /** Adds the editor's stylesheet to the page while mounted. */
7
+ export const useEditorStyles = () => useEffect(() => injectStyles(), []);
8
+ //# sourceMappingURL=useIsClient.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useIsClient.js","sourceRoot":"","sources":["../src/useIsClient.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,oBAAoB,EAAE,MAAM,OAAO,CAAA;AACvD,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAA;AAE1C,MAAM,aAAa,GAAG,GAAG,EAAE,CAAC,GAAG,EAAE,GAAE,CAAC,CAAA;AAEpC,0FAA0F;AAC1F,MAAM,CAAC,MAAM,WAAW,GAAG,GAAY,EAAE,CACvC,oBAAoB,CAClB,aAAa,EACb,GAAG,EAAE,CAAC,IAAI,EACV,GAAG,EAAE,CAAC,KAAK,CACZ,CAAA;AAEH,8DAA8D;AAC9D,MAAM,CAAC,MAAM,eAAe,GAAG,GAAS,EAAE,CAAC,SAAS,CAAC,GAAG,EAAE,CAAC,YAAY,EAAE,EAAE,EAAE,CAAC,CAAA"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@payload-onpage/react",
3
- "version": "0.2.1",
3
+ "version": "0.3.1",
4
4
  "description": "payload-onpage React editor: overlay, toolbars, field editors, annotations and client mode.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -15,12 +15,23 @@
15
15
  "keywords": [
16
16
  "payload",
17
17
  "payloadcms",
18
+ "payload-cms",
18
19
  "payload-plugin",
19
20
  "nextjs",
21
+ "next.js",
22
+ "app-router",
20
23
  "visual-editing",
21
- "live-preview",
24
+ "visual-editor",
22
25
  "inline-editing",
23
- "cms"
26
+ "front-end-editing",
27
+ "live-preview",
28
+ "draft-preview",
29
+ "preview-links",
30
+ "page-builder",
31
+ "wysiwyg",
32
+ "headless-cms",
33
+ "cms",
34
+ "react"
24
35
  ],
25
36
  "exports": {
26
37
  ".": {
@@ -61,7 +72,7 @@
61
72
  "provenance": true
62
73
  },
63
74
  "dependencies": {
64
- "@payload-onpage/core": "0.2.1"
75
+ "@payload-onpage/core": "0.3.1"
65
76
  },
66
77
  "peerDependencies": {
67
78
  "react": "^19.0.0",
@@ -12,10 +12,11 @@
12
12
  * invisible markers (turn off with `autoMap={false}`).
13
13
  */
14
14
  import { useEvent } from './useEvent.ts'
15
+ import { toRef } from './annotations.ts'
15
16
  import { encodeDocument, refKey, type DocRef } from '@payload-onpage/core'
16
17
  import { useEffect, useMemo, type ReactNode } from 'react'
17
18
  import { editable, editableOff, type Editable } from './annotations.ts'
18
- import { useOnPageOptional } from './context.tsx'
19
+ import { useOnPageOptional, useSessionSnapshot } from './context.tsx'
19
20
 
20
21
  export type OnPageClientProps<T> = {
21
22
  doc: T
@@ -33,9 +34,9 @@ export function OnPageClient<T extends { id?: string | number } | null | undefin
33
34
  const ref = useMemo<DocRef | null>(
34
35
  () =>
35
36
  globalSlug
36
- ? { kind: 'global', slug: globalSlug }
37
- : collection && id != null
38
- ? { kind: 'collection', slug: collection, id }
37
+ ? toRef({ global: globalSlug })
38
+ : collection
39
+ ? toRef({ collection, doc: { id } })
39
40
  : null,
40
41
  [globalSlug, collection, id],
41
42
  )
@@ -51,7 +52,8 @@ export function OnPageClient<T extends { id?: string | number } | null | undefin
51
52
  })
52
53
  useEffect(() => (enabled && ref ? join(ref) : undefined), [enabled, ref, join])
53
54
 
54
- const state = key ? ctx?.snap.docs[key] : undefined
55
+ const snap = useSessionSnapshot(ctx?.session)
56
+ const state = key ? snap.docs[key] : undefined
55
57
  const stateDoc = state?.doc
56
58
  const fields = state?.schema?.fields
57
59
  const autoMap = props.autoMap !== false
@@ -63,5 +65,7 @@ export function OnPageClient<T extends { id?: string | number } | null | undefin
63
65
  () => (enabled && ref ? editable(ref, { enabled: true }) : editableOff),
64
66
  [enabled, ref],
65
67
  )
68
+ // The session's copy started as this `doc` (same document, edited, held as plain JSON),
69
+ // so it is handed back as the page's own type.
66
70
  return <>{render(current as T, e)}</>
67
71
  }
@@ -51,7 +51,7 @@ export const editableOff: Editable = {
51
51
 
52
52
  export type EditableTarget =
53
53
  | DocRef
54
- | { collection: string; doc: { id?: string | number } | null | undefined }
54
+ | { collection: string; doc: { id?: string | number | undefined } | null | undefined }
55
55
  | { global: string }
56
56
 
57
57
  export function toRef(t: EditableTarget): DocRef | null {
package/src/context.tsx CHANGED
@@ -1,23 +1,66 @@
1
1
  'use client'
2
2
 
3
- import { createContext, useContext, useSyncExternalStore } from 'react'
3
+ import { createContext, useContext, useSyncExternalStore, type ReactNode } from 'react'
4
+ import type { EditorSession, SessionSnapshot } from '@payload-onpage/core'
4
5
  import { hub } from './hub.ts'
5
- import type { OnPageContextValue } from './types.ts'
6
+ import type { OnPageApi, OnPageContextValue, OnPageState } from './types.ts'
6
7
 
7
- export const OnPageContext = createContext<OnPageContextValue | null>(null)
8
+ const ApiContext = createContext<OnPageApi | null>(null)
9
+ const StateContext = createContext<OnPageState | null>(null)
8
10
 
9
- /** The editor, for code rendered inside it (field editors, panels). */
11
+ /** Provides the editor to everything it renders (two contexts: the stable API and the changing state). */
12
+ export function OnPageProvider({
13
+ api,
14
+ state,
15
+ children,
16
+ }: {
17
+ api: OnPageApi
18
+ state: OnPageState
19
+ children: ReactNode
20
+ }) {
21
+ return (
22
+ <ApiContext.Provider value={api}>
23
+ <StateContext.Provider value={state}>{children}</StateContext.Provider>
24
+ </ApiContext.Provider>
25
+ )
26
+ }
27
+
28
+ /** The editor, for code rendered inside it (field editors, panels): its API and state. */
10
29
  export function useOnPage(): OnPageContextValue {
11
- const ctx = useContext(OnPageContext)
12
- if (!ctx) throw new Error('useOnPage must be used inside <OnPageEditor>')
13
- return ctx
30
+ const api = useContext(ApiContext)
31
+ const state = useContext(StateContext)
32
+ if (!api || !state) throw new Error('useOnPage must be used inside <OnPageEditor>')
33
+ return { ...api, ...state }
34
+ }
35
+
36
+ /** Only the editor's API (session, actions): re-renders when the editor is reconfigured, not on every edit. */
37
+ export function useOnPageApi(): OnPageApi {
38
+ const api = useContext(ApiContext)
39
+ if (!api) throw new Error('useOnPageApi must be used inside <OnPageEditor>')
40
+ return api
14
41
  }
15
42
 
16
43
  const getNull = () => null
17
44
 
18
- /** The editor, from inside it or anywhere on the page; null when no editor is mounted (public pages). */
19
- export function useOnPageOptional(): OnPageContextValue | null {
20
- const inside = useContext(OnPageContext)
45
+ /**
46
+ * The editor's API from inside it or anywhere on the page; null when no editor
47
+ * is mounted (public pages). Read documents with `useSessionSnapshot(api?.session)`.
48
+ */
49
+ export function useOnPageOptional(): OnPageApi | null {
50
+ const inside = useContext(ApiContext)
21
51
  const outside = useSyncExternalStore(hub.subscribe, hub.get, getNull)
22
52
  return inside ?? outside
23
53
  }
54
+
55
+ const EMPTY: SessionSnapshot = { docs: {}, canUndo: false, canRedo: false, busy: false }
56
+ const noopSubscribe = () => () => {}
57
+ const getEmpty = () => EMPTY
58
+
59
+ /** A session's documents, re-rendering when they change (an empty snapshot without a session). */
60
+ export function useSessionSnapshot(session: EditorSession | null | undefined): SessionSnapshot {
61
+ return useSyncExternalStore(
62
+ session?.subscribe ?? noopSubscribe,
63
+ session?.getSnapshot ?? getEmpty,
64
+ getEmpty,
65
+ )
66
+ }
package/src/dom.ts CHANGED
@@ -1,9 +1,21 @@
1
1
  /** DOM helpers shared by the editor UI (browser-only, no React). */
2
2
 
3
- /** Attribute that hides a page element while an editor or ghost stands in for it. */
4
- export const HIDDEN = 'data-onpage-hidden'
5
- /** Marks the editor's own UI, which locators and click handling ignore. */
6
- export const UI = 'data-onpage-ui'
3
+ /** Every DOM attribute the editor sets (annotations, which sites write, are `ATTR`). */
4
+ export const DOM_ATTR = {
5
+ /** Hides a page element while an editor or ghost stands in for it. */
6
+ hidden: 'data-onpage-hidden',
7
+ /** Marks the editor's own UI, which locators and click handling ignore. */
8
+ ui: 'data-onpage-ui',
9
+ /** A page target the editor made keyboard-focusable. */
10
+ focusable: 'data-onpage-focusable',
11
+ /** On <html> while editing. */
12
+ editing: 'data-onpage-editing',
13
+ /** The editor's injected stylesheet. */
14
+ styles: 'data-onpage-styles',
15
+ } as const
16
+
17
+ export const HIDDEN = DOM_ATTR.hidden
18
+ export const UI = DOM_ATTR.ui
7
19
 
8
20
  /** Computed styles copied onto an inline editor so it looks like the element it replaces. */
9
21
  export const TYPOGRAPHY = [
@@ -94,20 +106,7 @@ export function placeCaret(el: HTMLElement, point?: { x: number; y: number }): v
94
106
  sel.addRange(range)
95
107
  }
96
108
 
97
- /** Position a fixed popover below (or above) an element, kept inside the viewport. */
98
- export function popoverPosition(
99
- el: Element | null,
100
- size = { width: 300, height: 320 },
101
- ): { top: number; left: number } {
102
- const vw = window.innerWidth
103
- const vh = window.innerHeight
104
- if (!el) return { top: 80, left: Math.max(8, vw / 2 - size.width / 2) }
105
- const r = el.getBoundingClientRect()
106
- const below = r.bottom + 8
107
- const top = below + size.height < vh ? below : Math.max(8, r.top - 8 - size.height)
108
- const left = Math.min(Math.max(8, r.left), vw - size.width - 8)
109
- return { top, left }
110
- }
109
+ export { popoverPosition } from './geometry.ts'
111
110
 
112
111
  const FOCUSABLE =
113
112
  'a[href],button:not([disabled]),input:not([disabled]),select:not([disabled]),textarea:not([disabled]),[tabindex]:not([tabindex="-1"])'
@@ -1,19 +1,34 @@
1
1
  'use client'
2
2
 
3
- import { fieldAtPath, getAt } from '@payload-onpage/core'
3
+ import { fieldAtPath, getAt, isReady, type ReadyDocumentState } from '@payload-onpage/core'
4
4
  import { Suspense, useCallback } from 'react'
5
5
  import { useOnPage } from '../context.tsx'
6
- import type { FieldEditorProps } from '../types.ts'
6
+ import { canEdit } from '../registry.ts'
7
+ import type { EditingState, FieldEditorDef, FieldEditorProps } from '../types.ts'
7
8
 
8
9
  /** Renders the open field editor, if it belongs to this layer. */
9
10
  export function FieldHost({ mode }: { mode: 'inline' | 'popover' }) {
10
- const { editing, editors, session, snap, targets, setEditing, addGhost, isLive, t } = useOnPage()
11
- const def = editing ? editors[editing.editor] : undefined
12
- const state = editing ? snap.docs[editing.key] : undefined
13
- const key = editing?.key ?? ''
14
- const path = editing?.path ?? ''
11
+ const { editing, editors, snap } = useOnPage()
12
+ if (!editing) return null
13
+ const def = editors[editing.editor]
14
+ const state = snap.docs[editing.key]
15
+ if (!def || def.mode !== mode || !isReady(state)) return null
16
+ return <OpenEditor editing={editing} def={def} state={state} />
17
+ }
18
+
19
+ /** The editor for one field of a loaded document. */
20
+ function OpenEditor({
21
+ editing,
22
+ def,
23
+ state,
24
+ }: {
25
+ editing: EditingState
26
+ def: FieldEditorDef
27
+ state: ReadyDocumentState
28
+ }) {
29
+ const { session, targets, setEditing, addGhost, isLive, t } = useOnPage()
30
+ const { key, path, el: clicked } = editing
15
31
  const live = isLive(key)
16
- const clicked = editing?.el
17
32
 
18
33
  const element = useCallback(
19
34
  () => (clicked?.isConnected ? clicked : (targets.find(key, path)?.el ?? null)),
@@ -39,10 +54,10 @@ export function FieldHost({ mode }: { mode: 'inline' | 'popover' }) {
39
54
  [addGhost, key, path],
40
55
  )
41
56
 
42
- if (!editing || !def || def.mode !== mode || !state?.schema || !state.doc) return null
43
57
  const field = fieldAtPath(state.schema.fields, state.doc, path)?.field
44
- if (!field) return null
58
+ if (!field || !canEdit(def, field)) return null
45
59
  const Editor = def.component
60
+ const raw = getAt(state.doc, path)
46
61
  // Editors may be lazy (the rich-text editor is): render nothing until they've loaded.
47
62
  return (
48
63
  <Suspense fallback={null}>
@@ -52,7 +67,7 @@ export function FieldHost({ mode }: { mode: 'inline' | 'popover' }) {
52
67
  path={path}
53
68
  field={field}
54
69
  element={element}
55
- value={getAt(state.doc, path)}
70
+ value={def.normalize ? def.normalize(raw, field) : raw}
56
71
  commit={commit}
57
72
  preview={preview}
58
73
  close={close}