payload-live-preview 1.0.2 → 1.0.4

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 (71) hide show
  1. package/README.md +150 -84
  2. package/dist/adapters/astro/RichText.astro +8 -4
  3. package/dist/adapters/astro/index.d.ts +84 -9
  4. package/dist/adapters/astro/index.js +1 -363
  5. package/dist/adapters/astro/index.js.map +1 -1
  6. package/dist/adapters/astro/middleware-entry.d.ts +1 -1
  7. package/dist/adapters/astro/middleware-entry.js +1 -296
  8. package/dist/adapters/astro/middleware-entry.js.map +1 -1
  9. package/dist/adapters/nextjs/index.d.ts +24 -10
  10. package/dist/adapters/nextjs/index.js +1 -305
  11. package/dist/adapters/nextjs/index.js.map +1 -1
  12. package/dist/adapters/nuxt/index.d.ts +20 -10
  13. package/dist/adapters/nuxt/index.js +1 -293
  14. package/dist/adapters/nuxt/index.js.map +1 -1
  15. package/dist/adapters/sveltekit/index.d.ts +22 -6
  16. package/dist/adapters/sveltekit/index.js +1 -272
  17. package/dist/adapters/sveltekit/index.js.map +1 -1
  18. package/dist/codegen-astro.js +1 -609
  19. package/dist/codegen-astro.js.map +1 -1
  20. package/dist/codegen-cli.js +1 -665
  21. package/dist/codegen-cli.js.map +1 -1
  22. package/dist/codegen.cjs +1 -550
  23. package/dist/codegen.cjs.map +1 -1
  24. package/dist/codegen.js +1 -546
  25. package/dist/codegen.js.map +1 -1
  26. package/dist/core.cjs +1 -3719
  27. package/dist/core.cjs.map +1 -1
  28. package/dist/core.d.cts +1210 -7
  29. package/dist/core.d.ts +1210 -7
  30. package/dist/core.js +1 -3692
  31. package/dist/core.js.map +1 -1
  32. package/dist/index.cjs +1 -4118
  33. package/dist/index.cjs.map +1 -1
  34. package/dist/index.d.cts +1359 -34
  35. package/dist/index.d.ts +1359 -34
  36. package/dist/index.js +1 -4075
  37. package/dist/index.js.map +1 -1
  38. package/dist/{middleware-BwIlf0jW.d.cts → middleware-CkSeLZ-9.d.ts} +35 -19
  39. package/dist/payload.cjs +1 -30
  40. package/dist/payload.cjs.map +1 -1
  41. package/dist/payload.d.cts +4 -3
  42. package/dist/payload.d.ts +4 -3
  43. package/dist/payload.js +1 -28
  44. package/dist/payload.js.map +1 -1
  45. package/package.json +55 -16
  46. package/dist/adapters/astro/index.cjs +0 -370
  47. package/dist/adapters/astro/index.cjs.map +0 -1
  48. package/dist/adapters/astro/index.d.cts +0 -105
  49. package/dist/adapters/astro/middleware-entry.cjs +0 -303
  50. package/dist/adapters/astro/middleware-entry.cjs.map +0 -1
  51. package/dist/adapters/astro/middleware-entry.d.cts +0 -15
  52. package/dist/adapters/nextjs/index.cjs +0 -309
  53. package/dist/adapters/nextjs/index.cjs.map +0 -1
  54. package/dist/adapters/nextjs/index.d.cts +0 -75
  55. package/dist/adapters/nuxt/index.cjs +0 -299
  56. package/dist/adapters/nuxt/index.cjs.map +0 -1
  57. package/dist/adapters/nuxt/index.d.cts +0 -126
  58. package/dist/adapters/sveltekit/index.cjs +0 -275
  59. package/dist/adapters/sveltekit/index.cjs.map +0 -1
  60. package/dist/adapters/sveltekit/index.d.cts +0 -80
  61. package/dist/bind-BSgxoQhN.d.cts +0 -1137
  62. package/dist/bind-BSgxoQhN.d.ts +0 -1137
  63. package/dist/codegen-astro.cjs +0 -612
  64. package/dist/codegen-astro.cjs.map +0 -1
  65. package/dist/codegen-astro.d.cts +0 -54
  66. package/dist/codegen-cli.cjs +0 -669
  67. package/dist/codegen-cli.cjs.map +0 -1
  68. package/dist/codegen-cli.d.cts +0 -19
  69. package/dist/middleware-BwIlf0jW.d.ts +0 -168
  70. package/dist/preview-request-DAvxm18T.d.cts +0 -68
  71. package/dist/preview-request-DAvxm18T.d.ts +0 -68
package/dist/index.d.ts CHANGED
@@ -1,6 +1,645 @@
1
- import { l as LivePreviewPlugin, e as FieldRenderer } from './bind-BSgxoQhN.js';
2
- export { B as BindOptions, C as CachedElement, a as CspDirectiveMerge, E as EventEmitter, b as EventHandler, F as FieldBindingAttributes, c as FieldName, d as FieldPath, f as FieldTransform, g as FieldType, h as FrameAncestorsOptions, L as LIBRARY_PROTOCOL_VERSION, i as LivePreviewClient, j as LivePreviewClientConfig, k as LivePreviewEventMap, O as OriginDetector, P as PayloadFieldSchema, m as PayloadFieldType, n as PayloadLivePreviewData, o as PayloadLivePreviewMessage, p as PluginContext, q as ProtocolCapability, r as ProtocolNegotiation, R as RenderContext, S as SanitizerDocument, U as Unsubscribe, V as VERSION, s as ValueAt, t as bind, u as bindByPath, v as buildFrameAncestors, w as buildScriptSrcWithNonce, x as detectInitialLocale, y as escapeHtml, z as escapeHtmlAttribute, A as generateCspNonce, D as hasCapability, G as initLivePreview, H as isDevMode, I as isExternalHttpUrl, J as isInIframe, K as isInPopup, M as isInPreviewContext, N as isSafeUrl, Q as mergeCspHeader, T as negotiateProtocol, W as sanitizeHtml, X as setCspCrypto, Y as setSanitizerDocument } from './bind-BSgxoQhN.js';
3
- export { P as PreviewRequestLike, a as PreviewRequestOptions, i as isPreviewRequest } from './preview-request-DAvxm18T.js';
1
+ /**
2
+ * Single source of truth for the library version.
3
+ *
4
+ * Both the npm package surface (`src/index.ts`, `src/core-entry.ts`)
5
+ * and the inline runtime (`src/core/runtime.ts`, which gets bundled by
6
+ * esbuild and embedded into `runtime.generated.ts`) read the version
7
+ * from here so they never drift.
8
+ *
9
+ * Imports `package.json` directly — tsc, tsup, and esbuild all inline
10
+ * the value at build time when `resolveJsonModule` is enabled.
11
+ *
12
+ * @module @/version
13
+ */
14
+ declare const VERSION: string;
15
+
16
+ /**
17
+ * Payload Live Preview message protocol.
18
+ *
19
+ * Mirrors the messages emitted by Payload CMS through
20
+ * `window.postMessage`, verified against the shipped admin code
21
+ * (`@payloadcms/ui` → `elements/LivePreview/Window`):
22
+ *
23
+ * - Payload **3.x** sends `{ type, collectionSlug, globalSlug, data,
24
+ * locale, externallyUpdatedRelationship }` on every form edit —
25
+ * `data` holds **raw form values** (relationships as bare IDs) and
26
+ * there is **no** `fieldSchemaJSON`.
27
+ * - Payload **2.x** additionally sent `fieldSchemaJSON` with the
28
+ * first update (client-side merge era).
29
+ * - `previewToken` and `protocolVersion` are extensions of THIS
30
+ * library for custom admin senders; stock Payload never sends
31
+ * them and ignores them in our `ready` handshake.
32
+ *
33
+ * @module @/types/payload-protocol
34
+ */
35
+ /**
36
+ * A `payload-live-preview` data update.
37
+ *
38
+ * `data` is the serialized document state. `fieldSchemaJSON`, when
39
+ * present (Payload 2.x or custom senders), allows the schema-driven
40
+ * engine to resolve field types without any DOM annotations.
41
+ */
42
+ interface PayloadLivePreviewMessage {
43
+ readonly type: 'payload-live-preview';
44
+ readonly data?: Record<string, unknown>;
45
+ readonly fieldSchemaJSON?: readonly PayloadFieldSchema[];
46
+ readonly globalSlug?: string;
47
+ readonly collectionSlug?: string;
48
+ readonly locale?: string;
49
+ readonly ready?: boolean;
50
+ /**
51
+ * Payload 3.x: most recent relationship-document event (create/update
52
+ * in a drawer), or `null`. The official client ignores it too — it is
53
+ * modeled here for completeness and consumer event access.
54
+ */
55
+ readonly externallyUpdatedRelationship?: PayloadDocumentEventDetail | null;
56
+ /**
57
+ * Optional preview JWT. ⚠️ Library extension — stock Payload never
58
+ * sends one. When `RuntimeOptions.validateToken` is set, the runtime
59
+ * requires every data update to carry a token that passes the
60
+ * validator; messages without a valid token are dropped.
61
+ */
62
+ readonly previewToken?: string;
63
+ /**
64
+ * Highest protocol version the sender supports. ⚠️ Library extension
65
+ * for custom senders — stock Payload sends no version. When absent,
66
+ * the receiver assumes v1.
67
+ */
68
+ readonly protocolVersion?: number;
69
+ }
70
+ /**
71
+ * Payload's `DocumentEvent` shape (admin `providers/DocumentEvents`).
72
+ */
73
+ interface PayloadDocumentEventDetail {
74
+ readonly entitySlug: string;
75
+ readonly operation?: 'create' | 'update';
76
+ readonly id?: string | number;
77
+ readonly updatedAt?: string;
78
+ readonly [extra: string]: unknown;
79
+ }
80
+ /**
81
+ * Parsed, validated message payload exposed to consumers via events.
82
+ */
83
+ interface PayloadLivePreviewData {
84
+ readonly fields: Record<string, unknown>;
85
+ readonly schema?: readonly PayloadFieldSchema[];
86
+ readonly globalSlug?: string;
87
+ readonly collectionSlug?: string;
88
+ readonly locale?: string;
89
+ }
90
+ /**
91
+ * The subset of Payload's field-schema description we rely on.
92
+ *
93
+ * Payload emits the entire field configuration tree but we only need
94
+ * the discriminator `type` and the nesting via `fields`/`blocks`.
95
+ * Extra properties are preserved opaquely.
96
+ */
97
+ interface PayloadFieldSchema {
98
+ readonly name: string;
99
+ readonly type: PayloadFieldType;
100
+ readonly label?: string;
101
+ readonly required?: boolean;
102
+ readonly localized?: boolean;
103
+ readonly fields?: readonly PayloadFieldSchema[];
104
+ readonly blocks?: readonly PayloadBlockSchema[];
105
+ readonly relationTo?: string | readonly string[];
106
+ readonly hasMany?: boolean;
107
+ readonly admin?: {
108
+ readonly condition?: PayloadFieldCondition;
109
+ };
110
+ readonly [extra: string]: unknown;
111
+ }
112
+ interface PayloadBlockSchema {
113
+ readonly slug: string;
114
+ readonly fields: readonly PayloadFieldSchema[];
115
+ readonly [extra: string]: unknown;
116
+ }
117
+ /**
118
+ * The Payload core field types this library recognises (20 as of
119
+ * Payload 3.x). `tabs` is treated as a structural container; `group`
120
+ * flattens nested fields.
121
+ */
122
+ type PayloadFieldType = 'text' | 'textarea' | 'richText' | 'email' | 'number' | 'checkbox' | 'date' | 'select' | 'radio' | 'array' | 'blocks' | 'group' | 'tabs' | 'row' | 'collapsible' | 'relationship' | 'upload' | 'point' | 'json' | 'code' | 'ui';
123
+ /**
124
+ * Payload's `admin.condition` is a function in source, but it cannot
125
+ * be transferred over postMessage. The schema arrives with this slot
126
+ * either absent or, in future Payload versions, replaced by a
127
+ * serialized expression. We model the slot but do not consume it yet.
128
+ */
129
+ type PayloadFieldCondition = (data: unknown, siblingData: unknown) => boolean;
130
+
131
+ /**
132
+ * Public event payload shapes for the live preview lifecycle.
133
+ *
134
+ * The map keys form the union of valid event names. Adding a new event
135
+ * is a two-line change: add the entry here and dispatch from the
136
+ * relevant module. Consumers get autocompletion and exhaustive checks
137
+ * for free because the `EventEmitter` is generic over this map.
138
+ *
139
+ * @module @events/types
140
+ */
141
+
142
+ interface LivePreviewEventMap {
143
+ /**
144
+ * Fired once per startup attempt after observers, cache, and message listening
145
+ * are active. A later startup failure may roll that attempt back; retrying then
146
+ * emits a new `init` event.
147
+ */
148
+ readonly init: {
149
+ readonly timestamp: number;
150
+ };
151
+ /** Fired for the first accepted data-bearing update from a trusted origin. */
152
+ readonly connect: {
153
+ readonly origin: string;
154
+ readonly timestamp: number;
155
+ };
156
+ /**
157
+ * Fired when the heartbeat times out or the runtime is destroyed. The
158
+ * `'unload'` reason remains in the public union for 1.x producer compatibility;
159
+ * the built-in runtime does not currently emit it.
160
+ */
161
+ readonly disconnect: {
162
+ readonly reason: 'timeout' | 'destroy' | 'unload';
163
+ readonly timestamp: number;
164
+ };
165
+ /**
166
+ * Fired before the DOM is mutated for an incoming update.
167
+ *
168
+ * Handlers may call `cancel()` before their synchronous or asynchronous
169
+ * work settles to skip exactly this revision.
170
+ */
171
+ readonly beforeUpdate: {
172
+ readonly data: PayloadLivePreviewData;
173
+ /** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
174
+ readonly revision?: number;
175
+ readonly cancel: () => void;
176
+ };
177
+ /**
178
+ * Fired after each actual, current DOM application batch. Visibility replay
179
+ * may produce another event for the same revision; cancelled, obsolete, and
180
+ * deferred-only batches produce none.
181
+ */
182
+ readonly afterUpdate: {
183
+ readonly data: PayloadLivePreviewData;
184
+ readonly updatedCount: number;
185
+ readonly durationMs: number;
186
+ /** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
187
+ readonly revision?: number;
188
+ };
189
+ /** Fired for each successful, still-current element write. */
190
+ readonly elementUpdate: {
191
+ readonly element: Element;
192
+ readonly fieldName: string;
193
+ readonly previousValue: unknown;
194
+ readonly nextValue: unknown;
195
+ /** Runtime-populated ordering metadata; optional for 1.x producer compatibility. */
196
+ readonly revision?: number;
197
+ };
198
+ /** Fired whenever the element cache is rebuilt (initial scan or MutationObserver-triggered). */
199
+ readonly cacheRefresh: {
200
+ readonly elementCount: number;
201
+ readonly fieldCount: number;
202
+ readonly durationMs: number;
203
+ };
204
+ /** Fired when a `payload-document-event` message arrives (document save). */
205
+ readonly documentSave: {
206
+ readonly timestamp: number;
207
+ };
208
+ /**
209
+ * Fired on errors that the runtime caught but cannot fully recover from.
210
+ *
211
+ * The `context` string identifies where the error originated.
212
+ */
213
+ readonly error: {
214
+ readonly error: Error;
215
+ readonly context: string;
216
+ };
217
+ /**
218
+ * Fired during destroy after browser observers and the message listener are
219
+ * removed. Event/plugin listeners remain available for this notification and
220
+ * are released by the owning client afterwards.
221
+ */
222
+ readonly destroy: {
223
+ readonly timestamp: number;
224
+ };
225
+ }
226
+ /**
227
+ * Handler signature: receives the payload and may return a Promise.
228
+ *
229
+ * Promise-returning handlers are awaited in registration order — this
230
+ * is important for `beforeUpdate.cancel()` semantics.
231
+ */
232
+ type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
233
+ /** Subscription removal function returned by `on()`/`once()`. */
234
+ type Unsubscribe = () => void;
235
+
236
+ /**
237
+ * Per-instance, type-safe event emitter.
238
+ *
239
+ * Replaces the legacy module-level singleton. Each `LivePreviewClient`
240
+ * instantiates its own emitter so that calling `destroy()` on one
241
+ * instance cannot clobber listeners attached to another.
242
+ *
243
+ * Design notes:
244
+ *
245
+ * - Generic over an event map type. The default `LivePreviewEventMap`
246
+ * captures every event this library emits, but the implementation
247
+ * is reusable for any map.
248
+ * - Handlers may be `async`. They are awaited sequentially in
249
+ * registration order. This is intentional: `beforeUpdate.cancel()`
250
+ * and similar synchronization rely on stable ordering.
251
+ * - Errors thrown by one handler are logged via `console.error` and
252
+ * do not prevent later handlers from running.
253
+ * - `once()` semantics: registered into a separate map; the entire
254
+ * snapshot for an event is claimed immediately before its dispatch,
255
+ * and remains consumed even if individual handlers throw.
256
+ *
257
+ * @module @events/emitter
258
+ */
259
+
260
+ declare class EventEmitter<TMap extends object = LivePreviewEventMap> {
261
+ #private;
262
+ /**
263
+ * Subscribe `handler` to `event`. Returns an unsubscribe function;
264
+ * call it to remove the handler. The handler is invoked for every
265
+ * subsequent emit until unsubscribed.
266
+ */
267
+ on<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
268
+ /**
269
+ * Subscribe `handler` for a single emit. After the first dispatch,
270
+ * the handler is removed automatically.
271
+ */
272
+ once<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
273
+ /**
274
+ * Remove `handler` from `event`. Works for both `on`- and
275
+ * `once`-registered handlers. No-op when the handler isn't registered.
276
+ */
277
+ off<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): void;
278
+ /**
279
+ * Dispatch `payload` to every handler registered for `event`.
280
+ *
281
+ * Handlers run sequentially in registration order — regular ones
282
+ * first, then `once` handlers. Async handlers are awaited. Synchronous
283
+ * exceptions are caught, logged, and isolated to the offending handler.
284
+ */
285
+ emit<E extends keyof TMap>(event: E, payload: TMap[E]): Promise<void>;
286
+ /**
287
+ * Lifecycle-only guarded dispatch. The predicate is checked before and after
288
+ * every handler so an async boundary cannot let an obsolete transaction run
289
+ * later callbacks. `emit()` intentionally keeps its established behaviour.
290
+ *
291
+ * Once-handlers are claimed only when dispatch reaches the once phase. Once
292
+ * claimed, the whole snapshot remains consumed even if the predicate changes
293
+ * while one of its handlers is awaiting, matching normal once semantics.
294
+ *
295
+ * @internal
296
+ * @returns `true` when the complete handler sequence ran while eligible.
297
+ */
298
+ emitWhile<E extends keyof TMap>(event: E, payload: TMap[E], shouldContinue: () => boolean): Promise<boolean>;
299
+ /**
300
+ * Number of handlers currently registered for `event`.
301
+ * Sum of `on` and `once` registrations.
302
+ */
303
+ listenerCount(event: keyof TMap): number;
304
+ /**
305
+ * Remove every handler. Pass an event name to limit the removal to
306
+ * one event; omit it to clear every event on this emitter.
307
+ *
308
+ * Crucially this only affects the current instance — there is no
309
+ * shared global state.
310
+ */
311
+ removeAllListeners(event?: keyof TMap): void;
312
+ /**
313
+ * Snapshot of all event names that currently have at least one
314
+ * registered handler. Useful for debugging.
315
+ */
316
+ eventNames(): (keyof TMap)[];
317
+ }
318
+
319
+ /**
320
+ * Core type definitions shared across all runtime primitives.
321
+ *
322
+ * @module @core/types
323
+ */
324
+
325
+ /**
326
+ * Field types recognized by the live preview renderer.
327
+ *
328
+ * Extends Payload's field set with `html` and `url` which are not
329
+ * Payload field types per se but useful DOM-binding categories.
330
+ */
331
+ type FieldType = PayloadFieldType | 'html' | 'url' | 'image' | 'structural-array';
332
+ /**
333
+ * A DOM element that has been registered as a live preview binding.
334
+ *
335
+ * Stored in the element cache indexed by `fieldName`. Multiple cached
336
+ * entries can share a field name when the same field is rendered in
337
+ * several places (e.g., a title shown in both the header and the
338
+ * page body).
339
+ */
340
+ interface CachedElement {
341
+ /** The bound DOM element. */
342
+ readonly element: Element;
343
+ /** Payload field path, e.g., `title` or `hero.subtitle`. */
344
+ readonly fieldName: string;
345
+ /**
346
+ * Resolved field type, used to dispatch to the right renderer.
347
+ *
348
+ * The cache resolves this from DOM attributes (`data-payload-type`,
349
+ * tag-based heuristics) at build time. When `explicitFieldType` is
350
+ * `false` the lifecycle is allowed to override this with the type
351
+ * learned from the schema once `fieldSchemaJSON` arrives.
352
+ */
353
+ readonly fieldType: FieldType;
354
+ /**
355
+ * `true` when the field type was set by an explicit
356
+ * `data-payload-type` attribute (consumer trumps schema), `false`
357
+ * when it was inferred via tag-based heuristics (schema may
358
+ * override later). Optional so synthetic bindings (tests / programmatic
359
+ * `cache.add()` callers) need not supply it; the cache always
360
+ * populates it for DOM-derived bindings.
361
+ */
362
+ readonly explicitFieldType?: boolean;
363
+ /**
364
+ * Optional target attribute: the value is written to this attribute
365
+ * instead of the element's content (from `data-payload-attribute`,
366
+ * or `bind(field, { attribute })`). Writes are policed — event
367
+ * handlers, `style`, and unsafe URLs are refused.
368
+ */
369
+ readonly targetAttribute?: string;
370
+ /** Optional sibling-field path bound to the element's `href` attribute. */
371
+ readonly hrefField?: string;
372
+ /** Optional sibling-field path bound to the element's `src` attribute. */
373
+ readonly srcField?: string;
374
+ /** Optional sibling-field path bound to the element's `alt` attribute. */
375
+ readonly altField?: string;
376
+ /** Optional inline template for array/blocks rendering. */
377
+ readonly arrayTemplate?: string;
378
+ /** Optional separator for primitive-array stringification. */
379
+ readonly arraySeparator?: string;
380
+ /** Optional locale code locked onto this element (overrides the global locale). */
381
+ readonly locale?: string;
382
+ }
383
+ /**
384
+ * Context passed to every field renderer. Provides access to the
385
+ * entire field tree so renderers can resolve sibling fields (e.g.,
386
+ * an `<a>` that pulls `href` from a different field).
387
+ */
388
+ interface RenderContext {
389
+ /** Full field map for the current update — used for sibling lookups. */
390
+ readonly allFields: Record<string, unknown>;
391
+ /** Active locale, or `undefined` when none is established. */
392
+ readonly locale: string | undefined;
393
+ /** Optional schema descriptor for the field, when available. */
394
+ readonly schema: PayloadFieldSchema | undefined;
395
+ }
396
+ /**
397
+ * Contract that every field renderer implements.
398
+ *
399
+ * Renderers are pure DOM-write functions: they receive the cached
400
+ * binding plus the new value and apply it to the element. They must
401
+ * not throw — failures should be silenced and logged so that one bad
402
+ * field cannot stop an entire update. The public renderer result remains
403
+ * `void` for 1.x compatibility. Built-in renderers may use an internal exact
404
+ * `false` sentinel when they deliberately perform no DOM write; callers must
405
+ * not rely on that implementation detail.
406
+ */
407
+ interface FieldRenderer {
408
+ readonly name: FieldType;
409
+ render(target: CachedElement, value: unknown, context: RenderContext): void;
410
+ }
411
+
412
+ /**
413
+ * Plugin system types.
414
+ *
415
+ * Each plugin participates in the live-preview lifecycle through a
416
+ * scoped, per-instance context. Plugins can register custom field
417
+ * renderers, transform incoming values, and subscribe to events —
418
+ * the same primitives the built-in renderers use.
419
+ *
420
+ * The system is intentionally minimal. Anything that can be expressed
421
+ * with an event subscription belongs in `events`, not `plugins`.
422
+ *
423
+ * @module @plugins/types
424
+ */
425
+
426
+ /** Idempotent handle that releases one plugin-owned resource. */
427
+ type PluginDisposer = () => void;
428
+ /**
429
+ * Event subscriptions scoped to one plugin registration.
430
+ *
431
+ * This remains nominally compatible with the pre-1.0.4 `EventEmitter` context
432
+ * type. Its methods are registration-scoped: even bulk removal affects only
433
+ * this plugin's subscriptions, never a consumer's or another plugin's
434
+ * listeners.
435
+ */
436
+ interface PluginEvents extends EventEmitter {
437
+ readonly on: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => Unsubscribe;
438
+ readonly once: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => Unsubscribe;
439
+ readonly off: <E extends keyof LivePreviewEventMap>(event: E, handler: EventHandler<LivePreviewEventMap[E]>) => void;
440
+ /** Emit through the owning client's event channel. */
441
+ readonly emit: <E extends keyof LivePreviewEventMap>(event: E, payload: LivePreviewEventMap[E]) => Promise<void>;
442
+ /**
443
+ * Guarded emit through the owning client channel. Dispatch stops when the
444
+ * caller predicate becomes false or this plugin registration is removed.
445
+ */
446
+ readonly emitWhile: <E extends keyof LivePreviewEventMap>(event: E, payload: LivePreviewEventMap[E], shouldContinue: () => boolean) => Promise<boolean>;
447
+ /** Number of listeners owned by this plugin registration. */
448
+ readonly listenerCount: (event: keyof LivePreviewEventMap) => number;
449
+ /** Remove only listeners owned by this plugin registration. */
450
+ readonly removeAllListeners: (event?: keyof LivePreviewEventMap) => void;
451
+ /** Event names for which this plugin registration owns listeners. */
452
+ readonly eventNames: () => (keyof LivePreviewEventMap)[];
453
+ }
454
+ /**
455
+ * Synchronous function that transforms a merged field value while a revision's
456
+ * per-binding scheduler entry is prepared. Transforms run in registration
457
+ * order and each receives the preceding transform's result. The final value is
458
+ * frozen into that entry before later renderer or attribute dispatch;
459
+ * `allFields` remains the merged, untransformed update snapshot.
460
+ *
461
+ * Returning a different shape than the input is allowed, but returning a
462
+ * Promise or other thenable is a contract error. If a transform throws or
463
+ * returns a thenable, the chain stops, the runtime emits an `error` event, and
464
+ * dispatch falls back to the original merged value. That fallback still passes
465
+ * through the normal renderer and attribute security controls.
466
+ */
467
+ type FieldTransform = (value: unknown, context: {
468
+ readonly fieldName: string;
469
+ readonly element: Element;
470
+ readonly allFields: Record<string, unknown>;
471
+ }) => unknown;
472
+ /**
473
+ * Per-instance plugin context. Replaces the legacy singleton context.
474
+ *
475
+ * Plugins receive a context that is bound to the parent
476
+ * `LivePreviewClient`. They can:
477
+ *
478
+ * - subscribe via `events.on/once`,
479
+ * - register field renderers,
480
+ * - register value transforms,
481
+ * - read the client's (frozen) configuration,
482
+ * - log through the client's debug channel.
483
+ */
484
+ interface PluginContext {
485
+ readonly events: PluginEvents;
486
+ /** Register a renderer owned by this plugin registration. */
487
+ readonly registerFieldRenderer: (renderer: FieldRenderer) => void;
488
+ /** Register an ordered transform owned by this plugin registration. */
489
+ readonly registerTransform: (fieldName: string, transform: FieldTransform) => void;
490
+ /**
491
+ * Register any additional synchronous cleanup owned by this registration.
492
+ * The manager invokes it during rollback, `unuse()`, or client destruction.
493
+ * Optional so pre-1.0.4 structural context mocks remain assignable.
494
+ */
495
+ readonly registerCleanup?: (cleanup: PluginDisposer) => void;
496
+ readonly getConfig: () => Readonly<Record<string, unknown>>;
497
+ readonly log: (...args: unknown[]) => void;
498
+ }
499
+ /**
500
+ * Plugin definition. `init` runs once per registration; `destroy`
501
+ * runs when the plugin is unregistered or the client is destroyed.
502
+ *
503
+ * Both hooks may return a promise; the manager awaits them.
504
+ */
505
+ interface LivePreviewPlugin {
506
+ readonly name: string;
507
+ readonly version?: string;
508
+ readonly init: (context: PluginContext) => void | Promise<void>;
509
+ readonly destroy?: () => void | Promise<void>;
510
+ }
511
+
512
+ /**
513
+ * `LivePreviewClient` configuration shape.
514
+ *
515
+ * @module @client/config
516
+ */
517
+ interface LivePreviewClientConfig {
518
+ /** Explicit additional trusted origins. */
519
+ readonly allowedOrigins?: readonly string[];
520
+ /**
521
+ * Payload server origin, e.g. `https://cms.example.com`. When set,
522
+ * every incoming update is re-fetched through the Payload REST API
523
+ * so relationship/upload fields render populated instead of as bare
524
+ * IDs (same strategy as the official client). Recommended for
525
+ * Payload 3.x.
526
+ */
527
+ readonly serverURL?: string;
528
+ /** REST API route prefix used with `serverURL`. Defaults to `/api`. */
529
+ readonly apiRoute?: string;
530
+ /** Population depth used with `serverURL`. Defaults to `1`. */
531
+ readonly mergeDepth?: number;
532
+ /**
533
+ * Custom fetch implementation for the `serverURL` merge request —
534
+ * the equivalent of the official client's `requestHandler`. Use it
535
+ * to attach auth headers or route through your own proxy. Only
536
+ * available on the programmatic client (functions cannot be
537
+ * serialised into the inline script).
538
+ */
539
+ readonly mergeFetch?: typeof fetch;
540
+ /** Enable verbose console debug output. Defaults to dev-mode detection. */
541
+ readonly debug?: boolean;
542
+ /** Debounce window for incoming updates. Defaults to 50 ms. */
543
+ readonly debounceMs?: number;
544
+ /**
545
+ * Heartbeat timeout in ms. Defaults to `0` (disabled) because the
546
+ * Payload admin sends messages only on form edits — there is no
547
+ * protocol keepalive, so an idle timeout would produce false
548
+ * disconnects.
549
+ */
550
+ readonly heartbeatMs?: number;
551
+ /** IntersectionObserver rootMargin. Defaults to `200px`. */
552
+ readonly intersectionRootMargin?: string;
553
+ /** Bypass the visibility gate (apply every update). Defaults to `false`. */
554
+ readonly disableVisibilityGate?: boolean;
555
+ /** Cache-size threshold above which off-screen updates are queued for replay. Defaults to 50. */
556
+ readonly visibilityGateThreshold?: number;
557
+ /**
558
+ * Mount an `aria-live` region for connections, applied updates, and mounted
559
+ * heartbeat-timeout disconnects. Destroy releases it synchronously. Default `true`.
560
+ */
561
+ readonly enableA11y?: boolean;
562
+ /** Locale used to pick A11y announcement strings. Defaults to detected locale. */
563
+ readonly a11yLocale?: string;
564
+ /** Document root, defaults to `document`. */
565
+ readonly root?: Document | Element;
566
+ /** Disable `document.referrer` detection. */
567
+ readonly disableReferrerDetection?: boolean;
568
+ /** Disable the localhost dev-mode pattern matcher. */
569
+ readonly disableLocalhostMatching?: boolean;
570
+ /** Auto-start the runtime in the constructor. Defaults to `true`. */
571
+ readonly autoStart?: boolean;
572
+ /**
573
+ * Optional preview-token validator. When set, every data update
574
+ * must carry a `previewToken` field that this function approves.
575
+ *
576
+ * ⚠️ `previewToken` is an extension of THIS library, not part of the
577
+ * stock Payload postMessage protocol — the Payload admin never sends
578
+ * one. Only enable this when a custom admin component adds the token
579
+ * to outgoing messages; against an unmodified Payload admin it would
580
+ * drop every update.
581
+ */
582
+ readonly validateToken?: (token: string | undefined, origin: string) => boolean | Promise<boolean>;
583
+ }
584
+
585
+ /**
586
+ * Public, high-level `LivePreviewClient`.
587
+ *
588
+ * The client wraps a `LivePreviewRuntime` with:
589
+ *
590
+ * - Per-instance event emitter (no singleton).
591
+ * - Per-instance plugin manager.
592
+ * - Custom-renderer registration.
593
+ * - Field-name transforms frozen into revision-bound scheduler entries before
594
+ * their later attribute or renderer dispatch.
595
+ *
596
+ * Consumers who want full programmatic control instantiate
597
+ * `LivePreviewClient` directly. Consumers who just want a working
598
+ * inline preview should use `generateInlineScript()` instead.
599
+ *
600
+ * @module @client
601
+ */
602
+
603
+ declare class LivePreviewClient {
604
+ #private;
605
+ constructor(config?: LivePreviewClientConfig);
606
+ /**
607
+ * Start the runtime. Returns `true` when this client is eligible and runtime
608
+ * startup is active or scheduled for DOM readiness. Repeated calls on an
609
+ * active client also return `true`; `false` means the client was destroyed or
610
+ * the page is outside a preview context. A deferred startup can still fail,
611
+ * roll back, and be retried by a later call.
612
+ */
613
+ start(): boolean;
614
+ /**
615
+ * Stop the runtime and tear down every plugin. Idempotent.
616
+ */
617
+ destroy(): Promise<void>;
618
+ /**
619
+ * Register a plugin.
620
+ */
621
+ use(plugin: LivePreviewPlugin): Promise<void>;
622
+ /** Unregister a plugin and release all resources owned by that registration. */
623
+ unuse(name: string): Promise<void>;
624
+ /** Rebuild the element cache manually. */
625
+ refreshCache(): void;
626
+ /** Read-only access to the event emitter for `on`/`once`/`off`. */
627
+ get events(): EventEmitter;
628
+ /** Names of currently registered plugins. */
629
+ get plugins(): readonly string[];
630
+ /** Current connection status. */
631
+ get status(): 'disconnected' | 'connecting' | 'connected';
632
+ /** Number of valid updates received so far. */
633
+ get updateCount(): number;
634
+ /** `true` once `destroy()` has been called. */
635
+ get destroyed(): boolean;
636
+ }
637
+ /**
638
+ * Convenience factory: instantiate the client, returning `null` when
639
+ * the page is not currently a preview context (top-level navigation).
640
+ * Useful for SSR-style integrations that import this from any context.
641
+ */
642
+ declare function initLivePreview(config?: LivePreviewClientConfig): LivePreviewClient | null;
4
643
 
5
644
  interface RuntimeBuildInfo {
6
645
  readonly generatedAt: string;
@@ -83,9 +722,11 @@ interface InlineScriptConfig {
83
722
  */
84
723
  readonly disableLocalhostMatching?: boolean;
85
724
  /**
86
- * Optional CSP nonce for the generated `<script>` tag. When provided
87
- * via `wrapWithScriptTag()` the nonce is set on the tag. Useful for
88
- * strict CSP policies that disallow `unsafe-inline`.
725
+ * Retained for 1.x source compatibility, but has no effect here:
726
+ * `generateInlineScript()` returns a script body and creates no tag.
727
+ * Pass the nonce separately to `wrapWithScriptTag(body, { nonce })`.
728
+ *
729
+ * @deprecated Use the second argument of `wrapWithScriptTag()`.
89
730
  */
90
731
  readonly nonce?: string;
91
732
  }
@@ -110,33 +751,354 @@ declare function wrapWithScriptTag(body: string, options?: {
110
751
  */
111
752
  declare function runtimeBuildInfo(): RuntimeBuildInfo;
112
753
 
754
+ /**
755
+ * HTML-escape primitives.
756
+ *
757
+ * Implementations follow the OWASP XSS Prevention Cheat Sheet: each
758
+ * special character is replaced with its decimal or hex entity. The
759
+ * functions are isomorphic — they have no dependency on the DOM and
760
+ * can run in any JavaScript environment (browser, Node, Deno, Bun).
761
+ *
762
+ * @module @security/escape
763
+ */
764
+ /**
765
+ * Escape every HTML-significant character in `text` so the result is
766
+ * safe to interpolate into any HTML context (element body or attribute).
767
+ *
768
+ * - Coerces non-strings via `String(value)`; pass `null`/`undefined`
769
+ * intentionally — they become `"null"`/`"undefined"`.
770
+ * - Cannot fail; returns `""` for the empty input.
771
+ */
772
+ declare function escapeHtml(text: string): string;
773
+ /**
774
+ * Escape characters that would break out of a double-quoted HTML
775
+ * attribute value. Stricter than `escapeHtml` for body content but
776
+ * preserves URL characters (`/`, `=`, etc.) that are common in
777
+ * `href` and `src` values.
778
+ *
779
+ * Only call this for values that have already been validated as safe
780
+ * URLs or trusted strings; it does NOT block dangerous protocols.
781
+ */
782
+ declare function escapeHtmlAttribute(value: string): string;
783
+
784
+ /**
785
+ * URL safety validation.
786
+ *
787
+ * Single source of truth for URL-protocol decisions used by both the
788
+ * inline runtime and the high-level client. The previous version of
789
+ * this library had two implementations that subtly disagreed; this
790
+ * module replaces both.
791
+ *
792
+ * Policy:
793
+ * - Allow only the protocols in `SAFE_PROTOCOLS`.
794
+ * - Reject every other protocol explicitly, including `javascript:`,
795
+ * `data:`, `vbscript:`, `file:`, `blob:`, and any custom scheme.
796
+ * - Empty strings are NOT considered safe (this is a tightening from
797
+ * the legacy behaviour and avoids producing `<a href="">` markup).
798
+ *
799
+ * @module @security/url-validator
800
+ */
801
+ /**
802
+ * Returns `true` only when `url` is one of the explicitly allowed forms:
803
+ *
804
+ * - Absolute URL with `http:`, `https:`, `mailto:`, or `tel:` protocol
805
+ * - Protocol-relative URL (`//example.com/...`)
806
+ * - Same-origin absolute path (`/foo`)
807
+ * - Hash or query fragment (`#section`, `?q=1`)
808
+ * - Plain relative path (`foo/bar`, `./foo`, `../foo`)
809
+ */
810
+ declare function isSafeUrl(url: unknown): boolean;
811
+ /**
812
+ * Returns `true` when the URL points to an external HTTP(S) origin.
813
+ * Protocol-relative URLs (`//example.com/...`) count as external —
814
+ * they resolve to another origin and need the same `noopener`
815
+ * hardening as absolute ones.
816
+ *
817
+ * Used by renderers to decide whether to add `target="_blank"` and the
818
+ * `noopener noreferrer` rel attributes to anchors. Inputs that fail
819
+ * `isSafeUrl()` always return `false`.
820
+ */
821
+ declare function isExternalHttpUrl(url: string): boolean;
822
+
823
+ interface SanitizeOptions {
824
+ /** Extra tags to allow beyond the built-in list. Lower-case, untrimmed. */
825
+ readonly additionalAllowedTags?: readonly string[];
826
+ /** Extra per-tag attributes to allow. Tag and attribute names must be lower-case. */
827
+ readonly additionalAllowedAttributes?: Readonly<Record<string, readonly string[]>>;
828
+ }
829
+ /**
830
+ * Minimal `Document`-shaped surface the sanitizer needs. Any DOM
831
+ * implementation (browser, jsdom, linkedom, happy-dom, parse5 + a
832
+ * thin adapter) that produces `<template>`-style elements works.
833
+ */
834
+ interface SanitizerDocument {
835
+ createElement: (tagName: string) => {
836
+ innerHTML: string;
837
+ readonly content: ParentNode;
838
+ };
839
+ }
840
+ /**
841
+ * Inject a `Document` implementation for the sanitizer to use when
842
+ * `globalThis.document` is unavailable.
843
+ *
844
+ * Server-side renderers (Node, Bun, Deno without DOM globals) can wire
845
+ * this up once at startup with any of the popular pure-JS DOM
846
+ * libraries:
847
+ *
848
+ * ```ts
849
+ * // linkedom — recommended (smallest, fastest)
850
+ * import { parseHTML } from 'linkedom';
851
+ * import { setSanitizerDocument } from 'payload-live-preview';
852
+ * const { document } = parseHTML('<!doctype html><html><body></body></html>');
853
+ * setSanitizerDocument(document);
854
+ * ```
855
+ *
856
+ * ```ts
857
+ * // jsdom — heavier, but already in many SSR pipelines
858
+ * import { JSDOM } from 'jsdom';
859
+ * setSanitizerDocument(new JSDOM().window.document);
860
+ * ```
861
+ *
862
+ * Pass `null` to clear a previous override (mostly useful for tests).
863
+ */
864
+ declare function setSanitizerDocument(doc: SanitizerDocument | null): void;
865
+ /**
866
+ * Sanitize `html` and return safe HTML.
867
+ *
868
+ * The function never returns `undefined`/`null`. On malformed input the
869
+ * browser parser silently recovers and the sanitizer continues from
870
+ * the recovered tree.
871
+ *
872
+ * @throws {SanitizerEnvironmentError} when no DOM is available.
873
+ */
874
+ declare function sanitizeHtml(html: string, options?: SanitizeOptions): string;
875
+
876
+ /**
877
+ * Content-Security-Policy helpers.
878
+ *
879
+ * Two responsibilities:
880
+ *
881
+ * 1. Generate cryptographic nonces so consumers can opt into a strict
882
+ * `script-src 'nonce-...'` policy and avoid `'unsafe-inline'`.
883
+ * 2. Build the `frame-ancestors` directive value, restricting which
884
+ * origins may embed the preview page in an iframe.
885
+ *
886
+ * Both functions are isomorphic — they work in browsers, Node, and
887
+ * edge runtimes that expose Web Crypto via `globalThis.crypto`.
888
+ *
889
+ * @module @security/csp
890
+ */
891
+ /**
892
+ * Minimal Web-Crypto surface area we actually call. The full `Crypto`
893
+ * type drags in browser-only declarations that don't typecheck cleanly
894
+ * against Node's `webcrypto` shape — this narrower interface accepts
895
+ * both without an unsafe cast.
896
+ */
897
+ interface WebCryptoLike {
898
+ getRandomValues: <T extends ArrayBufferView | null>(array: T) => T;
899
+ }
900
+ /**
901
+ * Inject a Web Crypto implementation to use when `globalThis.crypto`
902
+ * is unavailable.
903
+ *
904
+ * Node.js exposed `globalThis.crypto` globally starting with v19. On
905
+ * Node 18 (still LTS at time of writing) consumers can flip the
906
+ * `--experimental-global-webcrypto` flag *or* call this function once
907
+ * at startup:
908
+ *
909
+ * ```ts
910
+ * import { webcrypto } from 'node:crypto';
911
+ * import { setCspCrypto } from 'payload-live-preview';
912
+ * setCspCrypto(webcrypto as { getRandomValues: typeof webcrypto.getRandomValues });
913
+ * ```
914
+ *
915
+ * Browser bundles never need to call this — `globalThis.crypto` is
916
+ * always present there, and the override only matters when the global
917
+ * is missing.
918
+ *
919
+ * Pass `null` to clear a previous override (mostly useful for tests).
920
+ */
921
+ declare function setCspCrypto(crypto: WebCryptoLike | null): void;
922
+ /**
923
+ * Generate a base64url-encoded cryptographic nonce.
924
+ *
925
+ * Uses `crypto.getRandomValues` everywhere — falls back to throwing
926
+ * if no Web Crypto implementation is available, because a predictable
927
+ * nonce would silently defeat the entire CSP.
928
+ *
929
+ * @param bytes Number of random bytes (defaults to 16 → 128-bit entropy).
930
+ */
931
+ declare function generateCspNonce(bytes?: number): string;
932
+ interface FrameAncestorsOptions {
933
+ readonly self?: boolean;
934
+ readonly origins?: readonly string[];
935
+ readonly allowNone?: boolean;
936
+ }
937
+ /**
938
+ * Build the value for `frame-ancestors` in a `Content-Security-Policy`
939
+ * header.
940
+ *
941
+ * Returns `'none'` if `allowNone: true` and no other sources are
942
+ * provided. Otherwise concatenates `'self'` (if requested) with the
943
+ * given origins, deduplicating and trimming entries.
944
+ *
945
+ * @example
946
+ * `frame-ancestors ${buildFrameAncestors({ self: true, origins: [adminUrl] })}`
947
+ */
948
+ declare function buildFrameAncestors(options?: FrameAncestorsOptions): string;
949
+ /**
950
+ * Build a `script-src` value that permits a single nonce plus optional
951
+ * extra sources.
952
+ *
953
+ * `'strict-dynamic'` is **opt-in**: under CSP 3 it makes browsers
954
+ * ignore `'self'` and every host source in the directive, so any
955
+ * parser-inserted script without a nonce (framework hydration scripts,
956
+ * consumer `<script>` tags) would be blocked. Only enable it when every
957
+ * script on the page carries the nonce.
958
+ */
959
+ declare function buildScriptSrcWithNonce(nonce: string, options?: {
960
+ readonly self?: boolean;
961
+ readonly extra?: readonly string[];
962
+ readonly strictDynamic?: boolean;
963
+ }): string;
964
+ /**
965
+ * How a directive is merged into an existing CSP header value.
966
+ *
967
+ * - `union` (default) — the new sources are added to the existing
968
+ * directive's sources (deduplicated). An existing `'none'` is
969
+ * dropped when real sources are added, since `'none'` may not be
970
+ * combined with other sources.
971
+ * - `replace` — the new value overwrites the existing directive.
972
+ */
973
+ interface CspDirectiveMerge {
974
+ readonly value: string;
975
+ readonly mode?: 'union' | 'replace';
976
+ }
977
+ /**
978
+ * Merge directives into an existing `Content-Security-Policy` header
979
+ * value without clobbering what a consumer (or another middleware)
980
+ * already configured. Directives not mentioned in `additions` pass
981
+ * through untouched; new directives are appended. Existing policy parsing
982
+ * follows CSP3's ASCII-whitespace and first-duplicate-wins rules.
983
+ *
984
+ * This is the single CSP-merge implementation shared by every adapter.
985
+ */
986
+ declare function mergeCspHeader(existing: string, additions: Readonly<Record<string, string | CspDirectiveMerge>>): string;
987
+
988
+ /**
989
+ * Server-side preview-intent detection.
990
+ *
991
+ * The live-preview runtime only ever activates inside the Payload
992
+ * admin's iframe, but the *server* still has to decide whether a
993
+ * request expresses preview intent before doing optional preview
994
+ * work. Adapters use this predicate as a delivery optimisation, and
995
+ * consumers with hand-rolled middleware can import it directly.
996
+ *
997
+ * IMPORTANT: every signal inspected here is controlled or triggerable
998
+ * by the client. A positive result is never proof of identity,
999
+ * authentication, or permission to read drafts. Before forwarding
1000
+ * credentials, fetching draft data, bypassing caches, changing CSP,
1001
+ * or injecting the runtime, verify the request with an application-
1002
+ * owned server session or a short-lived signed authorization. Use the
1003
+ * same verified decision for all of those effects.
1004
+ *
1005
+ * Preview intent is present when any of these hold:
1006
+ *
1007
+ * 1. A preview query parameter is present (`?preview=true`,
1008
+ * `?draft=true`, `?livePreview=true` — configurable).
1009
+ * 2. `Sec-Fetch-Dest: iframe` — the document is being loaded as an
1010
+ * iframe, which is exactly how the Payload admin embeds it.
1011
+ * (Sent by all evergreen browsers; absence never *excludes* a
1012
+ * request, presence includes it.)
1013
+ * 3. The `Referer` points at one of the given admin origins.
1014
+ *
1015
+ * @module @adapters/shared/preview-request
1016
+ */
1017
+ /** The individual signals the predicate can consider. */
1018
+ type PreviewSignal = 'query' | 'fetch-dest' | 'referer';
1019
+ interface PreviewRequestOptions {
1020
+ /**
1021
+ * Which signals count as preview intent. Default: all three.
1022
+ * Restricting this to `['query']` reduces accidental activation, but
1023
+ * an explicit `?preview=true` is still client-controlled and must not
1024
+ * authorize response changes or privileged data access by itself.
1025
+ */
1026
+ readonly signals?: readonly PreviewSignal[];
1027
+ /**
1028
+ * Query parameters (with values `true` or `1`) that signal preview
1029
+ * intent. Default: `['preview', 'draft', 'livePreview']`.
1030
+ */
1031
+ readonly queryParams?: readonly string[];
1032
+ /**
1033
+ * Treat `Sec-Fetch-Dest: iframe` as a preview-intent signal. Default `true`.
1034
+ * Legacy alias for excluding `'fetch-dest'` from `signals`.
1035
+ */
1036
+ readonly checkFetchDest?: boolean;
1037
+ /**
1038
+ * Admin origins whose `Referer` signals preview intent, e.g.
1039
+ * `['https://cms.example.com']`. Referer matching is not HTTP-request
1040
+ * authentication.
1041
+ */
1042
+ readonly adminOrigins?: readonly string[];
1043
+ }
1044
+ /**
1045
+ * The minimal request surface the predicate needs. The standard
1046
+ * `Request` satisfies it structurally, and server frameworks without
1047
+ * fetch-style requests (e.g. Nitro/H3) can supply a tiny shim.
1048
+ */
1049
+ interface PreviewRequestLike {
1050
+ readonly url: string;
1051
+ readonly headers: {
1052
+ get(name: string): string | null;
1053
+ };
1054
+ }
1055
+ /**
1056
+ * Detect whether `request` carries live-preview intent. See the module
1057
+ * docblock for the exact signals and the required authorization
1058
+ * boundary. This compatibility name does not imply authentication.
1059
+ */
1060
+ declare function isPreviewRequest(request: PreviewRequestLike, options?: PreviewRequestOptions): boolean;
1061
+
113
1062
  /**
114
1063
  * Draft-aware document fetching for the initial preview render.
115
1064
  *
116
1065
  * The live-preview runtime patches the DOM *after* the page loads —
117
1066
  * the initial server render is the consumer's job. With Payload
118
- * drafts enabled, a preview must render the **draft** version on
119
- * first load, otherwise editors see stale published content until
120
- * their first keystroke.
1067
+ * drafts enabled, an **authorized** preview should render the draft
1068
+ * version on first load, otherwise editors see stale published
1069
+ * content until their first keystroke.
121
1070
  *
122
- * These helpers wrap the two REST queries every preview loader needs,
123
- * with the right flags (`draft=true`, `depth`) and an explicit nudge
124
- * to keep `depth` in sync with the runtime's `mergeDepth` — a
125
- * mismatch makes populated relationships degrade to IDs after the
126
- * first edit.
1071
+ * These helpers wrap the corresponding REST queries with `draft` and
1072
+ * `depth` options and an explicit nudge to keep `depth` in sync with
1073
+ * the runtime's `mergeDepth` — a mismatch makes populated
1074
+ * relationships degrade to IDs after the first edit.
1075
+ *
1076
+ * These helpers do not authenticate or authorize requests.
1077
+ * `isPreviewRequest()` detects client-controlled preview intent only.
1078
+ * Verify an application-owned server session or short-lived signed
1079
+ * authorization first, and use that one decision to gate `draft`, any
1080
+ * privileged headers, cache bypass, CSP changes, and runtime injection.
1081
+ * Never attach a long-lived service/API key merely because
1082
+ * `isPreviewRequest()` returned `true`.
127
1083
  *
128
1084
  * Isomorphic and dependency-free: works in Astro frontmatter,
129
1085
  * SvelteKit `load`, Next.js server components, or any Node/edge
130
1086
  * runtime with `fetch`.
131
1087
  *
132
1088
  * ```ts
133
- * // Astro frontmatter
1089
+ * // Astro frontmatter. `verifyAppPreviewSession` is application-owned,
1090
+ * // server-only code; it validates the request and returns only the
1091
+ * // minimal Payload session headers needed for this request.
1092
+ * const hasPreviewIntent = isPreviewRequest(Astro.request);
1093
+ * const authorization = hasPreviewIntent
1094
+ * ? await verifyAppPreviewSession(Astro.request)
1095
+ * : null;
134
1096
  * const page = await fetchPreviewDocument<Page>({
135
1097
  * serverURL: import.meta.env.PAYLOAD_URL,
136
1098
  * collection: 'pages',
137
1099
  * where: { slug: { equals: Astro.params.slug } },
138
- * draft: isPreviewRequest(Astro.request),
139
- * headers: { Authorization: `users API-Key ${import.meta.env.PAYLOAD_PREVIEW_KEY}` },
1100
+ * draft: authorization !== null,
1101
+ * ...(authorization === null ? {} : { headers: authorization.payloadHeaders }),
140
1102
  * });
141
1103
  * ```
142
1104
  *
@@ -155,16 +1117,18 @@ interface PreviewFetchBaseOptions {
155
1117
  */
156
1118
  readonly depth?: number;
157
1119
  /**
158
- * Fetch the draft version. Defaults to `true` (this is a *preview*
159
- * fetch). Pass the result of `isPreviewRequest(request)` to make
160
- * the same loader serve published content to normal traffic.
1120
+ * Fetch the draft version. Defaults to `true` for 1.x compatibility.
1121
+ * Set this from a verified authorization decision, not directly
1122
+ * from `isPreviewRequest()`, whose result only expresses intent.
161
1123
  */
162
1124
  readonly draft?: boolean;
163
1125
  /** Locale to fetch. */
164
1126
  readonly locale?: string;
165
1127
  /**
166
- * Extra request headers — typically auth, since draft reads require
167
- * an authenticated user (e.g. `Authorization: users API-Key …`).
1128
+ * Extra request headers. Supply only credentials derived from the
1129
+ * current request's verified server-side session (or another
1130
+ * short-lived, scoped authorization). Do not attach a long-lived
1131
+ * service/API key based on a preview-intent signal.
168
1132
  */
169
1133
  readonly headers?: Readonly<Record<string, string>>;
170
1134
  /** Injectable fetch implementation (tests, custom agents). */
@@ -188,13 +1152,15 @@ interface FetchPreviewGlobalOptions extends PreviewFetchBaseOptions {
188
1152
  readonly global: string;
189
1153
  }
190
1154
  /**
191
- * Fetch a single collection document (draft-first by default).
1155
+ * Fetch a single collection document (draft-first by default for 1.x
1156
+ * compatibility; callers must authorize draft access).
192
1157
  * Returns `null` when nothing matches or the request fails —
193
1158
  * loaders should fall back to their regular data path or a 404.
194
1159
  */
195
1160
  declare function fetchPreviewDocument<T = Record<string, unknown>>(options: FetchPreviewDocumentOptions): Promise<T | null>;
196
1161
  /**
197
- * Fetch a global (draft-first by default). Returns `null` on failure.
1162
+ * Fetch a global (draft-first by default for 1.x compatibility;
1163
+ * callers must authorize draft access). Returns `null` on failure.
198
1164
  */
199
1165
  declare function fetchPreviewGlobal<T = Record<string, unknown>>(options: FetchPreviewGlobalOptions): Promise<T | null>;
200
1166
 
@@ -360,6 +1326,15 @@ declare function createAnalyticsPlugin(): LivePreviewPlugin & {
360
1326
  */
361
1327
 
362
1328
  type DocumentSaveStrategy = 'silent' | 'reload' | 'revalidate' | 'fetch';
1329
+ /**
1330
+ * A fetch strategy handler. The optional parameter preserves calls through
1331
+ * pre-1.0.4 option objects (`options.handler?.()`), while method bivariance
1332
+ * also accepts explicitly signal-aware handlers. The built-in plugin always
1333
+ * supplies a live `AbortSignal`.
1334
+ */
1335
+ type DocumentSaveHandler = {
1336
+ bivarianceHack(signal?: AbortSignal): void | Promise<void>;
1337
+ }['bivarianceHack'];
363
1338
  interface DocumentSavePluginOptions {
364
1339
  /** Strategy to invoke on each `documentSave` event. Default `'silent'`. */
365
1340
  readonly strategy?: DocumentSaveStrategy;
@@ -375,9 +1350,11 @@ interface DocumentSavePluginOptions {
375
1350
  readonly revalidateHeaders?: Readonly<Record<string, string>>;
376
1351
  /**
377
1352
  * For `'fetch'`: async handler called on each event. The plugin
378
- * awaits it; rejections are logged but don't crash the runtime.
1353
+ * awaits it; rejections are logged but don't crash the runtime. The signal
1354
+ * aborts when the plugin is removed. Existing zero-argument handlers remain
1355
+ * compatible because they may ignore the argument.
379
1356
  */
380
- readonly handler?: () => void | Promise<void>;
1357
+ readonly handler?: DocumentSaveHandler;
381
1358
  /**
382
1359
  * Optional fallback strategy when `'revalidate'` POST fails (network
383
1360
  * error or non-2xx). Set to `'reload'` to hard-refresh the page when
@@ -390,6 +1367,192 @@ interface DocumentSavePluginOptions {
390
1367
  */
391
1368
  declare function documentSavePlugin(options?: DocumentSavePluginOptions): LivePreviewPlugin;
392
1369
 
1370
+ /**
1371
+ * Environment introspection helpers.
1372
+ *
1373
+ * The detection layer needs to know whether the runtime is currently
1374
+ * running inside an iframe, in a popup, in development mode, and
1375
+ * whether environment variables are accessible. None of these checks
1376
+ * is dramatic on its own, but having a single, tested module avoids
1377
+ * the duplicated, subtly-different `typeof process` snippets that
1378
+ * accumulated in the legacy implementation.
1379
+ *
1380
+ * @module @detection/environment
1381
+ */
1382
+ /**
1383
+ * Returns `true` when the current window is inside an iframe.
1384
+ *
1385
+ * Uses a try/catch around `window.self !== window.top` because the
1386
+ * comparison throws in cross-origin iframes — and that very throw is
1387
+ * itself a definitive "yes, I am in an iframe" signal.
1388
+ */
1389
+ declare function isInIframe(): boolean;
1390
+ /**
1391
+ * Returns `true` when the current window was opened via `window.open()`
1392
+ * and the opener reference is still accessible.
1393
+ */
1394
+ declare function isInPopup(): boolean;
1395
+ /**
1396
+ * Returns `true` when running in a preview-capable context: iframe or
1397
+ * popup. Anything else (top-level navigation) should skip live preview
1398
+ * initialisation entirely.
1399
+ */
1400
+ declare function isInPreviewContext(): boolean;
1401
+ /**
1402
+ * Returns `true` when the runtime appears to be in development mode.
1403
+ *
1404
+ * Detection order:
1405
+ * 1. `process.env.NODE_ENV !== 'production'`
1406
+ * 2. `import.meta.env.DEV === true` (Vite / Astro / SvelteKit)
1407
+ * 3. `window.location.hostname` is `localhost`/`127.0.0.1`
1408
+ *
1409
+ * Returns `false` when none of those are available.
1410
+ */
1411
+ declare function isDevMode(): boolean;
1412
+
1413
+ /**
1414
+ * Origin detection and matching.
1415
+ *
1416
+ * Builds the set of trusted Payload admin origins and exposes a fast
1417
+ * matcher used by the message bus. Three sources, in precedence order:
1418
+ *
1419
+ * 1. Explicit origins passed via configuration. Always trusted.
1420
+ * 2. `document.referrer` — **only when no explicit origins are
1421
+ * configured**. The browser writes the embedder's origin here
1422
+ * when the iframe loads; using it as a fallback gives a
1423
+ * zero-config path, but it must never widen an explicit
1424
+ * allow-list (the referrer is whoever framed the page, which in
1425
+ * an attack is the attacker).
1426
+ * 3. Localhost pattern (any port on `localhost` / `127.0.0.1`),
1427
+ * enabled when development mode is detected. Replaces the
1428
+ * legacy hand-rolled port list.
1429
+ *
1430
+ * Once the parent's origin is confirmed by an accepted data-bearing
1431
+ * update, callers can call `lockOrigin()` to drop every other candidate,
1432
+ * making subsequent matches exact. A data-less `ready` handshake does
1433
+ * not lock the detector.
1434
+ *
1435
+ * @module @detection/origin
1436
+ */
1437
+ interface OriginDetectorOptions {
1438
+ /** Explicit additional origins to trust. Always allowed. */
1439
+ readonly additionalOrigins?: readonly string[];
1440
+ /** Set to `false` to opt out of `document.referrer` detection. */
1441
+ readonly enableReferrerDetection?: boolean;
1442
+ /** Set to `false` to opt out of localhost-pattern matching. */
1443
+ readonly enableLocalhostMatching?: boolean;
1444
+ /**
1445
+ * Override the dev-mode detection. When `undefined`, dev mode is
1446
+ * inferred from the runtime (see `isDevMode`).
1447
+ */
1448
+ readonly forceDevMode?: boolean;
1449
+ /** Override `document.referrer` for tests. */
1450
+ readonly referrer?: string;
1451
+ }
1452
+ /**
1453
+ * Holds the resolved origin policy and exposes a matcher.
1454
+ *
1455
+ * The detector is stateful: it starts with the union of all candidate
1456
+ * sources, then narrows down to a single, verified origin once the
1457
+ * lifecycle calls `lockOrigin()` for its first accepted data-bearing
1458
+ * update.
1459
+ */
1460
+ declare class OriginDetector {
1461
+ #private;
1462
+ constructor(options?: OriginDetectorOptions);
1463
+ /**
1464
+ * Returns `true` when `origin` is trusted under the current policy.
1465
+ *
1466
+ * If an origin has been locked, only that exact value matches.
1467
+ * Otherwise explicit origins and (in dev mode) localhost-pattern
1468
+ * matches are accepted. The referrer-derived origin is a **fallback,
1469
+ * not a union member**: it only counts when NO explicit origins are
1470
+ * configured. `document.referrer` always names whoever actually
1471
+ * framed the page — if it were unioned with the explicit list, any
1472
+ * site embedding the page in an iframe would become a trusted
1473
+ * postMessage sender despite the consumer having pinned the admin
1474
+ * origin explicitly.
1475
+ */
1476
+ matches(origin: string): boolean;
1477
+ /**
1478
+ * Narrow the trusted set to a single origin after an accepted
1479
+ * data-bearing update. Subsequent calls to `matches()` only allow
1480
+ * `origin`.
1481
+ *
1482
+ * Returns `true` if locking succeeded; `false` if the candidate is
1483
+ * not currently considered trusted (which would defeat the purpose
1484
+ * of locking).
1485
+ */
1486
+ lockOrigin(origin: string): boolean;
1487
+ /**
1488
+ * Release the origin lock so any allow-listed origin is trusted
1489
+ * again until the next accepted data-bearing update. The lifecycle
1490
+ * calls this on heartbeat timeout — after the trusted parent disappears,
1491
+ * a different (still-allowed) origin should be free to reconnect.
1492
+ *
1493
+ * Returns the previously-locked origin, or `undefined` when nothing
1494
+ * was locked.
1495
+ */
1496
+ unlockOrigin(): string | undefined;
1497
+ /** The currently locked origin, or `undefined`. */
1498
+ get lockedOrigin(): string | undefined;
1499
+ /**
1500
+ * Snapshot of every origin currently allowed by the policy. Used by
1501
+ * the message bus to broadcast `ready` messages.
1502
+ *
1503
+ * Localhost pattern matches are expanded into the common dev ports
1504
+ * so the handshake can find a Payload running on its usual port.
1505
+ */
1506
+ enumerate(): string[];
1507
+ /**
1508
+ * Reports whether `document.referrer` contributed at least one
1509
+ * origin. Useful for surfacing a console warning when running in
1510
+ * production without an explicit origin and the referrer is absent
1511
+ * (because of a `Referrer-Policy: no-referrer` header).
1512
+ */
1513
+ get referrerWasAvailable(): boolean;
1514
+ /**
1515
+ * Indicates whether the detector is operating in a "production-without-
1516
+ * explicit-origin" configuration. When `true`, the host should log a
1517
+ * loud warning — silent connection failure is the worst outcome.
1518
+ */
1519
+ get isProductionUnconfigured(): boolean;
1520
+ /**
1521
+ * `true` when the referrer is the ONLY trust source (no explicit
1522
+ * origins, no localhost matching). In that mode any site that frames
1523
+ * the page becomes a trusted postMessage sender — `document.referrer`
1524
+ * always names the actual embedder. Content is still sanitized, but
1525
+ * the origin allow-list no longer restricts who may drive it. Hosts
1526
+ * should surface a warning suggesting explicit `allowedOrigins` and
1527
+ * a `frame-ancestors` policy in production.
1528
+ */
1529
+ get isReferrerOnlyTrust(): boolean;
1530
+ }
1531
+
1532
+ /**
1533
+ * Locale detection.
1534
+ *
1535
+ * The runtime needs a sensible default locale for date/number renders
1536
+ * before Payload tells us the active locale. Order of precedence:
1537
+ *
1538
+ * 1. The `lang` attribute on `<html>`.
1539
+ * 2. `navigator.language`.
1540
+ * 3. The hard-coded fallback `'en'`.
1541
+ *
1542
+ * The previous version of this library defaulted to `'de'` — a German
1543
+ * default that surprised consumers in other locales. We now use the
1544
+ * browser hint and fall back to English.
1545
+ *
1546
+ * @module @detection/locale
1547
+ */
1548
+ /**
1549
+ * Returns a BCP-47 locale string suitable for `Intl.*` constructors.
1550
+ *
1551
+ * Always returns a non-empty string. When no signal is available the
1552
+ * fallback (`'en'`) is returned.
1553
+ */
1554
+ declare function detectInitialLocale(): string;
1555
+
393
1556
  /**
394
1557
  * Field-renderer registry.
395
1558
  *
@@ -401,18 +1564,15 @@ declare function documentSavePlugin(options?: DocumentSavePluginOptions): LivePr
401
1564
  * - Plugins can register custom renderers via the high-level client.
402
1565
  * - New field types ship without touching `lifecycle.ts`.
403
1566
  *
404
- * The built-in renderers are collected through `registerBuiltinRenderer`
405
- * from each renderer module. To avoid tree-shaking, the explicit
406
- * aggregator `buildBuiltinRenderers` accepts an *additional* renderer
407
- * array — `@field-types/index` invokes it with every concrete renderer
408
- * so the references stay reachable.
1567
+ * The explicit aggregator in `@field-types/index` supplies the default
1568
+ * renderers as values, so it remains tree-shaking-safe without module import
1569
+ * side effects. This registry stores only explicit consumer overrides.
409
1570
  *
410
1571
  * @module @field-types/registry
411
1572
  */
412
1573
 
413
1574
  /**
414
- * Register a built-in renderer. Called from each individual renderer
415
- * module's side-effecting registration.
1575
+ * Register or replace an explicit renderer override for future snapshots.
416
1576
  */
417
1577
  declare function registerBuiltinRenderer(renderer: FieldRenderer): void;
418
1578
 
@@ -470,4 +1630,169 @@ interface PayloadRelationship<TSlug extends string = string> {
470
1630
  */
471
1631
  declare function buildBuiltinRenderers(): Readonly<Record<string, FieldRenderer>>;
472
1632
 
473
- export { type AnalyticsSnapshot, type BlockRenderContext, type BlockRenderer, type DocumentSavePluginOptions, type DocumentSaveStrategy, type FetchPreviewDocumentOptions, type FetchPreviewGlobalOptions, FieldRenderer, type InlineScriptConfig, type LexicalNode, type LexicalRoot, LivePreviewPlugin, type PayloadMedia, type PayloadRelationship, type PreviewFetchBaseOptions, type PreviewWhere, buildBuiltinRenderers, createAnalyticsPlugin, debugPlugin, documentSavePlugin, fetchPreviewDocument, fetchPreviewGlobal, generateInlineScript, highlightPlugin, isLexicalContent, lexicalToHtml, lexicalToPlainText, registerBlockRenderer, registerBuiltinRenderer, runtimeBuildInfo, wrapWithScriptTag };
1633
+ /**
1634
+ * Protocol-version negotiation.
1635
+ *
1636
+ * ⚠️ Reality check: **stock Payload does not version its live-preview
1637
+ * postMessage protocol.** The admin never sends a `protocolVersion`
1638
+ * field and ignores extra fields in our `ready` handshake. Observed
1639
+ * wire behaviour:
1640
+ *
1641
+ * - Payload **2.x** sent `fieldSchemaJSON` with the first update
1642
+ * (client-side merge era).
1643
+ * - Payload **3.x** removed `fieldSchemaJSON` entirely; the official
1644
+ * client re-fetches merged documents through the REST API instead.
1645
+ * - No Payload version sends `previewToken` or any keepalive.
1646
+ *
1647
+ * This module therefore only matters for NON-stock admin panels — a
1648
+ * custom `window.postMessage` sender can advertise a version and this
1649
+ * library will negotiate `min(ours, theirs)` and expose capability
1650
+ * flags. Against stock Payload, `theirs` stays `undefined`, negotiation
1651
+ * collapses to v1, and nothing is gated on it: schema handling
1652
+ * activates whenever `fieldSchemaJSON` is actually present, and token
1653
+ * validation only runs when the consumer opts in.
1654
+ *
1655
+ * Version ladder (this library's own numbering, not Payload's):
1656
+ *
1657
+ * - **v1** — data + slug + locale (matches stock Payload of any era).
1658
+ * - **v2** — remote sends `fieldSchemaJSON` (Payload 2.x behaviour).
1659
+ * - **v3** — remote sends `previewToken` (custom senders only).
1660
+ * - **v4** — nested-array recursion / richer document events
1661
+ * (custom senders only).
1662
+ *
1663
+ * @module @core/protocol-version
1664
+ */
1665
+ declare const LIBRARY_PROTOCOL_VERSION = 4;
1666
+ /** Capability flag → minimum protocol version it requires. */
1667
+ declare const CAPABILITY_REQUIREMENTS: Readonly<Record<string, number>>;
1668
+ type ProtocolCapability = keyof typeof CAPABILITY_REQUIREMENTS;
1669
+ interface ProtocolNegotiation {
1670
+ /** This library's protocol version. */
1671
+ readonly ours: number;
1672
+ /** The remote party's version, when known. */
1673
+ readonly theirs: number | undefined;
1674
+ /** `min(ours, theirs ?? 1)` — the version both sides actually share. */
1675
+ readonly negotiated: number;
1676
+ /** Capability flags enabled at the negotiated version. */
1677
+ readonly capabilities: ReadonlySet<string>;
1678
+ }
1679
+ /**
1680
+ * Compute the negotiated view of the protocol.
1681
+ *
1682
+ * `theirs === undefined` collapses to v1 — assume the remote party is
1683
+ * an older Payload that does not advertise a version.
1684
+ *
1685
+ * Non-finite or non-positive `theirs` values fall back to v1 as well —
1686
+ * we never trust an obviously-corrupted negotiation byte.
1687
+ */
1688
+ declare function negotiateProtocol(theirs: number | undefined): ProtocolNegotiation;
1689
+ declare function hasCapability(negotiation: ProtocolNegotiation, capability: ProtocolCapability): boolean;
1690
+
1691
+ /**
1692
+ * Type-level path utilities — used by the typed binding DSL to give
1693
+ * callers compile-time autocomplete on field names.
1694
+ *
1695
+ * The same path strings the runtime reads from `data-payload-field`
1696
+ * are typed here, so a typo like `'heroTitlee'` is rejected at compile
1697
+ * time *and* the runtime side does not need to change.
1698
+ *
1699
+ * @module @dsl/paths
1700
+ */
1701
+ /**
1702
+ * All string-typed top-level keys of `T`. Non-string keys (numeric
1703
+ * indices on arrays) are filtered out.
1704
+ */
1705
+ type FieldName<T> = Extract<keyof T, string>;
1706
+ /**
1707
+ * Dotted paths into nested object structures, capped at three levels
1708
+ * deep — sufficient for Payload's `group`/`tab`/`array` nesting
1709
+ * without ballooning the IDE's IntelliSense list past the point of
1710
+ * usefulness.
1711
+ */
1712
+ type FieldPath<T, Depth extends 0 | 1 | 2 | 3 = 3> = Depth extends 0 ? never : T extends readonly (infer U)[] ? FieldPath<U, Prev<Depth>> : T extends object ? {
1713
+ [K in Extract<keyof T, string>]: K | (T[K] extends object ? `${K}.${FieldPath<T[K], Prev<Depth>>}` : never);
1714
+ }[Extract<keyof T, string>] : never;
1715
+ /** Decrement helper for depth-bounded recursion. */
1716
+ type Prev<N extends 0 | 1 | 2 | 3> = N extends 3 ? 2 : N extends 2 ? 1 : N extends 1 ? 0 : 0;
1717
+ /**
1718
+ * Resolve the value type at a dotted path. Returns `unknown` when the
1719
+ * path does not exist on `T`, so consumers can opt into stricter
1720
+ * `keyof`-based DSLs when they want absolute type safety.
1721
+ */
1722
+ type ValueAt<T, P extends string> = P extends `${infer Head}.${infer Rest}` ? Head extends keyof T ? T[Head] extends readonly (infer U)[] ? ValueAt<U, Rest> : T[Head] extends object ? ValueAt<T[Head], Rest> : unknown : unknown : P extends keyof T ? T[P] : unknown;
1723
+
1724
+ /**
1725
+ * Typed binding helpers — emit the `data-payload-field` attribute pair
1726
+ * that the live-preview runtime reads, with compile-time validation
1727
+ * of the field name.
1728
+ *
1729
+ * Two forms are exposed:
1730
+ *
1731
+ * - **`bind<Schema>('heroTitle')`** — string-literal form. Lightest
1732
+ * possible runtime (no Proxy), but a rename in the IDE does NOT
1733
+ * follow into the string.
1734
+ * - **`bindByPath<Schema>(s => s.heroTitle)`** — proxy form. The
1735
+ * callback walks a Proxy that records the access path, returning
1736
+ * the same shape as `bind` so the two are interchangeable. Slightly
1737
+ * heavier runtime, but rename-safe and discourages typos.
1738
+ *
1739
+ * Both helpers also accept a richer return — `bind('image', 'src')` —
1740
+ * to bind an attribute other than the default `data-payload-field`,
1741
+ * matching the cache resolver's `data-payload-attribute` flow.
1742
+ *
1743
+ * @module @dsl/bind
1744
+ */
1745
+
1746
+ /**
1747
+ * Shape returned by every binding helper — a spreadable HTML attribute
1748
+ * record. Astro / JSX / Svelte all accept this style directly:
1749
+ *
1750
+ * ```astro
1751
+ * <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
1752
+ * ```
1753
+ */
1754
+ interface FieldBindingAttributes {
1755
+ readonly 'data-payload-field': string;
1756
+ readonly 'data-payload-attribute'?: string;
1757
+ readonly 'data-payload-type'?: string;
1758
+ }
1759
+ interface BindOptions {
1760
+ /** Override the bound attribute (e.g., `'src'` for `<img>`). Default: textContent. */
1761
+ readonly attribute?: string;
1762
+ /** Explicit field-type override — bypasses schema detection. */
1763
+ readonly type?: string;
1764
+ }
1765
+ /**
1766
+ * String-literal binding helper.
1767
+ *
1768
+ * ```ts
1769
+ * import { bind } from 'payload-live-preview';
1770
+ * import type { Homepage } from './payload-types';
1771
+ *
1772
+ * <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
1773
+ * <img {...bind<Homepage>('heroImage', { attribute: 'src' })} />
1774
+ * ```
1775
+ *
1776
+ * The generic `T` is the schema interface emitted by `pll-codegen`.
1777
+ * `field` is constrained to `keyof T` so misspellings fail at compile
1778
+ * time. When `T` is omitted, the helper accepts any string — useful
1779
+ * for incremental adoption.
1780
+ */
1781
+ declare function bind<T = Record<string, unknown>>(field: FieldName<T>, options?: BindOptions): FieldBindingAttributes;
1782
+ /**
1783
+ * Proxy-path binding helper.
1784
+ *
1785
+ * ```ts
1786
+ * <h1 {...bindByPath<Homepage>(d => d.heroTitle)}>{data.heroTitle}</h1>
1787
+ * ```
1788
+ *
1789
+ * The picker is invoked once with a recording Proxy; the recorded path
1790
+ * is joined with `.` and emitted as the binding name. Property access
1791
+ * on nested objects and arrays both work — `d => d.slides[0].title`
1792
+ * becomes `slides.title` (array indices are normalised away because the
1793
+ * runtime resolves bindings against schema, not against positional
1794
+ * indices).
1795
+ */
1796
+ declare function bindByPath<T = Record<string, unknown>>(picker: (data: T) => unknown, options?: BindOptions): FieldBindingAttributes;
1797
+
1798
+ export { type AnalyticsSnapshot, type BindOptions, type BlockRenderContext, type BlockRenderer, type CachedElement, type CspDirectiveMerge, type DocumentSaveHandler, type DocumentSavePluginOptions, type DocumentSaveStrategy, EventEmitter, type EventHandler, type FetchPreviewDocumentOptions, type FetchPreviewGlobalOptions, type FieldBindingAttributes, type FieldName, type FieldPath, type FieldRenderer, type FieldTransform, type FieldType, type FrameAncestorsOptions, type InlineScriptConfig, LIBRARY_PROTOCOL_VERSION, type LexicalNode, type LexicalRoot, LivePreviewClient, type LivePreviewClientConfig, type LivePreviewEventMap, type LivePreviewPlugin, OriginDetector, type PayloadFieldSchema, type PayloadFieldType, type PayloadLivePreviewData, type PayloadLivePreviewMessage, type PayloadMedia, type PayloadRelationship, type PluginContext, type PluginDisposer, type PluginEvents, type PreviewFetchBaseOptions, type PreviewRequestLike, type PreviewRequestOptions, type PreviewWhere, type ProtocolCapability, type ProtocolNegotiation, type RenderContext, type SanitizerDocument, type Unsubscribe, VERSION, type ValueAt, bind, bindByPath, buildBuiltinRenderers, buildFrameAncestors, buildScriptSrcWithNonce, createAnalyticsPlugin, debugPlugin, detectInitialLocale, documentSavePlugin, escapeHtml, escapeHtmlAttribute, fetchPreviewDocument, fetchPreviewGlobal, generateCspNonce, generateInlineScript, hasCapability, highlightPlugin, initLivePreview, isDevMode, isExternalHttpUrl, isInIframe, isInPopup, isInPreviewContext, isLexicalContent, isPreviewRequest, isSafeUrl, lexicalToHtml, lexicalToPlainText, mergeCspHeader, negotiateProtocol, registerBlockRenderer, registerBuiltinRenderer, runtimeBuildInfo, sanitizeHtml, setCspCrypto, setSanitizerDocument, wrapWithScriptTag };