@blinkk/root-cms 3.0.6 → 3.1.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 (146) hide show
  1. package/README.md +77 -0
  2. package/dist/app.js +6 -4
  3. package/dist/browser-client/browser-client.d.ts +148 -0
  4. package/dist/browser-client/embed-window.d.ts +66 -0
  5. package/dist/browser-client/emitter.d.ts +11 -0
  6. package/dist/browser-client.js +377 -0
  7. package/dist/{chunk-RXWDB3K4.js → chunk-4VZTYUQ7.js} +1 -1
  8. package/dist/{chunk-N4K6ICFR.js → chunk-AFBSITEO.js} +4 -6
  9. package/dist/{chunk-PNLLZXVY.js → chunk-AJDMNTZQ.js} +2 -2
  10. package/dist/{chunk-7L5OI5ES.js → chunk-GXV4UQ6Z.js} +8 -4
  11. package/dist/chunk-J72JK4KR.js +8 -0
  12. package/dist/cli.js +2 -1
  13. package/dist/client.js +2 -1
  14. package/dist/core/client.d.ts +5 -0
  15. package/dist/core.js +4 -2
  16. package/dist/functions.js +4 -3
  17. package/dist/plugin.js +5 -4
  18. package/dist/shared/embed-protocol.d.ts +72 -0
  19. package/dist/ui/AIPage-EL6DQYII.js +1 -0
  20. package/dist/ui/AssetsPage-5S6PNMF6.js +1 -0
  21. package/dist/ui/CollectionPage-CU3VDEXA.js +1 -0
  22. package/dist/ui/ComparePage-FTZLYO2O.js +1 -0
  23. package/dist/ui/DataPage-XIXFKXDN.js +1 -0
  24. package/dist/ui/DataSourcePage-ORIEQH3Y.js +1 -0
  25. package/dist/ui/DocTranslationsPage-4OVBIPSG.js +1 -0
  26. package/dist/ui/DocumentPage-3JPFJF3E.js +1 -0
  27. package/dist/ui/EditDataSourcePage-HIGZZQJC.js +1 -0
  28. package/dist/ui/EditReleasePage-KUHBU2HQ.js +1 -0
  29. package/dist/ui/EmbeddedAIPage-CJWZE5FC.js +1 -0
  30. package/dist/ui/EmbeddedDocumentPage-6XQHYIAO.js +1 -0
  31. package/dist/ui/{ImageEditorDialog-QEULJAT2.js → ImageEditorDialog-GGS4VFEA.js} +2 -2
  32. package/dist/ui/LogsPage-4DVGYXZY.js +1 -0
  33. package/dist/ui/NewDataSourcePage-7TUUE6ZL.js +1 -0
  34. package/dist/ui/NewReleasePage-6UT36HDW.js +1 -0
  35. package/dist/ui/NotFoundPage-AW6UC6II.js +1 -0
  36. package/dist/ui/ProjectPage-US5FJGFK.js +1 -0
  37. package/dist/ui/ReleasePage-W2R3DIFX.js +1 -0
  38. package/dist/ui/ReleasesPage-HBBUGE7D.js +1 -0
  39. package/dist/ui/SettingsPage-7JQQILUV.js +1 -0
  40. package/dist/ui/SidebarToolsPage-MIYXKHF2.js +1 -0
  41. package/dist/ui/TaskPage-JYUQEGRB.js +1 -0
  42. package/dist/ui/TasksPage-4VFQF243.js +1 -0
  43. package/dist/ui/TranslationsArbPage-AJLM5VSD.js +2 -0
  44. package/dist/ui/TranslationsEditPage-RTTGTIGI.js +1 -0
  45. package/dist/ui/{TranslationsPage-OBN6CQZZ.js → TranslationsPage-HPYAJQ4Q.js} +2 -2
  46. package/dist/ui/{chunk-WKOX3VFN.js → chunk-2KSMCQ6H.js} +2 -2
  47. package/dist/ui/{chunk-V7RAXP2W.js → chunk-3E6CASIN.js} +1 -1
  48. package/dist/ui/{chunk-N4DFAIC5.js → chunk-3T4ZWQR4.js} +1 -1
  49. package/dist/ui/chunk-4ISFTE2G.js +5 -0
  50. package/dist/ui/chunk-5AVRMJVW.js +1 -0
  51. package/dist/ui/chunk-764W6634.js +1 -0
  52. package/dist/ui/chunk-76ZGLKLT.js +1 -0
  53. package/dist/ui/{chunk-SKEP5WYH.js → chunk-7SK47HHZ.js} +3 -3
  54. package/dist/ui/chunk-AZCGXQTL.js +1 -0
  55. package/dist/ui/{chunk-KYKFNAX4.js → chunk-B6M55OFE.js} +1 -1
  56. package/dist/ui/{chunk-2225WAJ2.js → chunk-CAY5NM4Z.js} +1 -1
  57. package/dist/ui/{chunk-4KE2SZKH.js → chunk-D3UIZHRK.js} +1 -1
  58. package/dist/ui/{chunk-NMBXHKIJ.js → chunk-E76ECIOX.js} +1 -1
  59. package/dist/ui/chunk-FBSBL476.js +1 -0
  60. package/dist/ui/chunk-FFHV5S6A.js +1 -0
  61. package/dist/ui/{chunk-NQB37NMX.js → chunk-FJLNXXE3.js} +1 -1
  62. package/dist/ui/chunk-FSVNOZ6F.js +1 -0
  63. package/dist/ui/{chunk-3IDOZ4MG.js → chunk-GDXNSFLE.js} +1 -1
  64. package/dist/ui/{chunk-EMYXSVTD.js → chunk-GQ5IRK43.js} +1 -1
  65. package/dist/ui/{chunk-UHWNLNP4.js → chunk-GU24U5RM.js} +1 -1
  66. package/dist/ui/{chunk-KPJ53X55.js → chunk-HARRKJFZ.js} +1 -1
  67. package/dist/ui/{chunk-6E7PGMZX.js → chunk-HO3W4RVG.js} +1 -1
  68. package/dist/ui/{chunk-CVGYBXVH.js → chunk-J5E457OI.js} +1 -1
  69. package/dist/ui/{chunk-W3HIZQ4Z.js → chunk-JS6TK4OP.js} +1 -1
  70. package/dist/ui/{chunk-BEDUTYQP.js → chunk-JTIQBW33.js} +1 -1
  71. package/dist/ui/{chunk-ORXEMIQC.js → chunk-KRNQ6L35.js} +1 -1
  72. package/dist/ui/chunk-LROR6N3B.js +7 -0
  73. package/dist/ui/{chunk-4CUWZQXT.js.LEGAL.txt → chunk-LROR6N3B.js.LEGAL.txt} +6 -1
  74. package/dist/ui/{chunk-AOCBDKLV.js → chunk-M3T56ZRP.js} +1 -1
  75. package/dist/ui/chunk-MXGHX55S.js +141 -0
  76. package/dist/ui/{chunk-HYARD7BK.js → chunk-MZTOPPWQ.js} +1 -1
  77. package/dist/ui/{chunk-RQKL4KXY.js → chunk-N3JSLTMV.js} +1 -1
  78. package/dist/ui/{chunk-D37DCENS.js → chunk-NSOTG64N.js} +1 -1
  79. package/dist/ui/{chunk-N5Y5OWTV.js → chunk-O5P3CPJ4.js} +1 -1
  80. package/dist/ui/chunk-OWQL5DVE.js +1 -0
  81. package/dist/ui/{chunk-SEUH7LN2.js → chunk-OXVYTSIZ.js} +1 -1
  82. package/dist/ui/chunk-P2Z66DHV.js +1 -0
  83. package/dist/ui/{chunk-TZEZEMRQ.js → chunk-QEE7XS6O.js} +1 -1
  84. package/dist/ui/{chunk-AXETBZ6T.js → chunk-QHJOTXYB.js} +1 -1
  85. package/dist/ui/{chunk-4IXGYXA2.js → chunk-R6B3W5AJ.js} +1 -1
  86. package/dist/ui/{chunk-T47V6AGW.js → chunk-RAXWL656.js} +1 -1
  87. package/dist/ui/chunk-SRX7EWL4.js +1 -0
  88. package/dist/ui/{chunk-OVRTVMO5.js → chunk-TDUBSLEC.js} +1 -1
  89. package/dist/ui/chunk-TFB3VGWP.js +1 -0
  90. package/dist/ui/{chunk-6MOPIV5T.js → chunk-U7PWJV6F.js} +1 -1
  91. package/dist/ui/{chunk-GDSSFO5R.js → chunk-UUVAD4U4.js} +1 -1
  92. package/dist/ui/chunk-V4CTOC6E.js +1 -0
  93. package/dist/ui/{chunk-EGEB6N4J.js → chunk-VD6G5NV4.js} +11 -11
  94. package/dist/ui/chunk-W5DOO6ID.js +1 -0
  95. package/dist/ui/{chunk-3XYCOR6Z.js → chunk-WFQU7V3W.js} +1 -1
  96. package/dist/ui/{chunk-XYBHH5L4.js → chunk-WKF7CSWS.js} +1 -1
  97. package/dist/ui/{chunk-DXKEDMEI.js → chunk-WXT7LTQM.js} +1 -1
  98. package/dist/ui/{chunk-6GPTGBA7.js → chunk-XT5FNJCX.js} +1 -1
  99. package/dist/ui/chunk-YLSRRNSE.js +1 -0
  100. package/dist/ui/{chunk-UFQSZ6J2.js → chunk-ZN2Y5OUF.js} +1 -1
  101. package/dist/ui/gcs-ECSDFCIU.js +1 -0
  102. package/dist/ui/ui.css +1 -1
  103. package/dist/ui/ui.js +1 -1
  104. package/package.json +7 -3
  105. package/dist/ui/AIPage-JPZ4QUB4.js +0 -1
  106. package/dist/ui/AssetsPage-GDJMKS3M.js +0 -1
  107. package/dist/ui/CollectionPage-5DVXJEET.js +0 -1
  108. package/dist/ui/ComparePage-LIXA6G66.js +0 -1
  109. package/dist/ui/DataPage-AP4Z5JPS.js +0 -1
  110. package/dist/ui/DataSourcePage-WO3ZKMFI.js +0 -1
  111. package/dist/ui/DocTranslationsPage-QKYFIVHP.js +0 -1
  112. package/dist/ui/DocumentPage-ON43I5KM.js +0 -1
  113. package/dist/ui/EditDataSourcePage-XVMEV2LJ.js +0 -1
  114. package/dist/ui/EditReleasePage-A2NB5TFG.js +0 -1
  115. package/dist/ui/LogsPage-4LZJX3O4.js +0 -1
  116. package/dist/ui/NewDataSourcePage-4YWX7IOG.js +0 -1
  117. package/dist/ui/NewReleasePage-HWLVTEXX.js +0 -1
  118. package/dist/ui/NotFoundPage-72AXQAYX.js +0 -1
  119. package/dist/ui/ProjectPage-L4GYDIA5.js +0 -1
  120. package/dist/ui/ReleasePage-43BSWL5B.js +0 -1
  121. package/dist/ui/ReleasesPage-CJXAZBP6.js +0 -1
  122. package/dist/ui/SettingsPage-SYXAMKSF.js +0 -1
  123. package/dist/ui/SidebarToolsPage-OFF5BOXP.js +0 -1
  124. package/dist/ui/TaskPage-HBDLHFI5.js +0 -1
  125. package/dist/ui/TasksPage-OLCDTQNB.js +0 -1
  126. package/dist/ui/TranslationsArbPage-E3NCMAMS.js +0 -2
  127. package/dist/ui/TranslationsEditPage-IWOI7Y5G.js +0 -1
  128. package/dist/ui/chunk-32LRQ5MJ.js +0 -1
  129. package/dist/ui/chunk-3HVDIPSS.js +0 -1
  130. package/dist/ui/chunk-4CUWZQXT.js +0 -7
  131. package/dist/ui/chunk-DUJYIOLE.js +0 -5
  132. package/dist/ui/chunk-IDYKCAAL.js +0 -1
  133. package/dist/ui/chunk-IKED2IGU.js +0 -1
  134. package/dist/ui/chunk-J3EGGI57.js +0 -1
  135. package/dist/ui/chunk-MZP6RIG2.js +0 -1
  136. package/dist/ui/chunk-NQ5ARARP.js +0 -1
  137. package/dist/ui/chunk-RI2JWWU3.js +0 -141
  138. package/dist/ui/chunk-RPOP6MVJ.js +0 -1
  139. package/dist/ui/chunk-TDKVKTW4.js +0 -1
  140. package/dist/ui/chunk-UA7UCTX5.js +0 -1
  141. package/dist/ui/gcs-L33FIRZU.js +0 -1
  142. /package/dist/ui/{ImageEditorDialog-QEULJAT2.js.LEGAL.txt → ImageEditorDialog-GGS4VFEA.js.LEGAL.txt} +0 -0
  143. /package/dist/ui/{chunk-SKEP5WYH.js.LEGAL.txt → chunk-7SK47HHZ.js.LEGAL.txt} +0 -0
  144. /package/dist/ui/{chunk-NMBXHKIJ.js.LEGAL.txt → chunk-E76ECIOX.js.LEGAL.txt} +0 -0
  145. /package/dist/ui/{chunk-RI2JWWU3.js.LEGAL.txt → chunk-MXGHX55S.js.LEGAL.txt} +0 -0
  146. /package/dist/ui/{chunk-EGEB6N4J.js.LEGAL.txt → chunk-VD6G5NV4.js.LEGAL.txt} +0 -0
package/README.md CHANGED
@@ -92,3 +92,80 @@ This is convenient for granting access to a whole workspace, but be aware:
92
92
  that grants access to anyone with a Google account.
93
93
  - Prefer explicit per-email grants for `ADMIN` and `EDITOR`; reserve the
94
94
  wildcard for `CONTRIBUTOR` or `VIEWER` if you use it at all.
95
+
96
+ # Embedding the CMS (`@blinkk/root-cms/browser-client`)
97
+
98
+ `@blinkk/root-cms/browser-client` is a dependency-free, framework-agnostic
99
+ browser library for embedding Root CMS into another site. It wraps the
100
+ underlying iframe/postMessage wiring behind a typed API, so the wire protocol
101
+ can evolve without breaking your integration.
102
+
103
+ For pop-up/iframe embedding (the doc editor and Root AI), the embedding page's
104
+ origin must be allowlisted in the CMS plugin config:
105
+
106
+ ```ts
107
+ // root.config.ts
108
+ cmsPlugin({
109
+ // ...
110
+ allowedIframeOrigins: ['https://app.example.com'],
111
+ });
112
+ ```
113
+
114
+ ## Headless doc editor (pop-up or iframe)
115
+
116
+ Opens `/cms/embed/content/<collection>/<slug>`, a minimal version of the doc
117
+ editor with explicit (non-auto) saving. In `popup` mode, call from a user
118
+ gesture (e.g. a click handler) to avoid pop-up blockers. Closing the editor
119
+ discards unsaved changes.
120
+
121
+ ```ts
122
+ import {RootCMSBrowserClient} from '@blinkk/root-cms/browser-client';
123
+
124
+ const root = new RootCMSBrowserClient({cmsOrigin: 'https://cms.example.com'});
125
+
126
+ const editor = root.openEditor('Pages/about', {
127
+ mode: 'popup', // or 'iframe' (pass `container`)
128
+ deeplink: 'hero.title', // optional: field to scroll to on load
129
+ });
130
+ editor.on('ready', () => console.log('editor loaded'));
131
+ editor.on('saved', () => location.reload());
132
+ editor.on('published', () => editor.closeAndReload());
133
+ editor.on('close', () => console.log('editor closed'));
134
+ editor.focusField('hero.image'); // scroll the editor to a field
135
+ editor.reload();
136
+ editor.close(); // or editor.closeAndReload()
137
+ ```
138
+
139
+ ## Headless Root AI (pop-up or iframe)
140
+
141
+ Opens `/cms/embed/ai`, the compact Root AI chat, optionally with a doc as
142
+ context:
143
+
144
+ ```ts
145
+ const ai = root.openRootAI({docId: 'Pages/about', mode: 'popup'});
146
+ ai.on('ready', () => console.log('chat loaded'));
147
+ ai.close();
148
+ ```
149
+
150
+ ## Field focus from the in-CMS preview pane ("click to edit")
151
+
152
+ When your site is rendered inside the CMS preview pane, use a
153
+ `PreviewConnection` to focus editor fields from the page and to highlight page
154
+ nodes when the editor requests it. The preview pane is same-origin with the
155
+ CMS, so no config is needed and the connection is inert when the page is not
156
+ embedded:
157
+
158
+ ```ts
159
+ import {RootCMSBrowserClient} from '@blinkk/root-cms/browser-client';
160
+
161
+ const preview = RootCMSBrowserClient.connectPreview();
162
+ if (preview.isEmbedded) {
163
+ // "Click to edit": focus the field in the doc editor.
164
+ el.addEventListener('click', () => preview.focusField('hero.title'));
165
+ // Highlight page nodes when the editor hovers/focuses a field.
166
+ preview.on('highlight', ({deepKey, scroll}) => {
167
+ clearHighlights();
168
+ if (deepKey) highlightNode(deepKey, {scroll});
169
+ });
170
+ }
171
+ ```
package/dist/app.js CHANGED
@@ -1,9 +1,10 @@
1
1
  import {
2
2
  getServerVersion,
3
3
  serializeAiConfig
4
- } from "./chunk-7L5OI5ES.js";
5
- import "./chunk-RXWDB3K4.js";
6
- import "./chunk-N4K6ICFR.js";
4
+ } from "./chunk-GXV4UQ6Z.js";
5
+ import "./chunk-4VZTYUQ7.js";
6
+ import "./chunk-AFBSITEO.js";
7
+ import "./chunk-J72JK4KR.js";
7
8
  import {
8
9
  getCollectionSchema,
9
10
  getProjectSchemas
@@ -150,7 +151,8 @@ async function renderApp(req, res, options) {
150
151
  hasExport: isFunction(t.onExport)
151
152
  })),
152
153
  defaultOneOfVariant: cmsConfig.defaultOneOfVariant || "dropdown",
153
- excludeLocalesFromTranslations: cmsConfig.excludeLocalesFromTranslations || []
154
+ excludeLocalesFromTranslations: cmsConfig.excludeLocalesFromTranslations || [],
155
+ allowedIframeOrigins: cmsConfig.allowedIframeOrigins || []
154
156
  };
155
157
  const projectName = cmsConfig.name || cmsConfig.id || "";
156
158
  const title = getCmsTitle(projectName, cmsConfig.minimalBranding);
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Browser client for embedding Root CMS into another site, exported as
3
+ * `@blinkk/root-cms/browser-client`. Framework-agnostic and dependency-free.
4
+ *
5
+ * Covers three scenarios:
6
+ * - Opening the headless doc editor in a pop-up or iframe
7
+ * ({@link RootCMSBrowserClient.openEditor}).
8
+ * - Opening the headless Root AI chat in a pop-up or iframe
9
+ * ({@link RootCMSBrowserClient.openRootAI}).
10
+ * - Communicating with the doc editor from a site rendered inside the in-CMS
11
+ * preview pane ({@link RootCMSBrowserClient.connectPreview}).
12
+ *
13
+ * The first two require the embedding page's origin to be listed in the
14
+ * `allowedIframeOrigins` option of the CMS plugin config.
15
+ */
16
+ import { EmbedWindow, EmbedWindowEvents } from './embed-window.js';
17
+ export { SaveState } from '../shared/embed-protocol.js';
18
+ export type { HighlightNodeMessage, RootEmbedMessage, ScrollToDeeplinkMessage, } from '../shared/embed-protocol.js';
19
+ export type { EmbedWindowEvents } from './embed-window.js';
20
+ export interface RootCMSBrowserClientOptions {
21
+ /** Origin of the Root CMS server, e.g. `https://cms.example.com`. */
22
+ cmsOrigin: string;
23
+ }
24
+ export interface OpenEditorOptions {
25
+ /** How to open the editor. Defaults to `popup`. */
26
+ mode?: 'popup' | 'iframe';
27
+ /** Container the iframe is appended to. Required when mode is `iframe`. */
28
+ container?: HTMLElement;
29
+ /** Deep key of a field to scroll to on load, e.g. `hero.title`. */
30
+ deeplink?: string;
31
+ /** Pop-up window width. Defaults to 480. */
32
+ width?: number;
33
+ /** Pop-up window height. Defaults to 720. */
34
+ height?: number;
35
+ }
36
+ export interface OpenRootAIOptions extends Omit<OpenEditorOptions, 'deeplink'> {
37
+ /** Doc to provide as context to the AI chat, e.g. `Pages/about`. */
38
+ docId?: string;
39
+ }
40
+ /** Events emitted by {@link EmbeddedEditor}. */
41
+ export type EditorEvents = EmbedWindowEvents;
42
+ /** Events emitted by {@link EmbeddedAI}. */
43
+ export type AIEvents = Pick<EmbedWindowEvents, 'ready' | 'error' | 'close'>;
44
+ /** Event emitted when the doc editor requests a field highlight. */
45
+ export interface PreviewHighlightEvent {
46
+ /** Deep key of the field to highlight, or `null` to clear highlights. */
47
+ deepKey: string | null;
48
+ /** Whether to scroll the highlighted node into view. */
49
+ scroll: boolean;
50
+ }
51
+ /**
52
+ * Handle to a headless doc editor opened via {@link RootCMSBrowserClient.openEditor}.
53
+ *
54
+ * Note: the embedded editor does not autosave; closing it discards any
55
+ * unsaved changes.
56
+ */
57
+ export declare class EmbeddedEditor {
58
+ /** The doc being edited, e.g. `Pages/about`. */
59
+ readonly docId: string;
60
+ private embedWindow;
61
+ constructor(docId: string, embedWindow: EmbedWindow);
62
+ /** The iframe element (iframe mode only). */
63
+ get element(): HTMLIFrameElement | null;
64
+ /** The pop-up window (popup mode only). */
65
+ get window(): Window | null;
66
+ /** Subscribes to an editor event. Returns an unsubscribe function. */
67
+ on<K extends keyof EditorEvents>(type: K, cb: (payload: EditorEvents[K]) => void): () => void;
68
+ /** Scrolls the editor to (focuses) a field, e.g. `hero.title`. */
69
+ focusField(deepKey: string): void;
70
+ /** Reloads the embedded editor. */
71
+ reload(): void;
72
+ /** Closes the embedded editor, discarding any unsaved changes. */
73
+ close(): void;
74
+ /** Closes the embedded editor and reloads the host page. */
75
+ closeAndReload(): void;
76
+ }
77
+ /** Handle to a headless Root AI chat opened via {@link RootCMSBrowserClient.openRootAI}. */
78
+ export declare class EmbeddedAI {
79
+ private embedWindow;
80
+ constructor(embedWindow: EmbedWindow);
81
+ /** The iframe element (iframe mode only). */
82
+ get element(): HTMLIFrameElement | null;
83
+ /** The pop-up window (popup mode only). */
84
+ get window(): Window | null;
85
+ /** Subscribes to an event. Returns an unsubscribe function. */
86
+ on<K extends keyof AIEvents>(type: K, cb: (payload: AIEvents[K]) => void): () => void;
87
+ /** Reloads the embedded chat (starts a fresh chat). */
88
+ reload(): void;
89
+ /** Closes the embedded chat. */
90
+ close(): void;
91
+ /** Closes the embedded chat and reloads the host page. */
92
+ closeAndReload(): void;
93
+ }
94
+ /**
95
+ * Channel between a site rendered inside the in-CMS preview pane and the doc
96
+ * editor that frames it. The preview iframe is same-origin with the CMS, so
97
+ * no configuration is needed. Safe to construct unconditionally: outside the
98
+ * preview pane the connection is inert (`isEmbedded` is false).
99
+ */
100
+ export declare class PreviewConnection {
101
+ /** Whether this page is rendered inside the in-CMS preview pane. */
102
+ readonly isEmbedded: boolean;
103
+ private emitter;
104
+ constructor();
105
+ /**
106
+ * Requests that the doc editor scroll to (focus) a field, e.g.
107
+ * `hero.title`. Used to implement "click to edit".
108
+ */
109
+ focusField(deepKey: string): void;
110
+ /**
111
+ * Subscribes to field highlight requests from the doc editor (sent when the
112
+ * user hovers/focuses a field). Returns an unsubscribe function.
113
+ */
114
+ on(type: 'highlight', cb: (event: PreviewHighlightEvent) => void): () => void;
115
+ /** Removes the message listener and all subscriptions. */
116
+ disconnect(): void;
117
+ private onMessage;
118
+ }
119
+ /** Client for embedding Root CMS into another site. */
120
+ export declare class RootCMSBrowserClient {
121
+ /** Normalized origin of the Root CMS server. */
122
+ readonly cmsOrigin: string;
123
+ constructor(options: RootCMSBrowserClientOptions);
124
+ /**
125
+ * Opens the headless doc editor for a doc (e.g. `Pages/about`) in a pop-up
126
+ * or iframe. In popup mode, call from a user gesture (e.g. a click handler)
127
+ * to avoid pop-up blockers.
128
+ *
129
+ * The slug portion of the docId is normalized the same way the CMS
130
+ * normalizes slugs, so a URL-like path also works, e.g. `Pages/about/foo`
131
+ * -> `Pages/about--foo`.
132
+ */
133
+ openEditor(docId: string, options?: OpenEditorOptions): EmbeddedEditor;
134
+ /**
135
+ * Opens the headless Root AI chat in a pop-up or iframe, optionally with a
136
+ * doc as context. In popup mode, call from a user gesture (e.g. a click
137
+ * handler) to avoid pop-up blockers.
138
+ */
139
+ openRootAI(options?: OpenRootAIOptions): EmbeddedAI;
140
+ /**
141
+ * Connects to the doc editor from a site rendered inside the in-CMS preview
142
+ * pane, enabling "click to edit" ({@link PreviewConnection.focusField}) and
143
+ * field highlighting (`on('highlight', ...)`).
144
+ */
145
+ static connectPreview(): PreviewConnection;
146
+ /** Returns whether this page is rendered inside the in-CMS preview pane. */
147
+ static isInPreviewIframe(): boolean;
148
+ }
@@ -0,0 +1,66 @@
1
+ import { SaveState } from '../shared/embed-protocol.js';
2
+ /** Events emitted by an embedded CMS window (iframe or pop-up). */
3
+ export interface EmbedWindowEvents {
4
+ /** The embedded page finished loading. */
5
+ ready: {
6
+ docId?: string;
7
+ };
8
+ /** The doc was saved. */
9
+ saved: {
10
+ docId?: string;
11
+ saveState?: SaveState;
12
+ };
13
+ /** The doc was published. */
14
+ published: {
15
+ docId?: string;
16
+ publishedAt?: number;
17
+ };
18
+ /** An error occurred (e.g. the pop-up was blocked). */
19
+ error: {
20
+ error?: string;
21
+ };
22
+ /** The embedded window was closed (via `close()` or by the user). */
23
+ close: void;
24
+ }
25
+ export interface EmbedWindowOptions {
26
+ /** How to open the embedded page. Defaults to `popup`. */
27
+ mode?: 'popup' | 'iframe';
28
+ /** Container the iframe is appended to. Required when mode is `iframe`. */
29
+ container?: HTMLElement;
30
+ /** Pop-up window width. Defaults to 480. */
31
+ width?: number;
32
+ /** Pop-up window height. Defaults to 720. */
33
+ height?: number;
34
+ }
35
+ /**
36
+ * Internal plumbing shared by the embedded doc editor and Root AI handles.
37
+ * Owns the iframe-or-popup lifecycle: opening, message routing (with origin
38
+ * and source validation), outbound message buffering until `ready`, reload,
39
+ * and close detection.
40
+ */
41
+ export declare class EmbedWindow {
42
+ /** The iframe element (iframe mode only). */
43
+ readonly element: HTMLIFrameElement | null;
44
+ /** The pop-up window (popup mode only). */
45
+ readonly window: Window | null;
46
+ private url;
47
+ private cmsOrigin;
48
+ private emitter;
49
+ private ready;
50
+ private closed;
51
+ private outbox;
52
+ private pollTimer;
53
+ constructor(url: string, cmsOrigin: string, options?: EmbedWindowOptions);
54
+ on<K extends keyof EmbedWindowEvents>(type: K, cb: (payload: EmbedWindowEvents[K]) => void): () => void;
55
+ /**
56
+ * Posts a message to the embedded window, targeted at the CMS origin.
57
+ * Messages are buffered until the embedded page signals `ready`.
58
+ */
59
+ post(message: unknown): void;
60
+ /** Reloads the embedded page. */
61
+ reload(): void;
62
+ /** Closes the embedded window. Safe to call multiple times. */
63
+ close(): void;
64
+ private contentWindow;
65
+ private onMessage;
66
+ }
@@ -0,0 +1,11 @@
1
+ type Listener<T> = (payload: T) => void;
2
+ /** Minimal typed event emitter. */
3
+ export declare class Emitter<Events extends Record<string, any>> {
4
+ private listeners;
5
+ /** Subscribes to an event. Returns an unsubscribe function. */
6
+ on<K extends keyof Events>(type: K, cb: Listener<Events[K]>): () => void;
7
+ off<K extends keyof Events>(type: K, cb: Listener<Events[K]>): void;
8
+ emit<K extends keyof Events>(type: K, payload: Events[K]): void;
9
+ removeAll(): void;
10
+ }
11
+ export {};