@typecms/sdk 0.1.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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/preview/client.ts","../../src/preview/dom.ts"],"sourcesContent":["/**\n * Preview Client\n *\n * Core client for handling live preview PostMessage communication.\n * This runs on the preview site (your frontend) inside an iframe.\n */\n\nimport type {\n PreviewConfig,\n PreviewState,\n PreviewMessage,\n PreviewUpdateMessage,\n PreviewInitMessage,\n PreviewReadyMessage,\n PreviewFocusMessage,\n} from './types'\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_ALLOWED_ORIGINS = [\n 'https://app.typecms.com',\n 'https://staging.typecms.com',\n 'http://localhost:3000',\n 'http://localhost:3001',\n]\n\n// =============================================================================\n// Preview Client\n// =============================================================================\n\nexport type PreviewListener = (state: PreviewState) => void\n\n/**\n * Create a preview client instance.\n *\n * This handles PostMessage communication between the Type CMS editor\n * and your preview site. The client runs in the preview iframe.\n *\n * @example\n * ```typescript\n * import { createPreviewClient } from '@typecms/sdk/preview'\n *\n * const preview = createPreviewClient({\n * debug: true,\n * onUpdate: (data) => {\n * console.log('Value changed:', data.path, data.value)\n * },\n * })\n *\n * // Start listening for messages\n * preview.connect()\n *\n * // Get current preview state\n * const state = preview.getState()\n *\n * // Cleanup when done\n * preview.disconnect()\n * ```\n */\nexport function createPreviewClient(config: PreviewConfig = {}) {\n const {\n allowedOrigins = DEFAULT_ALLOWED_ORIGINS,\n onInit,\n onUpdate,\n onNavigate,\n onFocus,\n debug = false,\n } = config\n\n // Internal state\n let state: PreviewState = {\n enabled: false,\n entryId: null,\n projectId: null,\n locale: null,\n typeId: null,\n values: {},\n focusedPath: null,\n }\n\n const listeners = new Set<PreviewListener>()\n let isConnected = false\n let parentOrigin: string | null = null\n\n // ==========================================================================\n // Internal Helpers\n // ==========================================================================\n\n function log(...args: unknown[]) {\n if (debug) {\n console.log('[TypeCMS Preview]', ...args)\n }\n }\n\n function setState(updates: Partial<PreviewState>) {\n state = { ...state, ...updates }\n listeners.forEach((listener) => listener(state))\n }\n\n function isAllowedOrigin(origin: string): boolean {\n return allowedOrigins.some((allowed) => {\n if (allowed === '*') return true\n // A trailing \"/*\" historically read as \"this origin\". Origins have no\n // path, so a startsWith() prefix match is unsafe — \"https://app.x.com/*\"\n // would also match the hostile origin \"https://app.x.com.evil.com\".\n // Treat \"/*\" as the exact origin (strip it) and always compare for\n // equality.\n const expected = allowed.endsWith('/*') ? allowed.slice(0, -2) : allowed\n return origin === expected\n })\n }\n\n function sendMessage(message: PreviewMessage) {\n if (!parentOrigin) {\n log('Cannot send message: no parent origin')\n return\n }\n\n if (typeof window === 'undefined' || !window.parent) {\n log('Cannot send message: not in iframe')\n return\n }\n\n log('Sending message:', message.type)\n window.parent.postMessage(message, parentOrigin)\n }\n\n // ==========================================================================\n // Message Handler\n // ==========================================================================\n\n function handleMessage(event: MessageEvent) {\n // Validate origin\n if (!isAllowedOrigin(event.origin)) {\n log('Ignored message from untrusted origin:', event.origin)\n return\n }\n\n // Validate message structure\n const message = event.data as PreviewMessage\n if (!message?.type?.startsWith('typecms:preview:')) {\n return\n }\n\n log('Received message:', message.type, message)\n parentOrigin = event.origin\n\n switch (message.type) {\n case 'typecms:preview:init': {\n const initMsg = message as PreviewInitMessage\n setState({\n enabled: true,\n entryId: initMsg.payload.entryId,\n projectId: initMsg.payload.projectId,\n locale: initMsg.payload.locale,\n typeId: initMsg.payload.typeId,\n values: {},\n focusedPath: null,\n })\n onInit?.(initMsg.payload)\n\n // Send ready response\n const readyMessage: PreviewReadyMessage = {\n type: 'typecms:preview:ready',\n timestamp: Date.now(),\n payload: {\n pathname: typeof window !== 'undefined' ? window.location.pathname : '/',\n },\n }\n sendMessage(readyMessage)\n break\n }\n\n case 'typecms:preview:update': {\n const updateMsg = message as PreviewUpdateMessage\n\n // If full values provided, replace entirely\n if (updateMsg.payload.values) {\n setState({ values: updateMsg.payload.values })\n } else {\n // Otherwise, merge the single path update\n const newValues = { ...state.values }\n setNestedValue(newValues, updateMsg.payload.path, updateMsg.payload.value)\n setState({ values: newValues })\n }\n\n onUpdate?.(updateMsg.payload)\n break\n }\n\n case 'typecms:preview:navigate': {\n onNavigate?.(message.payload.url)\n break\n }\n\n case 'typecms:preview:focus': {\n setState({ focusedPath: message.payload.path })\n onFocus?.(message.payload.path)\n break\n }\n }\n }\n\n // ==========================================================================\n // Public API\n // ==========================================================================\n\n return {\n /**\n * Start listening for preview messages.\n * Call this on mount.\n */\n connect() {\n if (typeof window === 'undefined') {\n log('Cannot connect: not in browser')\n return\n }\n\n if (isConnected) {\n log('Already connected')\n return\n }\n\n log('Connecting...')\n window.addEventListener('message', handleMessage)\n isConnected = true\n\n // Announce readiness to the embedding editor. The editor waits for a\n // `ready` before sending `init`, and we don't yet know its origin\n // (parentOrigin is only learned from an incoming message) — so without\n // this hello the handshake would deadlock. The payload is just our\n // pathname (non-sensitive), so it is safe to offer it to every\n // configured candidate origin; the browser delivers it only to the one\n // that actually matches the parent.\n if (window.parent && window.parent !== (window as unknown as Window)) {\n const hello: PreviewReadyMessage = {\n type: 'typecms:preview:ready',\n timestamp: Date.now(),\n payload: { pathname: window.location?.pathname ?? '/' },\n }\n for (const allowed of allowedOrigins) {\n const target = allowed === '*'\n ? '*'\n : allowed.endsWith('/*') ? allowed.slice(0, -2) : allowed\n try {\n window.parent.postMessage(hello, target)\n } catch {\n // Invalid target origin string — skip this candidate.\n }\n }\n }\n },\n\n /**\n * Stop listening for preview messages.\n * Call this on unmount.\n */\n disconnect() {\n if (typeof window === 'undefined') return\n\n log('Disconnecting...')\n window.removeEventListener('message', handleMessage)\n isConnected = false\n parentOrigin = null\n setState({\n enabled: false,\n entryId: null,\n projectId: null,\n locale: null,\n typeId: null,\n values: {},\n focusedPath: null,\n })\n },\n\n /**\n * Get current preview state.\n */\n getState(): PreviewState {\n return state\n },\n\n /**\n * Check if preview mode is active.\n */\n isEnabled(): boolean {\n return state.enabled\n },\n\n /**\n * Get a preview value by path.\n * Returns undefined if no preview value exists.\n */\n getValue<T = unknown>(path: string): T | undefined {\n return getNestedValue(state.values, path) as T | undefined\n },\n\n /**\n * Subscribe to state changes.\n */\n subscribe(listener: PreviewListener): () => void {\n listeners.add(listener)\n return () => listeners.delete(listener)\n },\n\n /**\n * Notify the editor that the preview has navigated.\n */\n notifyNavigation(pathname: string) {\n const message: PreviewReadyMessage = {\n type: 'typecms:preview:ready',\n timestamp: Date.now(),\n payload: { pathname },\n }\n sendMessage(message)\n },\n\n /**\n * Notify the editor that a field was clicked in the preview.\n * The editor will scroll to the corresponding field.\n *\n * @example\n * ```typescript\n * // On click of an element with data-preview-field attribute:\n * const path = el.closest('[data-preview-field]')?.dataset.previewField\n * if (path) preview.notifyFieldFocus(path)\n * ```\n */\n notifyFieldFocus(path: string) {\n const message: PreviewFocusMessage = {\n type: 'typecms:preview:focus',\n timestamp: Date.now(),\n payload: { path },\n }\n sendMessage(message)\n },\n }\n}\n\n// =============================================================================\n// Utility Functions\n// =============================================================================\n\n/**\n * Set a nested value by path (e.g., \"content.title\")\n */\nconst UNSAFE_KEYS = new Set(['__proto__', 'constructor', 'prototype'])\n\nfunction setNestedValue(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.')\n let current: Record<string, unknown> = obj\n\n for (let i = 0; i < parts.length - 1; i++) {\n const part = parts[i]\n // Handle array notation like \"items[0]\"\n const arrayMatch = part.match(/^(\\w+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, name, indexStr] = arrayMatch\n const index = parseInt(indexStr, 10)\n if (UNSAFE_KEYS.has(name)) continue\n if (!current[name]) {\n current[name] = []\n }\n const arr = current[name] as unknown[]\n if (!arr[index]) {\n arr[index] = {}\n }\n current = arr[index] as Record<string, unknown>\n } else {\n if (UNSAFE_KEYS.has(part)) continue\n if (!current[part] || typeof current[part] !== 'object') {\n current[part] = {}\n }\n current = current[part] as Record<string, unknown>\n }\n }\n\n const lastPart = parts[parts.length - 1]\n const arrayMatch = lastPart.match(/^(\\w+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, name, indexStr] = arrayMatch\n const index = parseInt(indexStr, 10)\n if (UNSAFE_KEYS.has(name)) return\n if (!current[name]) {\n current[name] = []\n }\n (current[name] as unknown[])[index] = value\n } else {\n if (UNSAFE_KEYS.has(lastPart)) return\n current[lastPart] = value\n }\n}\n\n/**\n * Get a nested value by path (supports \"items[0].name\" array notation)\n */\nexport function getNestedValue(obj: Record<string, unknown>, path: string): unknown {\n const parts = path.split('.')\n let current: unknown = obj\n\n for (const part of parts) {\n if (current === null || current === undefined) {\n return undefined\n }\n\n // Handle array notation like \"items[0]\"\n const arrayMatch = part.match(/^(\\w+)\\[(\\d+)\\]$/)\n\n if (arrayMatch) {\n const [, name, indexStr] = arrayMatch\n const index = parseInt(indexStr, 10)\n const arr = (current as Record<string, unknown>)[name] as unknown[] | undefined\n if (!arr) return undefined\n current = arr[index]\n } else {\n current = (current as Record<string, unknown>)[part]\n }\n }\n\n return current\n}\n\n/**\n * Deep merge two objects\n */\nexport function deepMerge<T extends Record<string, unknown>>(\n target: T,\n source: Record<string, unknown>\n): T {\n const result = { ...target }\n\n for (const key of Object.keys(source)) {\n const sourceValue = source[key]\n const targetValue = result[key as keyof T]\n\n if (\n sourceValue &&\n typeof sourceValue === 'object' &&\n !Array.isArray(sourceValue) &&\n targetValue &&\n typeof targetValue === 'object' &&\n !Array.isArray(targetValue)\n ) {\n result[key as keyof T] = deepMerge(\n targetValue as Record<string, unknown>,\n sourceValue as Record<string, unknown>\n ) as T[keyof T]\n } else {\n result[key as keyof T] = sourceValue as T[keyof T]\n }\n }\n\n return result\n}\n","/**\n * Preview DOM Binding\n *\n * Vanilla DOM binding for live preview — no framework required.\n * Elements annotated with `data-preview-field=\"path\"` are kept in sync\n * with the values streamed from the Type CMS editor. Ideal for Astro\n * and plain-HTML sites where a React/Vue layer isn't available.\n */\n\nimport { createPreviewClient, getNestedValue } from './client'\nimport type { PreviewConfig, PreviewState } from './types'\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface PreviewDomBindingOptions {\n /** Root node to search for bound elements (defaults to `document`) */\n root?: ParentNode\n /** Attribute carrying the field path (defaults to `data-preview-field`) */\n attribute?: string\n /**\n * Called for non-primitive values (objects, arrays, booleans, null).\n * Return a string to set as the element's textContent, or null/undefined\n * to skip the element. Without this callback, non-primitive values are\n * skipped entirely.\n */\n renderValue?: (el: Element, path: string, value: unknown) => string | null | undefined\n}\n\ninterface DomClient {\n subscribe(listener: (state: PreviewState) => void): () => void\n getState(): PreviewState\n}\n\nconst DEFAULT_ATTRIBUTE = 'data-preview-field'\n\n// =============================================================================\n// DOM Binding\n// =============================================================================\n\n/**\n * Bind preview values to the DOM.\n *\n * Subscribes to the preview client's state; whenever values change while\n * preview is enabled, every `[data-preview-field]` element whose path\n * resolves to a value (array notation like `items[0].name` is supported)\n * has its textContent updated. Each element's original textContent is\n * remembered the first time it is overwritten and restored on detach.\n *\n * SECURITY: values come from the editor via postMessage and are treated as\n * untrusted content. This binding only ever writes `textContent` — never\n * `innerHTML` — so preview values cannot inject markup or scripts (XSS).\n *\n * @returns A detach function that unsubscribes and restores originals.\n *\n * @example\n * ```typescript\n * const preview = createPreviewClient()\n * preview.connect()\n * const detach = bindPreviewToDom(preview)\n * // ...on teardown\n * detach()\n * ```\n */\nexport function bindPreviewToDom(\n client: DomClient,\n options: PreviewDomBindingOptions = {},\n): () => void {\n // SSR guard: nothing to bind outside the browser\n if (typeof document === 'undefined') {\n return () => {}\n }\n\n const attribute = options.attribute ?? DEFAULT_ATTRIBUTE\n const renderValue = options.renderValue\n\n // Original textContent per element, captured before the first overwrite\n const originals = new Map<Element, string | null>()\n\n function update(state: PreviewState) {\n if (!state.enabled) return\n\n const root: ParentNode = options.root ?? document\n const elements = root.querySelectorAll(`[${attribute}]`)\n\n elements.forEach((el) => {\n const path = el.getAttribute(attribute)\n if (!path) return\n\n const value = getNestedValue(state.values, path)\n if (value === undefined) return\n\n // Resolve the text to apply. Primitives render directly; everything\n // else is delegated to the site via renderValue (or skipped).\n let text: string | null = null\n if (typeof value === 'string') {\n text = value\n } else if (typeof value === 'number') {\n text = String(value)\n } else if (renderValue) {\n const result = renderValue(el, path, value)\n if (typeof result === 'string') {\n text = result\n }\n }\n if (text === null) return\n\n // Only write when changed, to avoid DOM churn (and layout/observer noise)\n if (el.textContent === text) return\n\n if (!originals.has(el)) {\n originals.set(el, el.textContent)\n }\n // textContent only — never innerHTML (untrusted content, XSS)\n el.textContent = text\n })\n }\n\n const unsubscribe = client.subscribe(update)\n update(client.getState())\n\n return () => {\n unsubscribe()\n originals.forEach((text, el) => {\n el.textContent = text\n })\n originals.clear()\n }\n}\n\n// =============================================================================\n// Convenience Entry Point\n// =============================================================================\n\n/**\n * One-call live preview for vanilla DOM sites.\n *\n * Creates a preview client, connects it, and binds it to the DOM.\n * Returns the client (for advanced use like `notifyFieldFocus`) and a\n * `destroy` function that unbinds and disconnects.\n *\n * @example Astro — add to a shared layout:\n * ```astro\n * <script>\n * import { initDomPreview } from '@typecms/sdk/preview/dom'\n * initDomPreview()\n * </script>\n * ```\n * Then annotate markup: `<h1 data-preview-field=\"title\">{entry.title}</h1>`\n */\nexport function initDomPreview(options: PreviewConfig & PreviewDomBindingOptions = {}) {\n const { root, attribute, renderValue, ...config } = options\n\n const client = createPreviewClient(config)\n client.connect()\n\n const unbind = bindPreviewToDom(client, { root, attribute, renderValue })\n\n return {\n client,\n destroy() {\n unbind()\n client.disconnect()\n },\n }\n}\n"],"mappings":"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","names":["arrayMatch"]}
@@ -0,0 +1,438 @@
1
+ export { P as PreviewListener, c as createPreviewClient, d as deepMerge, g as getNestedValue } from '../client-BW6BWpfF.mjs';
2
+ import { P as PreviewOverlayOptions } from '../overlay-CLYAeNwg.mjs';
3
+ export { a as attachPreviewOverlay } from '../overlay-CLYAeNwg.mjs';
4
+ import { P as PreviewState, a as PreviewConfig } from '../types-BNHDdA2G.mjs';
5
+ export { b as PreviewFocusMessage, c as PreviewInitMessage, d as PreviewMessage, e as PreviewMessageBase, f as PreviewMessageType, g as PreviewNavigateMessage, h as PreviewReadyMessage, i as PreviewUpdateMessage } from '../types-BNHDdA2G.mjs';
6
+ import * as react from 'react';
7
+ import { ReactNode } from 'react';
8
+
9
+ /**
10
+ * Preview Content Source Mapping (v1)
11
+ *
12
+ * Automatically tags preview-site DOM elements with `data-preview-field`
13
+ * attributes by matching element text against live preview field values,
14
+ * so click-to-edit and focus overlays work without the site hand-tagging
15
+ * every component.
16
+ *
17
+ * This is a deliberate alternative to stega-style source maps: no invisible
18
+ * unicode is encoded into API responses. Instead, the DOM is scanned and
19
+ * matched against the value map by plain string equality.
20
+ *
21
+ * HONEST LIMITS — this is heuristic value-matching:
22
+ * - Only exact, unique string values of at least `minLength` characters are
23
+ * matched. Values appearing under more than one field path are skipped
24
+ * (they cannot be attributed safely), as are short values like "OK" or
25
+ * "Go" that would tag buttons everywhere.
26
+ * - Only an element's own direct text is compared — never text inside nested
27
+ * elements — so the innermost rendering element is what gets tagged and
28
+ * wrapper containers never are.
29
+ * - Values the site transforms before rendering (uppercased, truncated,
30
+ * interpolated, markdown-rendered) will not match.
31
+ * - Framework re-renders are survived: when a tagged element is replaced,
32
+ * the next pass (value update or DOM mutation) notices the remembered
33
+ * element is disconnected and re-tags the replacement.
34
+ *
35
+ * Manual `data-preview-field` attributes remain the precise option and
36
+ * always win: auto-tagging never overwrites an existing attribute, and
37
+ * detach removes only the attributes it added.
38
+ *
39
+ * @example
40
+ * ```typescript
41
+ * import { createPreviewClient } from '@typecms/sdk/preview'
42
+ * import { autoTagPreviewFields } from '@typecms/sdk/preview'
43
+ *
44
+ * const preview = createPreviewClient()
45
+ * preview.connect()
46
+ *
47
+ * const detach = autoTagPreviewFields(preview)
48
+ * // Elements whose text equals a field value now carry
49
+ * // data-preview-field="path" — click-to-edit and overlays just work.
50
+ *
51
+ * // ...on teardown
52
+ * detach()
53
+ * ```
54
+ */
55
+
56
+ /**
57
+ * Minimal structural view of a DOM node — everything the tagger touches.
58
+ * Real `Element`/`Document` objects satisfy this, and tests can provide
59
+ * lightweight fakes without a DOM implementation.
60
+ */
61
+ interface AutoTagNode {
62
+ nodeType: number;
63
+ textContent?: string | null;
64
+ childNodes?: ArrayLike<AutoTagNode>;
65
+ /**
66
+ * Whether the node is still attached to the document. Real DOM elements
67
+ * provide this; when absent the node is assumed connected. Used to detect
68
+ * framework re-renders that replaced a tagged element.
69
+ */
70
+ isConnected?: boolean;
71
+ getAttribute?(name: string): string | null;
72
+ setAttribute?(name: string, value: string): void;
73
+ removeAttribute?(name: string): void;
74
+ }
75
+ interface AutoTagOptions {
76
+ /** Root node to scan for candidate elements (defaults to `document.body`) */
77
+ root?: AutoTagNode;
78
+ /** Attribute to write the field path into (defaults to `data-preview-field`) */
79
+ attribute?: string;
80
+ /**
81
+ * Minimum (trimmed) string value length to consider for matching
82
+ * (defaults to 3). Shorter values like "OK" would tag unrelated UI.
83
+ */
84
+ minLength?: number;
85
+ /**
86
+ * Re-run the matching pass when new nodes are added under `root`
87
+ * (client-side navigation, lazy rendering). Defaults to true; ignored
88
+ * in environments without `MutationObserver`.
89
+ */
90
+ observe?: boolean;
91
+ }
92
+ interface SourceMapClient {
93
+ subscribe(listener: (state: PreviewState) => void): () => void;
94
+ getState(): PreviewState;
95
+ }
96
+ /**
97
+ * Automatically tag DOM elements with preview field paths.
98
+ *
99
+ * Subscribes to the preview client; whenever preview is enabled and values
100
+ * are present (the editor's init sends a full values sync), string leaf
101
+ * values are matched against element text and matching elements receive
102
+ * `data-preview-field="<path>"`. The pass re-runs on value updates (new
103
+ * fields may have arrived) and — when `observe` is on — when new nodes are
104
+ * added under `root`. Re-runs are cheap: disabled states bail immediately
105
+ * and already-tagged paths are skipped.
106
+ *
107
+ * @returns A detach function that unsubscribes, stops observing, and removes
108
+ * only the attributes this tagger added (manual tags are untouched).
109
+ */
110
+ declare function autoTagPreviewFields(client: SourceMapClient, options?: AutoTagOptions): () => void;
111
+
112
+ /**
113
+ * Rich-Text Live Preview Helpers
114
+ *
115
+ * Rich-text fields ('richtextv2' → TipTap, 'richtext' → Slate) store large
116
+ * JSON documents. During live preview every keystroke sends the WHOLE new
117
+ * document, and naive consumers re-render the entire rich-text tree — losing
118
+ * performance and, in editable embeds, cursor state.
119
+ *
120
+ * These helpers apply STRUCTURAL SHARING: when a new document arrives, any
121
+ * subtree that is deep-equal to the previous document's subtree at the same
122
+ * position is returned as the SAME reference from the previous document.
123
+ * React/Vue memoization and keyed renderers then only re-render the nodes
124
+ * that actually changed.
125
+ *
126
+ * This module is framework-agnostic and dependency-free. Documents are
127
+ * assumed to be plain JSON data (no cycles, no functions, no Dates).
128
+ *
129
+ * @example
130
+ * ```typescript
131
+ * import { createRichTextPreview } from '@typecms/sdk/preview'
132
+ *
133
+ * const preview = createRichTextPreview()
134
+ * const docA = preview.apply(incomingDocA)
135
+ * const docB = preview.apply(incomingDocB)
136
+ * // Unchanged paragraphs in docB are the SAME object references as in docA.
137
+ * ```
138
+ */
139
+ /**
140
+ * A TipTap/ProseMirror node. Kept permissive on purpose — the editor may
141
+ * attach arbitrary attrs/marks and custom node types.
142
+ */
143
+ interface TipTapNode {
144
+ type: string;
145
+ content?: TipTapNode[];
146
+ text?: string;
147
+ marks?: Array<{
148
+ type: string;
149
+ attrs?: Record<string, unknown>;
150
+ }>;
151
+ attrs?: Record<string, unknown>;
152
+ [key: string]: unknown;
153
+ }
154
+ /**
155
+ * A TipTap document: `{ type: 'doc', content: [...] }`.
156
+ * Produced by 'richtextv2' fields.
157
+ */
158
+ interface TipTapDoc extends TipTapNode {
159
+ type: 'doc';
160
+ content: TipTapNode[];
161
+ }
162
+ /**
163
+ * A Slate node. Element nodes carry `children`; text leaves carry `text`
164
+ * (plus arbitrary mark properties like `bold`). Kept permissive on purpose.
165
+ */
166
+ interface SlateNode {
167
+ type?: string;
168
+ children?: SlateNode[];
169
+ text?: string;
170
+ [key: string]: unknown;
171
+ }
172
+ /**
173
+ * Check whether a value looks like a TipTap document
174
+ * (`{ type: 'doc', content: [...] }`, produced by 'richtextv2' fields).
175
+ *
176
+ * Permissive: only the root shape is inspected, children are not validated.
177
+ * Use it to decide which renderer to hand a preview value to.
178
+ */
179
+ declare function isTipTapDoc(value: unknown): value is TipTapDoc;
180
+ /**
181
+ * Check whether a value looks like a Slate document — an array of nodes
182
+ * where each node has `children` or is a text leaf with `text`
183
+ * (produced by 'richtext' fields).
184
+ *
185
+ * Permissive: only one level is inspected, grandchildren are not validated.
186
+ * An empty array is treated as a valid (empty) Slate fragment.
187
+ */
188
+ declare function isSlateDoc(value: unknown): value is SlateNode[];
189
+ /**
190
+ * Structurally share `next` against `prev`.
191
+ *
192
+ * Returns `prev` itself when the two are deep-equal. Otherwise returns a copy
193
+ * of `next` in which every child object/array is replaced by the structurally
194
+ * shared version against the corresponding `prev` child.
195
+ *
196
+ * Guarantee: any subtree of the result that is deep-equal to the previous
197
+ * document's subtree at the same position is the SAME reference from `prev`,
198
+ * so memoized/keyed renderers skip it.
199
+ *
200
+ * Arrays are aligned from the tail first, then by index from the head, so a
201
+ * mid-array insert or delete (the common rich-text edit) still preserves
202
+ * reference equality for the deep-equal siblings around the edit point.
203
+ *
204
+ * Handles arrays, plain objects, primitives, `null`, and mismatched types
205
+ * (mismatches simply return `next`). Input is assumed to be acyclic JSON.
206
+ */
207
+ declare function shareStructure<T>(prev: T | undefined, next: T): T;
208
+ interface RichTextPreviewOptions {
209
+ /**
210
+ * Seed the "previous document" memory, e.g. with the published document
211
+ * already rendered on the page, so the very first preview update can
212
+ * share unchanged subtrees with it.
213
+ */
214
+ initial?: unknown;
215
+ }
216
+ interface RichTextPreview {
217
+ /**
218
+ * Structurally share `nextDoc` against the last applied document and
219
+ * remember the result for the next call.
220
+ */
221
+ apply<T>(nextDoc: T): T;
222
+ /** Forget the last document. The next apply() returns its input as-is. */
223
+ reset(): void;
224
+ }
225
+ /**
226
+ * Create a small stateful structural-sharing instance for a single
227
+ * rich-text field. Useful outside React/Vue (vanilla JS, Svelte stores,
228
+ * custom render loops).
229
+ *
230
+ * @example
231
+ * ```typescript
232
+ * const preview = createRichTextPreview()
233
+ *
234
+ * client.subscribe((state) => {
235
+ * const doc = preview.apply(state.values.body)
236
+ * render(doc) // unchanged nodes keep their references
237
+ * })
238
+ * ```
239
+ */
240
+ declare function createRichTextPreview(options?: RichTextPreviewOptions): RichTextPreview;
241
+
242
+ interface PreviewContextValue {
243
+ state: PreviewState;
244
+ /** Check if running in preview mode */
245
+ isPreview: boolean;
246
+ /** Get preview value for a field path */
247
+ getValue: <T>(path: string) => T | undefined;
248
+ /** Merge published content with preview overrides */
249
+ mergeContent: <T extends Record<string, unknown>>(content: T) => T;
250
+ /** Notify editor of navigation */
251
+ notifyNavigation: (pathname: string) => void;
252
+ /** Notify editor that a field was clicked in the preview */
253
+ notifyFieldFocus: (path: string) => void;
254
+ }
255
+ interface PreviewProviderProps extends PreviewConfig {
256
+ children: ReactNode;
257
+ /**
258
+ * Draw an automatic highlight box over the `[data-preview-field]` element
259
+ * matching the field focused in the editor. Pass options to customize.
260
+ */
261
+ overlay?: boolean | PreviewOverlayOptions;
262
+ }
263
+ /**
264
+ * Provider component for Type CMS live preview.
265
+ *
266
+ * Wrap your app (or specific pages) with this provider to enable
267
+ * real-time preview updates from the Type CMS editor.
268
+ *
269
+ * @example
270
+ * ```tsx
271
+ * // app/layout.tsx or pages/_app.tsx
272
+ * import { PreviewProvider } from '@typecms/sdk/preview'
273
+ *
274
+ * export default function RootLayout({ children }) {
275
+ * return (
276
+ * <PreviewProvider debug={process.env.NODE_ENV === 'development'}>
277
+ * {children}
278
+ * </PreviewProvider>
279
+ * )
280
+ * }
281
+ * ```
282
+ */
283
+ declare function PreviewProvider({ children, overlay, ...config }: PreviewProviderProps): react.JSX.Element;
284
+ /**
285
+ * Access the preview context.
286
+ *
287
+ * @throws Error if used outside of PreviewProvider
288
+ */
289
+ declare function usePreviewContext(): PreviewContextValue;
290
+ /**
291
+ * Check if running in preview mode.
292
+ *
293
+ * @example
294
+ * ```tsx
295
+ * function Page() {
296
+ * const isPreview = useIsPreview()
297
+ *
298
+ * return (
299
+ * <div>
300
+ * {isPreview && <div className="preview-badge">Preview Mode</div>}
301
+ * <Content />
302
+ * </div>
303
+ * )
304
+ * }
305
+ * ```
306
+ */
307
+ declare function useIsPreview(): boolean;
308
+ /**
309
+ * Get current preview state.
310
+ */
311
+ declare function usePreviewState(): PreviewState | null;
312
+ /**
313
+ * Get a preview value by field path, with fallback to published value.
314
+ *
315
+ * @example
316
+ * ```tsx
317
+ * function Title({ entry }) {
318
+ * const title = usePreviewValue('title', entry.title)
319
+ * return <h1>{title}</h1>
320
+ * }
321
+ * ```
322
+ */
323
+ declare function usePreviewValue<T>(path: string, fallback: T): T;
324
+ /**
325
+ * Merge published content with preview overrides.
326
+ *
327
+ * This is the primary hook for integrating preview data.
328
+ * Pass your fetched/published content and get back a merged
329
+ * version that includes any live preview changes.
330
+ *
331
+ * @example
332
+ * ```tsx
333
+ * // In your page component
334
+ * function BlogPost({ post }) {
335
+ * // post is fetched from the API (published content)
336
+ * const content = usePreviewContent(post)
337
+ *
338
+ * // content now includes any live preview changes
339
+ * return (
340
+ * <article>
341
+ * <h1>{content.title}</h1>
342
+ * <div>{content.body}</div>
343
+ * </article>
344
+ * )
345
+ * }
346
+ * ```
347
+ */
348
+ declare function usePreviewContent<T extends Record<string, unknown>>(content: T): T;
349
+ /**
350
+ * Get the currently focused field path (if any).
351
+ *
352
+ * Use this to highlight fields in your preview when they're
353
+ * being edited in the Type CMS editor.
354
+ *
355
+ * @example
356
+ * ```tsx
357
+ * function Field({ path, children }) {
358
+ * const focusedPath = useFocusedField()
359
+ * const isFocused = focusedPath === path
360
+ *
361
+ * return (
362
+ * <div className={isFocused ? 'ring-2 ring-blue-500' : ''}>
363
+ * {children}
364
+ * </div>
365
+ * )
366
+ * }
367
+ * ```
368
+ */
369
+ declare function useFocusedField(): string | null;
370
+ /**
371
+ * Hook that provides a function to notify the editor of navigation.
372
+ *
373
+ * Call this when your app navigates to a new page so the editor
374
+ * can update its state accordingly.
375
+ *
376
+ * @example
377
+ * ```tsx
378
+ * function Page() {
379
+ * const pathname = usePathname()
380
+ * const notifyNavigation = usePreviewNavigation()
381
+ *
382
+ * useEffect(() => {
383
+ * notifyNavigation(pathname)
384
+ * }, [pathname, notifyNavigation])
385
+ *
386
+ * return <Content />
387
+ * }
388
+ * ```
389
+ */
390
+ declare function usePreviewNavigation(): (pathname: string) => void;
391
+ /**
392
+ * Hook that provides a function to notify the editor when a preview field is clicked.
393
+ *
394
+ * Add `data-preview-field="<path>"` to elements in your preview site. When a user
395
+ * clicks one, call this function with the path to highlight and scroll to the
396
+ * corresponding field in the Type CMS editor.
397
+ *
398
+ * @example
399
+ * ```tsx
400
+ * function PreviewField({ path, children }) {
401
+ * const notifyFieldFocus = usePreviewFieldFocus()
402
+ *
403
+ * return (
404
+ * <div
405
+ * data-preview-field={path}
406
+ * onClick={() => notifyFieldFocus(path)}
407
+ * >
408
+ * {children}
409
+ * </div>
410
+ * )
411
+ * }
412
+ * ```
413
+ */
414
+ declare function usePreviewFieldFocus(): (path: string) => void;
415
+ /**
416
+ * Get a rich-text preview value by field path with STRUCTURAL SHARING.
417
+ *
418
+ * Like `usePreviewValue`, but pipes the document through a persistent
419
+ * structural-sharing instance: subtrees that are deep-equal to the previous
420
+ * update keep their object references, so memoized node renderers
421
+ * (`React.memo`, keyed lists) only re-render nodes that actually changed —
422
+ * instead of re-rendering the whole rich-text tree on every keystroke.
423
+ *
424
+ * Works for both TipTap ('richtextv2') and Slate ('richtext') documents —
425
+ * or any JSON value. Returns the fallback (unshared) when preview is
426
+ * disabled. Sharing memory resets when `path` changes.
427
+ *
428
+ * @example
429
+ * ```tsx
430
+ * function Body({ entry }) {
431
+ * const doc = usePreviewRichText('body', entry.body)
432
+ * return <RichTextRenderer doc={doc} />
433
+ * }
434
+ * ```
435
+ */
436
+ declare function usePreviewRichText<T>(path: string, fallback: T): T;
437
+
438
+ export { type AutoTagOptions, PreviewConfig, PreviewOverlayOptions, PreviewProvider, PreviewState, type RichTextPreview, type RichTextPreviewOptions, autoTagPreviewFields, createRichTextPreview, isSlateDoc, isTipTapDoc, shareStructure, useFocusedField, useIsPreview, usePreviewContent, usePreviewContext, usePreviewFieldFocus, usePreviewNavigation, usePreviewRichText, usePreviewState, usePreviewValue };