payload-live-preview 1.0.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.
- package/LICENSE +21 -0
- package/README.md +389 -0
- package/dist/adapters/astro/RichText.astro +55 -0
- package/dist/adapters/astro/index.cjs +370 -0
- package/dist/adapters/astro/index.cjs.map +1 -0
- package/dist/adapters/astro/index.d.cts +105 -0
- package/dist/adapters/astro/index.d.ts +105 -0
- package/dist/adapters/astro/index.js +364 -0
- package/dist/adapters/astro/index.js.map +1 -0
- package/dist/adapters/astro/middleware-entry.cjs +303 -0
- package/dist/adapters/astro/middleware-entry.cjs.map +1 -0
- package/dist/adapters/astro/middleware-entry.d.cts +15 -0
- package/dist/adapters/astro/middleware-entry.d.ts +15 -0
- package/dist/adapters/astro/middleware-entry.js +297 -0
- package/dist/adapters/astro/middleware-entry.js.map +1 -0
- package/dist/adapters/nextjs/index.cjs +309 -0
- package/dist/adapters/nextjs/index.cjs.map +1 -0
- package/dist/adapters/nextjs/index.d.cts +75 -0
- package/dist/adapters/nextjs/index.d.ts +75 -0
- package/dist/adapters/nextjs/index.js +306 -0
- package/dist/adapters/nextjs/index.js.map +1 -0
- package/dist/adapters/nuxt/index.cjs +299 -0
- package/dist/adapters/nuxt/index.cjs.map +1 -0
- package/dist/adapters/nuxt/index.d.cts +126 -0
- package/dist/adapters/nuxt/index.d.ts +126 -0
- package/dist/adapters/nuxt/index.js +294 -0
- package/dist/adapters/nuxt/index.js.map +1 -0
- package/dist/adapters/sveltekit/index.cjs +275 -0
- package/dist/adapters/sveltekit/index.cjs.map +1 -0
- package/dist/adapters/sveltekit/index.d.cts +80 -0
- package/dist/adapters/sveltekit/index.d.ts +80 -0
- package/dist/adapters/sveltekit/index.js +273 -0
- package/dist/adapters/sveltekit/index.js.map +1 -0
- package/dist/bind-BSgxoQhN.d.cts +1137 -0
- package/dist/bind-BSgxoQhN.d.ts +1137 -0
- package/dist/codegen-astro.cjs +612 -0
- package/dist/codegen-astro.cjs.map +1 -0
- package/dist/codegen-astro.d.cts +54 -0
- package/dist/codegen-astro.d.ts +54 -0
- package/dist/codegen-astro.js +610 -0
- package/dist/codegen-astro.js.map +1 -0
- package/dist/codegen-cli.cjs +669 -0
- package/dist/codegen-cli.cjs.map +1 -0
- package/dist/codegen-cli.d.cts +19 -0
- package/dist/codegen-cli.d.ts +19 -0
- package/dist/codegen-cli.js +667 -0
- package/dist/codegen-cli.js.map +1 -0
- package/dist/codegen.cjs +551 -0
- package/dist/codegen.cjs.map +1 -0
- package/dist/codegen.d.cts +162 -0
- package/dist/codegen.d.ts +162 -0
- package/dist/codegen.js +547 -0
- package/dist/codegen.js.map +1 -0
- package/dist/core.cjs +3684 -0
- package/dist/core.cjs.map +1 -0
- package/dist/core.d.cts +24 -0
- package/dist/core.d.ts +24 -0
- package/dist/core.js +3657 -0
- package/dist/core.js.map +1 -0
- package/dist/index.cjs +4083 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +472 -0
- package/dist/index.d.ts +472 -0
- package/dist/index.js +4040 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware-BwIlf0jW.d.cts +168 -0
- package/dist/middleware-BwIlf0jW.d.ts +168 -0
- package/dist/payload.cjs +31 -0
- package/dist/payload.cjs.map +1 -0
- package/dist/payload.d.cts +87 -0
- package/dist/payload.d.ts +87 -0
- package/dist/payload.js +29 -0
- package/dist/payload.js.map +1 -0
- package/dist/preview-request-DAvxm18T.d.cts +68 -0
- package/dist/preview-request-DAvxm18T.d.ts +68 -0
- package/package.json +186 -0
|
@@ -0,0 +1,1137 @@
|
|
|
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
|
+
/** Fired once when a client instance finishes its synchronous init. */
|
|
144
|
+
readonly init: {
|
|
145
|
+
readonly timestamp: number;
|
|
146
|
+
};
|
|
147
|
+
/** Fired the first time a valid update message is received from a trusted origin. */
|
|
148
|
+
readonly connect: {
|
|
149
|
+
readonly origin: string;
|
|
150
|
+
readonly timestamp: number;
|
|
151
|
+
};
|
|
152
|
+
/** Fired when the heartbeat times out or the iframe is unloaded. */
|
|
153
|
+
readonly disconnect: {
|
|
154
|
+
readonly reason: 'timeout' | 'destroy' | 'unload';
|
|
155
|
+
readonly timestamp: number;
|
|
156
|
+
};
|
|
157
|
+
/**
|
|
158
|
+
* Fired before the DOM is mutated for an incoming update.
|
|
159
|
+
*
|
|
160
|
+
* Handlers may call `cancel()` synchronously to skip the update —
|
|
161
|
+
* useful for read-only previews or A/B testing.
|
|
162
|
+
*/
|
|
163
|
+
readonly beforeUpdate: {
|
|
164
|
+
readonly data: PayloadLivePreviewData;
|
|
165
|
+
readonly cancel: () => void;
|
|
166
|
+
};
|
|
167
|
+
/** Fired after all DOM mutations for an incoming update have completed. */
|
|
168
|
+
readonly afterUpdate: {
|
|
169
|
+
readonly data: PayloadLivePreviewData;
|
|
170
|
+
readonly updatedCount: number;
|
|
171
|
+
readonly durationMs: number;
|
|
172
|
+
};
|
|
173
|
+
/** Fired for each individual element write performed during an update. */
|
|
174
|
+
readonly elementUpdate: {
|
|
175
|
+
readonly element: Element;
|
|
176
|
+
readonly fieldName: string;
|
|
177
|
+
readonly previousValue: unknown;
|
|
178
|
+
readonly nextValue: unknown;
|
|
179
|
+
};
|
|
180
|
+
/** Fired whenever the element cache is rebuilt (initial scan or MutationObserver-triggered). */
|
|
181
|
+
readonly cacheRefresh: {
|
|
182
|
+
readonly elementCount: number;
|
|
183
|
+
readonly fieldCount: number;
|
|
184
|
+
readonly durationMs: number;
|
|
185
|
+
};
|
|
186
|
+
/** Fired when a `payload-document-event` message arrives (document save). */
|
|
187
|
+
readonly documentSave: {
|
|
188
|
+
readonly timestamp: number;
|
|
189
|
+
};
|
|
190
|
+
/**
|
|
191
|
+
* Fired on errors that the runtime caught but cannot fully recover from.
|
|
192
|
+
*
|
|
193
|
+
* The `context` string identifies where the error originated.
|
|
194
|
+
*/
|
|
195
|
+
readonly error: {
|
|
196
|
+
readonly error: Error;
|
|
197
|
+
readonly context: string;
|
|
198
|
+
};
|
|
199
|
+
/** Fired during destroy after observers and listeners are removed. */
|
|
200
|
+
readonly destroy: {
|
|
201
|
+
readonly timestamp: number;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Handler signature: receives the payload and may return a Promise.
|
|
206
|
+
*
|
|
207
|
+
* Promise-returning handlers are awaited in registration order — this
|
|
208
|
+
* is important for `beforeUpdate.cancel()` semantics.
|
|
209
|
+
*/
|
|
210
|
+
type EventHandler<TPayload> = (payload: TPayload) => void | Promise<void>;
|
|
211
|
+
/** Subscription removal function returned by `on()`/`once()`. */
|
|
212
|
+
type Unsubscribe = () => void;
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Per-instance, type-safe event emitter.
|
|
216
|
+
*
|
|
217
|
+
* Replaces the legacy module-level singleton. Each `LivePreviewClient`
|
|
218
|
+
* instantiates its own emitter so that calling `destroy()` on one
|
|
219
|
+
* instance cannot clobber listeners attached to another.
|
|
220
|
+
*
|
|
221
|
+
* Design notes:
|
|
222
|
+
*
|
|
223
|
+
* - Generic over an event map type. The default `LivePreviewEventMap`
|
|
224
|
+
* captures every event this library emits, but the implementation
|
|
225
|
+
* is reusable for any map.
|
|
226
|
+
* - Handlers may be `async`. They are awaited sequentially in
|
|
227
|
+
* registration order. This is intentional: `beforeUpdate.cancel()`
|
|
228
|
+
* and similar synchronization rely on stable ordering.
|
|
229
|
+
* - Errors thrown by one handler are logged via `console.error` and
|
|
230
|
+
* do not prevent later handlers from running.
|
|
231
|
+
* - `once()` semantics: registered into a separate map; the entire
|
|
232
|
+
* set for an event is cleared after dispatch, even if individual
|
|
233
|
+
* handlers throw.
|
|
234
|
+
*
|
|
235
|
+
* @module @events/emitter
|
|
236
|
+
*/
|
|
237
|
+
|
|
238
|
+
declare class EventEmitter<TMap extends object = LivePreviewEventMap> {
|
|
239
|
+
#private;
|
|
240
|
+
/**
|
|
241
|
+
* Subscribe `handler` to `event`. Returns an unsubscribe function;
|
|
242
|
+
* call it to remove the handler. The handler is invoked for every
|
|
243
|
+
* subsequent emit until unsubscribed.
|
|
244
|
+
*/
|
|
245
|
+
on<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
|
|
246
|
+
/**
|
|
247
|
+
* Subscribe `handler` for a single emit. After the first dispatch,
|
|
248
|
+
* the handler is removed automatically.
|
|
249
|
+
*/
|
|
250
|
+
once<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): Unsubscribe;
|
|
251
|
+
/**
|
|
252
|
+
* Remove `handler` from `event`. Works for both `on`- and
|
|
253
|
+
* `once`-registered handlers. No-op when the handler isn't registered.
|
|
254
|
+
*/
|
|
255
|
+
off<E extends keyof TMap>(event: E, handler: EventHandler<TMap[E]>): void;
|
|
256
|
+
/**
|
|
257
|
+
* Dispatch `payload` to every handler registered for `event`.
|
|
258
|
+
*
|
|
259
|
+
* Handlers run sequentially in registration order — regular ones
|
|
260
|
+
* first, then `once` handlers. Async handlers are awaited. Synchronous
|
|
261
|
+
* exceptions are caught, logged, and isolated to the offending handler.
|
|
262
|
+
*/
|
|
263
|
+
emit<E extends keyof TMap>(event: E, payload: TMap[E]): Promise<void>;
|
|
264
|
+
/**
|
|
265
|
+
* Number of handlers currently registered for `event`.
|
|
266
|
+
* Sum of `on` and `once` registrations.
|
|
267
|
+
*/
|
|
268
|
+
listenerCount(event: keyof TMap): number;
|
|
269
|
+
/**
|
|
270
|
+
* Remove every handler. Pass an event name to limit the removal to
|
|
271
|
+
* one event; omit it to clear every event on this emitter.
|
|
272
|
+
*
|
|
273
|
+
* Crucially this only affects the current instance — there is no
|
|
274
|
+
* shared global state.
|
|
275
|
+
*/
|
|
276
|
+
removeAllListeners(event?: keyof TMap): void;
|
|
277
|
+
/**
|
|
278
|
+
* Snapshot of all event names that currently have at least one
|
|
279
|
+
* registered handler. Useful for debugging.
|
|
280
|
+
*/
|
|
281
|
+
eventNames(): (keyof TMap)[];
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Core type definitions shared across all runtime primitives.
|
|
286
|
+
*
|
|
287
|
+
* @module @core/types
|
|
288
|
+
*/
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Field types recognized by the live preview renderer.
|
|
292
|
+
*
|
|
293
|
+
* Extends Payload's field set with `html` and `url` which are not
|
|
294
|
+
* Payload field types per se but useful DOM-binding categories.
|
|
295
|
+
*/
|
|
296
|
+
type FieldType = PayloadFieldType | 'html' | 'url' | 'image' | 'structural-array';
|
|
297
|
+
/**
|
|
298
|
+
* A DOM element that has been registered as a live preview binding.
|
|
299
|
+
*
|
|
300
|
+
* Stored in the element cache indexed by `fieldName`. Multiple cached
|
|
301
|
+
* entries can share a field name when the same field is rendered in
|
|
302
|
+
* several places (e.g., a title shown in both the header and the
|
|
303
|
+
* page body).
|
|
304
|
+
*/
|
|
305
|
+
interface CachedElement {
|
|
306
|
+
/** The bound DOM element. */
|
|
307
|
+
readonly element: Element;
|
|
308
|
+
/** Payload field path, e.g., `title` or `hero.subtitle`. */
|
|
309
|
+
readonly fieldName: string;
|
|
310
|
+
/**
|
|
311
|
+
* Resolved field type, used to dispatch to the right renderer.
|
|
312
|
+
*
|
|
313
|
+
* The cache resolves this from DOM attributes (`data-payload-type`,
|
|
314
|
+
* tag-based heuristics) at build time. When `explicitFieldType` is
|
|
315
|
+
* `false` the lifecycle is allowed to override this with the type
|
|
316
|
+
* learned from the schema once `fieldSchemaJSON` arrives.
|
|
317
|
+
*/
|
|
318
|
+
readonly fieldType: FieldType;
|
|
319
|
+
/**
|
|
320
|
+
* `true` when the field type was set by an explicit
|
|
321
|
+
* `data-payload-type` attribute (consumer trumps schema), `false`
|
|
322
|
+
* when it was inferred via tag-based heuristics (schema may
|
|
323
|
+
* override later). Optional so synthetic bindings (tests / programmatic
|
|
324
|
+
* `cache.add()` callers) need not supply it; the cache always
|
|
325
|
+
* populates it for DOM-derived bindings.
|
|
326
|
+
*/
|
|
327
|
+
readonly explicitFieldType?: boolean;
|
|
328
|
+
/**
|
|
329
|
+
* Optional target attribute: the value is written to this attribute
|
|
330
|
+
* instead of the element's content (from `data-payload-attribute`,
|
|
331
|
+
* or `bind(field, { attribute })`). Writes are policed — event
|
|
332
|
+
* handlers, `style`, and unsafe URLs are refused.
|
|
333
|
+
*/
|
|
334
|
+
readonly targetAttribute?: string;
|
|
335
|
+
/** Optional sibling-field path bound to the element's `href` attribute. */
|
|
336
|
+
readonly hrefField?: string;
|
|
337
|
+
/** Optional sibling-field path bound to the element's `src` attribute. */
|
|
338
|
+
readonly srcField?: string;
|
|
339
|
+
/** Optional sibling-field path bound to the element's `alt` attribute. */
|
|
340
|
+
readonly altField?: string;
|
|
341
|
+
/** Optional inline template for array/blocks rendering. */
|
|
342
|
+
readonly arrayTemplate?: string;
|
|
343
|
+
/** Optional separator for primitive-array stringification. */
|
|
344
|
+
readonly arraySeparator?: string;
|
|
345
|
+
/** Optional locale code locked onto this element (overrides the global locale). */
|
|
346
|
+
readonly locale?: string;
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Context passed to every field renderer. Provides access to the
|
|
350
|
+
* entire field tree so renderers can resolve sibling fields (e.g.,
|
|
351
|
+
* an `<a>` that pulls `href` from a different field).
|
|
352
|
+
*/
|
|
353
|
+
interface RenderContext {
|
|
354
|
+
/** Full field map for the current update — used for sibling lookups. */
|
|
355
|
+
readonly allFields: Record<string, unknown>;
|
|
356
|
+
/** Active locale, or `undefined` when none is established. */
|
|
357
|
+
readonly locale: string | undefined;
|
|
358
|
+
/** Optional schema descriptor for the field, when available. */
|
|
359
|
+
readonly schema: PayloadFieldSchema | undefined;
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Contract that every field renderer implements.
|
|
363
|
+
*
|
|
364
|
+
* Renderers are pure DOM-write functions: they receive the cached
|
|
365
|
+
* binding plus the new value and apply it to the element. They must
|
|
366
|
+
* not throw — failures should be silenced and logged so that one bad
|
|
367
|
+
* field cannot stop an entire update.
|
|
368
|
+
*/
|
|
369
|
+
interface FieldRenderer {
|
|
370
|
+
readonly name: FieldType;
|
|
371
|
+
render(target: CachedElement, value: unknown, context: RenderContext): void;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Plugin system types.
|
|
376
|
+
*
|
|
377
|
+
* Each plugin participates in the live-preview lifecycle through a
|
|
378
|
+
* scoped, per-instance context. Plugins can register custom field
|
|
379
|
+
* renderers, transform incoming values, and subscribe to events —
|
|
380
|
+
* the same primitives the built-in renderers use.
|
|
381
|
+
*
|
|
382
|
+
* The system is intentionally minimal. Anything that can be expressed
|
|
383
|
+
* with an event subscription belongs in `events`, not `plugins`.
|
|
384
|
+
*
|
|
385
|
+
* @module @plugins/types
|
|
386
|
+
*/
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Function that mutates a value before the renderer receives it.
|
|
390
|
+
* Returning a different shape than the input is allowed.
|
|
391
|
+
*/
|
|
392
|
+
type FieldTransform = (value: unknown, context: {
|
|
393
|
+
readonly fieldName: string;
|
|
394
|
+
readonly element: Element;
|
|
395
|
+
readonly allFields: Record<string, unknown>;
|
|
396
|
+
}) => unknown;
|
|
397
|
+
/**
|
|
398
|
+
* Per-instance plugin context. Replaces the legacy singleton context.
|
|
399
|
+
*
|
|
400
|
+
* Plugins receive a context that is bound to the parent
|
|
401
|
+
* `LivePreviewClient`. They can:
|
|
402
|
+
*
|
|
403
|
+
* - subscribe via `events.on/once`,
|
|
404
|
+
* - register field renderers,
|
|
405
|
+
* - register value transforms,
|
|
406
|
+
* - read the client's (frozen) configuration,
|
|
407
|
+
* - log through the client's debug channel.
|
|
408
|
+
*/
|
|
409
|
+
interface PluginContext {
|
|
410
|
+
readonly events: EventEmitter;
|
|
411
|
+
readonly registerFieldRenderer: (renderer: FieldRenderer) => void;
|
|
412
|
+
readonly registerTransform: (fieldName: string, transform: FieldTransform) => void;
|
|
413
|
+
readonly getConfig: () => Readonly<Record<string, unknown>>;
|
|
414
|
+
readonly log: (...args: unknown[]) => void;
|
|
415
|
+
}
|
|
416
|
+
/**
|
|
417
|
+
* Plugin definition. `init` runs once per registration; `destroy`
|
|
418
|
+
* runs when the plugin is unregistered or the client is destroyed.
|
|
419
|
+
*
|
|
420
|
+
* Both hooks may return a promise; the manager awaits them.
|
|
421
|
+
*/
|
|
422
|
+
interface LivePreviewPlugin {
|
|
423
|
+
readonly name: string;
|
|
424
|
+
readonly version?: string;
|
|
425
|
+
readonly init: (context: PluginContext) => void | Promise<void>;
|
|
426
|
+
readonly destroy?: () => void | Promise<void>;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* `LivePreviewClient` configuration shape.
|
|
431
|
+
*
|
|
432
|
+
* @module @client/config
|
|
433
|
+
*/
|
|
434
|
+
interface LivePreviewClientConfig {
|
|
435
|
+
/** Explicit additional trusted origins. */
|
|
436
|
+
readonly allowedOrigins?: readonly string[];
|
|
437
|
+
/**
|
|
438
|
+
* Payload server origin, e.g. `https://cms.example.com`. When set,
|
|
439
|
+
* every incoming update is re-fetched through the Payload REST API
|
|
440
|
+
* so relationship/upload fields render populated instead of as bare
|
|
441
|
+
* IDs (same strategy as the official client). Recommended for
|
|
442
|
+
* Payload 3.x.
|
|
443
|
+
*/
|
|
444
|
+
readonly serverURL?: string;
|
|
445
|
+
/** REST API route prefix used with `serverURL`. Defaults to `/api`. */
|
|
446
|
+
readonly apiRoute?: string;
|
|
447
|
+
/** Population depth used with `serverURL`. Defaults to `1`. */
|
|
448
|
+
readonly mergeDepth?: number;
|
|
449
|
+
/**
|
|
450
|
+
* Custom fetch implementation for the `serverURL` merge request —
|
|
451
|
+
* the equivalent of the official client's `requestHandler`. Use it
|
|
452
|
+
* to attach auth headers or route through your own proxy. Only
|
|
453
|
+
* available on the programmatic client (functions cannot be
|
|
454
|
+
* serialised into the inline script).
|
|
455
|
+
*/
|
|
456
|
+
readonly mergeFetch?: typeof fetch;
|
|
457
|
+
/** Enable verbose console debug output. Defaults to dev-mode detection. */
|
|
458
|
+
readonly debug?: boolean;
|
|
459
|
+
/** Debounce window for incoming updates. Defaults to 50 ms. */
|
|
460
|
+
readonly debounceMs?: number;
|
|
461
|
+
/**
|
|
462
|
+
* Heartbeat timeout in ms. Defaults to `0` (disabled) because the
|
|
463
|
+
* Payload admin sends messages only on form edits — there is no
|
|
464
|
+
* protocol keepalive, so an idle timeout would produce false
|
|
465
|
+
* disconnects.
|
|
466
|
+
*/
|
|
467
|
+
readonly heartbeatMs?: number;
|
|
468
|
+
/** IntersectionObserver rootMargin. Defaults to `200px`. */
|
|
469
|
+
readonly intersectionRootMargin?: string;
|
|
470
|
+
/** Bypass the visibility gate (apply every update). Defaults to `false`. */
|
|
471
|
+
readonly disableVisibilityGate?: boolean;
|
|
472
|
+
/** Cache-size threshold above which off-screen updates are queued for replay. Defaults to 50. */
|
|
473
|
+
readonly visibilityGateThreshold?: number;
|
|
474
|
+
/** Mount an `aria-live` region and announce lifecycle to screen readers. Default `true`. */
|
|
475
|
+
readonly enableA11y?: boolean;
|
|
476
|
+
/** Locale used to pick A11y announcement strings. Defaults to detected locale. */
|
|
477
|
+
readonly a11yLocale?: string;
|
|
478
|
+
/** Document root, defaults to `document`. */
|
|
479
|
+
readonly root?: Document | Element;
|
|
480
|
+
/** Disable `document.referrer` detection. */
|
|
481
|
+
readonly disableReferrerDetection?: boolean;
|
|
482
|
+
/** Disable the localhost dev-mode pattern matcher. */
|
|
483
|
+
readonly disableLocalhostMatching?: boolean;
|
|
484
|
+
/** Auto-start the runtime in the constructor. Defaults to `true`. */
|
|
485
|
+
readonly autoStart?: boolean;
|
|
486
|
+
/**
|
|
487
|
+
* Optional preview-token validator. When set, every data update
|
|
488
|
+
* must carry a `previewToken` field that this function approves.
|
|
489
|
+
*
|
|
490
|
+
* ⚠️ `previewToken` is an extension of THIS library, not part of the
|
|
491
|
+
* stock Payload postMessage protocol — the Payload admin never sends
|
|
492
|
+
* one. Only enable this when a custom admin component adds the token
|
|
493
|
+
* to outgoing messages; against an unmodified Payload admin it would
|
|
494
|
+
* drop every update.
|
|
495
|
+
*/
|
|
496
|
+
readonly validateToken?: (token: string | undefined, origin: string) => boolean | Promise<boolean>;
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Public, high-level `LivePreviewClient`.
|
|
501
|
+
*
|
|
502
|
+
* The client wraps a `LivePreviewRuntime` with:
|
|
503
|
+
*
|
|
504
|
+
* - Per-instance event emitter (no singleton).
|
|
505
|
+
* - Per-instance plugin manager.
|
|
506
|
+
* - Custom-renderer registration.
|
|
507
|
+
* - Field-name transforms applied before renderer dispatch.
|
|
508
|
+
*
|
|
509
|
+
* Consumers who want full programmatic control instantiate
|
|
510
|
+
* `LivePreviewClient` directly. Consumers who just want a working
|
|
511
|
+
* inline preview should use `generateInlineScript()` instead.
|
|
512
|
+
*
|
|
513
|
+
* @module @client
|
|
514
|
+
*/
|
|
515
|
+
|
|
516
|
+
declare class LivePreviewClient {
|
|
517
|
+
#private;
|
|
518
|
+
constructor(config?: LivePreviewClientConfig);
|
|
519
|
+
/**
|
|
520
|
+
* Start the runtime. Returns `true` when the runtime actually
|
|
521
|
+
* started (it may refuse to start outside a preview context).
|
|
522
|
+
*/
|
|
523
|
+
start(): boolean;
|
|
524
|
+
/**
|
|
525
|
+
* Stop the runtime and tear down every plugin. Idempotent.
|
|
526
|
+
*/
|
|
527
|
+
destroy(): Promise<void>;
|
|
528
|
+
/**
|
|
529
|
+
* Register a plugin.
|
|
530
|
+
*/
|
|
531
|
+
use(plugin: LivePreviewPlugin): Promise<void>;
|
|
532
|
+
/**
|
|
533
|
+
* Unregister a plugin by name.
|
|
534
|
+
*/
|
|
535
|
+
unuse(name: string): Promise<void>;
|
|
536
|
+
/** Rebuild the element cache manually. */
|
|
537
|
+
refreshCache(): void;
|
|
538
|
+
/** Read-only access to the event emitter for `on`/`once`/`off`. */
|
|
539
|
+
get events(): EventEmitter;
|
|
540
|
+
/** Names of currently registered plugins. */
|
|
541
|
+
get plugins(): readonly string[];
|
|
542
|
+
/** Current connection status. */
|
|
543
|
+
get status(): 'disconnected' | 'connecting' | 'connected';
|
|
544
|
+
/** Number of valid updates received so far. */
|
|
545
|
+
get updateCount(): number;
|
|
546
|
+
/** `true` once `destroy()` has been called. */
|
|
547
|
+
get destroyed(): boolean;
|
|
548
|
+
}
|
|
549
|
+
/**
|
|
550
|
+
* Convenience factory: instantiate the client, returning `null` when
|
|
551
|
+
* the page is not currently a preview context (top-level navigation).
|
|
552
|
+
* Useful for SSR-style integrations that import this from any context.
|
|
553
|
+
*/
|
|
554
|
+
declare function initLivePreview(config?: LivePreviewClientConfig): LivePreviewClient | null;
|
|
555
|
+
|
|
556
|
+
/**
|
|
557
|
+
* HTML-escape primitives.
|
|
558
|
+
*
|
|
559
|
+
* Implementations follow the OWASP XSS Prevention Cheat Sheet: each
|
|
560
|
+
* special character is replaced with its decimal or hex entity. The
|
|
561
|
+
* functions are isomorphic — they have no dependency on the DOM and
|
|
562
|
+
* can run in any JavaScript environment (browser, Node, Deno, Bun).
|
|
563
|
+
*
|
|
564
|
+
* @module @security/escape
|
|
565
|
+
*/
|
|
566
|
+
/**
|
|
567
|
+
* Escape every HTML-significant character in `text` so the result is
|
|
568
|
+
* safe to interpolate into any HTML context (element body or attribute).
|
|
569
|
+
*
|
|
570
|
+
* - Coerces non-strings via `String(value)`; pass `null`/`undefined`
|
|
571
|
+
* intentionally — they become `"null"`/`"undefined"`.
|
|
572
|
+
* - Cannot fail; returns `""` for the empty input.
|
|
573
|
+
*/
|
|
574
|
+
declare function escapeHtml(text: string): string;
|
|
575
|
+
/**
|
|
576
|
+
* Escape characters that would break out of a double-quoted HTML
|
|
577
|
+
* attribute value. Stricter than `escapeHtml` for body content but
|
|
578
|
+
* preserves URL characters (`/`, `=`, etc.) that are common in
|
|
579
|
+
* `href` and `src` values.
|
|
580
|
+
*
|
|
581
|
+
* Only call this for values that have already been validated as safe
|
|
582
|
+
* URLs or trusted strings; it does NOT block dangerous protocols.
|
|
583
|
+
*/
|
|
584
|
+
declare function escapeHtmlAttribute(value: string): string;
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* URL safety validation.
|
|
588
|
+
*
|
|
589
|
+
* Single source of truth for URL-protocol decisions used by both the
|
|
590
|
+
* inline runtime and the high-level client. The previous version of
|
|
591
|
+
* this library had two implementations that subtly disagreed; this
|
|
592
|
+
* module replaces both.
|
|
593
|
+
*
|
|
594
|
+
* Policy:
|
|
595
|
+
* - Allow only the protocols in `SAFE_PROTOCOLS`.
|
|
596
|
+
* - Reject every other protocol explicitly, including `javascript:`,
|
|
597
|
+
* `data:`, `vbscript:`, `file:`, `blob:`, and any custom scheme.
|
|
598
|
+
* - Empty strings are NOT considered safe (this is a tightening from
|
|
599
|
+
* the legacy behaviour and avoids producing `<a href="">` markup).
|
|
600
|
+
*
|
|
601
|
+
* @module @security/url-validator
|
|
602
|
+
*/
|
|
603
|
+
/**
|
|
604
|
+
* Returns `true` only when `url` is one of the explicitly allowed forms:
|
|
605
|
+
*
|
|
606
|
+
* - Absolute URL with `http:`, `https:`, `mailto:`, or `tel:` protocol
|
|
607
|
+
* - Protocol-relative URL (`//example.com/...`)
|
|
608
|
+
* - Same-origin absolute path (`/foo`)
|
|
609
|
+
* - Hash or query fragment (`#section`, `?q=1`)
|
|
610
|
+
* - Plain relative path (`foo/bar`, `./foo`, `../foo`)
|
|
611
|
+
*/
|
|
612
|
+
declare function isSafeUrl(url: unknown): boolean;
|
|
613
|
+
/**
|
|
614
|
+
* Returns `true` when the URL points to an external HTTP(S) origin.
|
|
615
|
+
* Protocol-relative URLs (`//example.com/...`) count as external —
|
|
616
|
+
* they resolve to another origin and need the same `noopener`
|
|
617
|
+
* hardening as absolute ones.
|
|
618
|
+
*
|
|
619
|
+
* Used by renderers to decide whether to add `target="_blank"` and the
|
|
620
|
+
* `noopener noreferrer` rel attributes to anchors. Inputs that fail
|
|
621
|
+
* `isSafeUrl()` always return `false`.
|
|
622
|
+
*/
|
|
623
|
+
declare function isExternalHttpUrl(url: string): boolean;
|
|
624
|
+
|
|
625
|
+
interface SanitizeOptions {
|
|
626
|
+
/** Extra tags to allow beyond the built-in list. Lower-case, untrimmed. */
|
|
627
|
+
readonly additionalAllowedTags?: readonly string[];
|
|
628
|
+
/** Extra per-tag attributes to allow. Tag and attribute names must be lower-case. */
|
|
629
|
+
readonly additionalAllowedAttributes?: Readonly<Record<string, readonly string[]>>;
|
|
630
|
+
}
|
|
631
|
+
/**
|
|
632
|
+
* Minimal `Document`-shaped surface the sanitizer needs. Any DOM
|
|
633
|
+
* implementation (browser, jsdom, linkedom, happy-dom, parse5 + a
|
|
634
|
+
* thin adapter) that produces `<template>`-style elements works.
|
|
635
|
+
*/
|
|
636
|
+
interface SanitizerDocument {
|
|
637
|
+
createElement: (tagName: string) => {
|
|
638
|
+
innerHTML: string;
|
|
639
|
+
readonly content: ParentNode;
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Inject a `Document` implementation for the sanitizer to use when
|
|
644
|
+
* `globalThis.document` is unavailable.
|
|
645
|
+
*
|
|
646
|
+
* Server-side renderers (Node, Bun, Deno without DOM globals) can wire
|
|
647
|
+
* this up once at startup with any of the popular pure-JS DOM
|
|
648
|
+
* libraries:
|
|
649
|
+
*
|
|
650
|
+
* ```ts
|
|
651
|
+
* // linkedom — recommended (smallest, fastest)
|
|
652
|
+
* import { parseHTML } from 'linkedom';
|
|
653
|
+
* import { setSanitizerDocument } from 'payload-live-preview';
|
|
654
|
+
* const { document } = parseHTML('<!doctype html><html><body></body></html>');
|
|
655
|
+
* setSanitizerDocument(document);
|
|
656
|
+
* ```
|
|
657
|
+
*
|
|
658
|
+
* ```ts
|
|
659
|
+
* // jsdom — heavier, but already in many SSR pipelines
|
|
660
|
+
* import { JSDOM } from 'jsdom';
|
|
661
|
+
* setSanitizerDocument(new JSDOM().window.document);
|
|
662
|
+
* ```
|
|
663
|
+
*
|
|
664
|
+
* Pass `null` to clear a previous override (mostly useful for tests).
|
|
665
|
+
*/
|
|
666
|
+
declare function setSanitizerDocument(doc: SanitizerDocument | null): void;
|
|
667
|
+
/**
|
|
668
|
+
* Sanitize `html` and return safe HTML.
|
|
669
|
+
*
|
|
670
|
+
* The function never returns `undefined`/`null`. On malformed input the
|
|
671
|
+
* browser parser silently recovers and the sanitizer continues from
|
|
672
|
+
* the recovered tree.
|
|
673
|
+
*
|
|
674
|
+
* @throws {SanitizerEnvironmentError} when no DOM is available.
|
|
675
|
+
*/
|
|
676
|
+
declare function sanitizeHtml(html: string, options?: SanitizeOptions): string;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Content-Security-Policy helpers.
|
|
680
|
+
*
|
|
681
|
+
* Two responsibilities:
|
|
682
|
+
*
|
|
683
|
+
* 1. Generate cryptographic nonces so consumers can opt into a strict
|
|
684
|
+
* `script-src 'nonce-...'` policy and avoid `'unsafe-inline'`.
|
|
685
|
+
* 2. Build the `frame-ancestors` directive value, restricting which
|
|
686
|
+
* origins may embed the preview page in an iframe.
|
|
687
|
+
*
|
|
688
|
+
* Both functions are isomorphic — they work in browsers, Node, and
|
|
689
|
+
* edge runtimes that expose Web Crypto via `globalThis.crypto`.
|
|
690
|
+
*
|
|
691
|
+
* @module @security/csp
|
|
692
|
+
*/
|
|
693
|
+
/**
|
|
694
|
+
* Minimal Web-Crypto surface area we actually call. The full `Crypto`
|
|
695
|
+
* type drags in browser-only declarations that don't typecheck cleanly
|
|
696
|
+
* against Node's `webcrypto` shape — this narrower interface accepts
|
|
697
|
+
* both without an unsafe cast.
|
|
698
|
+
*/
|
|
699
|
+
interface WebCryptoLike {
|
|
700
|
+
getRandomValues: <T extends ArrayBufferView | null>(array: T) => T;
|
|
701
|
+
}
|
|
702
|
+
/**
|
|
703
|
+
* Inject a Web Crypto implementation to use when `globalThis.crypto`
|
|
704
|
+
* is unavailable.
|
|
705
|
+
*
|
|
706
|
+
* Node.js exposed `globalThis.crypto` globally starting with v19. On
|
|
707
|
+
* Node 18 (still LTS at time of writing) consumers can flip the
|
|
708
|
+
* `--experimental-global-webcrypto` flag *or* call this function once
|
|
709
|
+
* at startup:
|
|
710
|
+
*
|
|
711
|
+
* ```ts
|
|
712
|
+
* import { webcrypto } from 'node:crypto';
|
|
713
|
+
* import { setCspCrypto } from 'payload-live-preview';
|
|
714
|
+
* setCspCrypto(webcrypto as { getRandomValues: typeof webcrypto.getRandomValues });
|
|
715
|
+
* ```
|
|
716
|
+
*
|
|
717
|
+
* Browser bundles never need to call this — `globalThis.crypto` is
|
|
718
|
+
* always present there, and the override only matters when the global
|
|
719
|
+
* is missing.
|
|
720
|
+
*
|
|
721
|
+
* Pass `null` to clear a previous override (mostly useful for tests).
|
|
722
|
+
*/
|
|
723
|
+
declare function setCspCrypto(crypto: WebCryptoLike | null): void;
|
|
724
|
+
/**
|
|
725
|
+
* Generate a base64url-encoded cryptographic nonce.
|
|
726
|
+
*
|
|
727
|
+
* Uses `crypto.getRandomValues` everywhere — falls back to throwing
|
|
728
|
+
* if no Web Crypto implementation is available, because a predictable
|
|
729
|
+
* nonce would silently defeat the entire CSP.
|
|
730
|
+
*
|
|
731
|
+
* @param bytes Number of random bytes (defaults to 16 → 128-bit entropy).
|
|
732
|
+
*/
|
|
733
|
+
declare function generateCspNonce(bytes?: number): string;
|
|
734
|
+
interface FrameAncestorsOptions {
|
|
735
|
+
readonly self?: boolean;
|
|
736
|
+
readonly origins?: readonly string[];
|
|
737
|
+
readonly allowNone?: boolean;
|
|
738
|
+
}
|
|
739
|
+
/**
|
|
740
|
+
* Build the value for `frame-ancestors` in a `Content-Security-Policy`
|
|
741
|
+
* header.
|
|
742
|
+
*
|
|
743
|
+
* Returns `'none'` if `allowNone: true` and no other sources are
|
|
744
|
+
* provided. Otherwise concatenates `'self'` (if requested) with the
|
|
745
|
+
* given origins, deduplicating and trimming entries.
|
|
746
|
+
*
|
|
747
|
+
* @example
|
|
748
|
+
* `frame-ancestors ${buildFrameAncestors({ self: true, origins: [adminUrl] })}`
|
|
749
|
+
*/
|
|
750
|
+
declare function buildFrameAncestors(options?: FrameAncestorsOptions): string;
|
|
751
|
+
/**
|
|
752
|
+
* Build a `script-src` value that permits a single nonce plus optional
|
|
753
|
+
* extra sources.
|
|
754
|
+
*
|
|
755
|
+
* `'strict-dynamic'` is **opt-in**: under CSP 3 it makes browsers
|
|
756
|
+
* ignore `'self'` and every host source in the directive, so any
|
|
757
|
+
* parser-inserted script without a nonce (framework hydration scripts,
|
|
758
|
+
* consumer `<script>` tags) would be blocked. Only enable it when every
|
|
759
|
+
* script on the page carries the nonce.
|
|
760
|
+
*/
|
|
761
|
+
declare function buildScriptSrcWithNonce(nonce: string, options?: {
|
|
762
|
+
readonly self?: boolean;
|
|
763
|
+
readonly extra?: readonly string[];
|
|
764
|
+
readonly strictDynamic?: boolean;
|
|
765
|
+
}): string;
|
|
766
|
+
/**
|
|
767
|
+
* How a directive is merged into an existing CSP header value.
|
|
768
|
+
*
|
|
769
|
+
* - `union` (default) — the new sources are added to the existing
|
|
770
|
+
* directive's sources (deduplicated). An existing `'none'` is
|
|
771
|
+
* dropped when real sources are added, since `'none'` may not be
|
|
772
|
+
* combined with other sources.
|
|
773
|
+
* - `replace` — the new value overwrites the existing directive.
|
|
774
|
+
*/
|
|
775
|
+
interface CspDirectiveMerge {
|
|
776
|
+
readonly value: string;
|
|
777
|
+
readonly mode?: 'union' | 'replace';
|
|
778
|
+
}
|
|
779
|
+
/**
|
|
780
|
+
* Merge directives into an existing `Content-Security-Policy` header
|
|
781
|
+
* value without clobbering what a consumer (or another middleware)
|
|
782
|
+
* already configured. Directives not mentioned in `additions` pass
|
|
783
|
+
* through untouched; new directives are appended.
|
|
784
|
+
*
|
|
785
|
+
* This is the single CSP-merge implementation shared by every adapter.
|
|
786
|
+
*/
|
|
787
|
+
declare function mergeCspHeader(existing: string, additions: Readonly<Record<string, string | CspDirectiveMerge>>): string;
|
|
788
|
+
|
|
789
|
+
/**
|
|
790
|
+
* Environment introspection helpers.
|
|
791
|
+
*
|
|
792
|
+
* The detection layer needs to know whether the runtime is currently
|
|
793
|
+
* running inside an iframe, in a popup, in development mode, and
|
|
794
|
+
* whether environment variables are accessible. None of these checks
|
|
795
|
+
* is dramatic on its own, but having a single, tested module avoids
|
|
796
|
+
* the duplicated, subtly-different `typeof process` snippets that
|
|
797
|
+
* accumulated in the legacy implementation.
|
|
798
|
+
*
|
|
799
|
+
* @module @detection/environment
|
|
800
|
+
*/
|
|
801
|
+
/**
|
|
802
|
+
* Returns `true` when the current window is inside an iframe.
|
|
803
|
+
*
|
|
804
|
+
* Uses a try/catch around `window.self !== window.top` because the
|
|
805
|
+
* comparison throws in cross-origin iframes — and that very throw is
|
|
806
|
+
* itself a definitive "yes, I am in an iframe" signal.
|
|
807
|
+
*/
|
|
808
|
+
declare function isInIframe(): boolean;
|
|
809
|
+
/**
|
|
810
|
+
* Returns `true` when the current window was opened via `window.open()`
|
|
811
|
+
* and the opener reference is still accessible.
|
|
812
|
+
*/
|
|
813
|
+
declare function isInPopup(): boolean;
|
|
814
|
+
/**
|
|
815
|
+
* Returns `true` when running in a preview-capable context: iframe or
|
|
816
|
+
* popup. Anything else (top-level navigation) should skip live preview
|
|
817
|
+
* initialisation entirely.
|
|
818
|
+
*/
|
|
819
|
+
declare function isInPreviewContext(): boolean;
|
|
820
|
+
/**
|
|
821
|
+
* Returns `true` when the runtime appears to be in development mode.
|
|
822
|
+
*
|
|
823
|
+
* Detection order:
|
|
824
|
+
* 1. `process.env.NODE_ENV !== 'production'`
|
|
825
|
+
* 2. `import.meta.env.DEV === true` (Vite / Astro / SvelteKit)
|
|
826
|
+
* 3. `window.location.hostname` is `localhost`/`127.0.0.1`
|
|
827
|
+
*
|
|
828
|
+
* Returns `false` when none of those are available.
|
|
829
|
+
*/
|
|
830
|
+
declare function isDevMode(): boolean;
|
|
831
|
+
|
|
832
|
+
/**
|
|
833
|
+
* Origin detection and matching.
|
|
834
|
+
*
|
|
835
|
+
* Builds the set of trusted Payload admin origins and exposes a fast
|
|
836
|
+
* matcher used by the message bus. Three sources, in precedence order:
|
|
837
|
+
*
|
|
838
|
+
* 1. Explicit origins passed via configuration. Always trusted.
|
|
839
|
+
* 2. `document.referrer` — **only when no explicit origins are
|
|
840
|
+
* configured**. The browser writes the embedder's origin here
|
|
841
|
+
* when the iframe loads; using it as a fallback gives a
|
|
842
|
+
* zero-config path, but it must never widen an explicit
|
|
843
|
+
* allow-list (the referrer is whoever framed the page, which in
|
|
844
|
+
* an attack is the attacker).
|
|
845
|
+
* 3. Localhost pattern (any port on `localhost` / `127.0.0.1`),
|
|
846
|
+
* enabled when development mode is detected. Replaces the
|
|
847
|
+
* legacy hand-rolled port list.
|
|
848
|
+
*
|
|
849
|
+
* Once the parent's origin is *confirmed* through a successful message
|
|
850
|
+
* handshake, callers can call `lockOrigin()` to drop every other
|
|
851
|
+
* candidate, making subsequent matches exact.
|
|
852
|
+
*
|
|
853
|
+
* @module @detection/origin
|
|
854
|
+
*/
|
|
855
|
+
interface OriginDetectorOptions {
|
|
856
|
+
/** Explicit additional origins to trust. Always allowed. */
|
|
857
|
+
readonly additionalOrigins?: readonly string[];
|
|
858
|
+
/** Set to `false` to opt out of `document.referrer` detection. */
|
|
859
|
+
readonly enableReferrerDetection?: boolean;
|
|
860
|
+
/** Set to `false` to opt out of localhost-pattern matching. */
|
|
861
|
+
readonly enableLocalhostMatching?: boolean;
|
|
862
|
+
/**
|
|
863
|
+
* Override the dev-mode detection. When `undefined`, dev mode is
|
|
864
|
+
* inferred from the runtime (see `isDevMode`).
|
|
865
|
+
*/
|
|
866
|
+
readonly forceDevMode?: boolean;
|
|
867
|
+
/** Override `document.referrer` for tests. */
|
|
868
|
+
readonly referrer?: string;
|
|
869
|
+
}
|
|
870
|
+
/**
|
|
871
|
+
* Holds the resolved origin policy and exposes a matcher.
|
|
872
|
+
*
|
|
873
|
+
* The detector is stateful: it starts with the union of all candidate
|
|
874
|
+
* sources, then narrows down to a single, verified origin once
|
|
875
|
+
* `lockOrigin()` is called by the message bus on first valid handshake.
|
|
876
|
+
*/
|
|
877
|
+
declare class OriginDetector {
|
|
878
|
+
#private;
|
|
879
|
+
constructor(options?: OriginDetectorOptions);
|
|
880
|
+
/**
|
|
881
|
+
* Returns `true` when `origin` is trusted under the current policy.
|
|
882
|
+
*
|
|
883
|
+
* If an origin has been locked, only that exact value matches.
|
|
884
|
+
* Otherwise explicit origins and (in dev mode) localhost-pattern
|
|
885
|
+
* matches are accepted. The referrer-derived origin is a **fallback,
|
|
886
|
+
* not a union member**: it only counts when NO explicit origins are
|
|
887
|
+
* configured. `document.referrer` always names whoever actually
|
|
888
|
+
* framed the page — if it were unioned with the explicit list, any
|
|
889
|
+
* site embedding the page in an iframe would become a trusted
|
|
890
|
+
* postMessage sender despite the consumer having pinned the admin
|
|
891
|
+
* origin explicitly.
|
|
892
|
+
*/
|
|
893
|
+
matches(origin: string): boolean;
|
|
894
|
+
/**
|
|
895
|
+
* Narrow the trusted set to a single origin after a successful
|
|
896
|
+
* handshake. Subsequent calls to `matches()` only allow `origin`.
|
|
897
|
+
*
|
|
898
|
+
* Returns `true` if locking succeeded; `false` if the candidate is
|
|
899
|
+
* not currently considered trusted (which would defeat the purpose
|
|
900
|
+
* of locking).
|
|
901
|
+
*/
|
|
902
|
+
lockOrigin(origin: string): boolean;
|
|
903
|
+
/**
|
|
904
|
+
* Release the origin lock so any allow-listed origin is trusted
|
|
905
|
+
* again until the next successful handshake. The lifecycle calls
|
|
906
|
+
* this on heartbeat timeout — after the trusted parent disappears,
|
|
907
|
+
* a different (still-allowed) origin should be free to reconnect.
|
|
908
|
+
*
|
|
909
|
+
* Returns the previously-locked origin, or `undefined` when nothing
|
|
910
|
+
* was locked.
|
|
911
|
+
*/
|
|
912
|
+
unlockOrigin(): string | undefined;
|
|
913
|
+
/** The currently locked origin, or `undefined`. */
|
|
914
|
+
get lockedOrigin(): string | undefined;
|
|
915
|
+
/**
|
|
916
|
+
* Snapshot of every origin currently allowed by the policy. Used by
|
|
917
|
+
* the message bus to broadcast `ready` messages.
|
|
918
|
+
*
|
|
919
|
+
* Localhost pattern matches are expanded into the common dev ports
|
|
920
|
+
* so the handshake can find a Payload running on its usual port.
|
|
921
|
+
*/
|
|
922
|
+
enumerate(): string[];
|
|
923
|
+
/**
|
|
924
|
+
* Reports whether `document.referrer` contributed at least one
|
|
925
|
+
* origin. Useful for surfacing a console warning when running in
|
|
926
|
+
* production without an explicit origin and the referrer is absent
|
|
927
|
+
* (because of a `Referrer-Policy: no-referrer` header).
|
|
928
|
+
*/
|
|
929
|
+
get referrerWasAvailable(): boolean;
|
|
930
|
+
/**
|
|
931
|
+
* Indicates whether the detector is operating in a "production-without-
|
|
932
|
+
* explicit-origin" configuration. When `true`, the host should log a
|
|
933
|
+
* loud warning — silent connection failure is the worst outcome.
|
|
934
|
+
*/
|
|
935
|
+
get isProductionUnconfigured(): boolean;
|
|
936
|
+
/**
|
|
937
|
+
* `true` when the referrer is the ONLY trust source (no explicit
|
|
938
|
+
* origins, no localhost matching). In that mode any site that frames
|
|
939
|
+
* the page becomes a trusted postMessage sender — `document.referrer`
|
|
940
|
+
* always names the actual embedder. Content is still sanitized, but
|
|
941
|
+
* the origin allow-list no longer restricts who may drive it. Hosts
|
|
942
|
+
* should surface a warning suggesting explicit `allowedOrigins` and
|
|
943
|
+
* a `frame-ancestors` policy in production.
|
|
944
|
+
*/
|
|
945
|
+
get isReferrerOnlyTrust(): boolean;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
/**
|
|
949
|
+
* Locale detection.
|
|
950
|
+
*
|
|
951
|
+
* The runtime needs a sensible default locale for date/number renders
|
|
952
|
+
* before Payload tells us the active locale. Order of precedence:
|
|
953
|
+
*
|
|
954
|
+
* 1. The `lang` attribute on `<html>`.
|
|
955
|
+
* 2. `navigator.language`.
|
|
956
|
+
* 3. The hard-coded fallback `'en'`.
|
|
957
|
+
*
|
|
958
|
+
* The previous version of this library defaulted to `'de'` — a German
|
|
959
|
+
* default that surprised consumers in other locales. We now use the
|
|
960
|
+
* browser hint and fall back to English.
|
|
961
|
+
*
|
|
962
|
+
* @module @detection/locale
|
|
963
|
+
*/
|
|
964
|
+
/**
|
|
965
|
+
* Returns a BCP-47 locale string suitable for `Intl.*` constructors.
|
|
966
|
+
*
|
|
967
|
+
* Always returns a non-empty string. When no signal is available the
|
|
968
|
+
* fallback (`'en'`) is returned.
|
|
969
|
+
*/
|
|
970
|
+
declare function detectInitialLocale(): string;
|
|
971
|
+
|
|
972
|
+
/**
|
|
973
|
+
* Protocol-version negotiation.
|
|
974
|
+
*
|
|
975
|
+
* ⚠️ Reality check: **stock Payload does not version its live-preview
|
|
976
|
+
* postMessage protocol.** The admin never sends a `protocolVersion`
|
|
977
|
+
* field and ignores extra fields in our `ready` handshake. Observed
|
|
978
|
+
* wire behaviour:
|
|
979
|
+
*
|
|
980
|
+
* - Payload **2.x** sent `fieldSchemaJSON` with the first update
|
|
981
|
+
* (client-side merge era).
|
|
982
|
+
* - Payload **3.x** removed `fieldSchemaJSON` entirely; the official
|
|
983
|
+
* client re-fetches merged documents through the REST API instead.
|
|
984
|
+
* - No Payload version sends `previewToken` or any keepalive.
|
|
985
|
+
*
|
|
986
|
+
* This module therefore only matters for NON-stock admin panels — a
|
|
987
|
+
* custom `window.postMessage` sender can advertise a version and this
|
|
988
|
+
* library will negotiate `min(ours, theirs)` and expose capability
|
|
989
|
+
* flags. Against stock Payload, `theirs` stays `undefined`, negotiation
|
|
990
|
+
* collapses to v1, and nothing is gated on it: schema handling
|
|
991
|
+
* activates whenever `fieldSchemaJSON` is actually present, and token
|
|
992
|
+
* validation only runs when the consumer opts in.
|
|
993
|
+
*
|
|
994
|
+
* Version ladder (this library's own numbering, not Payload's):
|
|
995
|
+
*
|
|
996
|
+
* - **v1** — data + slug + locale (matches stock Payload of any era).
|
|
997
|
+
* - **v2** — remote sends `fieldSchemaJSON` (Payload 2.x behaviour).
|
|
998
|
+
* - **v3** — remote sends `previewToken` (custom senders only).
|
|
999
|
+
* - **v4** — nested-array recursion / richer document events
|
|
1000
|
+
* (custom senders only).
|
|
1001
|
+
*
|
|
1002
|
+
* @module @core/protocol-version
|
|
1003
|
+
*/
|
|
1004
|
+
declare const LIBRARY_PROTOCOL_VERSION = 4;
|
|
1005
|
+
/** Capability flag → minimum protocol version it requires. */
|
|
1006
|
+
declare const CAPABILITY_REQUIREMENTS: Readonly<Record<string, number>>;
|
|
1007
|
+
type ProtocolCapability = keyof typeof CAPABILITY_REQUIREMENTS;
|
|
1008
|
+
interface ProtocolNegotiation {
|
|
1009
|
+
/** This library's protocol version. */
|
|
1010
|
+
readonly ours: number;
|
|
1011
|
+
/** The remote party's version, when known. */
|
|
1012
|
+
readonly theirs: number | undefined;
|
|
1013
|
+
/** `min(ours, theirs ?? 1)` — the version both sides actually share. */
|
|
1014
|
+
readonly negotiated: number;
|
|
1015
|
+
/** Capability flags enabled at the negotiated version. */
|
|
1016
|
+
readonly capabilities: ReadonlySet<string>;
|
|
1017
|
+
}
|
|
1018
|
+
/**
|
|
1019
|
+
* Compute the negotiated view of the protocol.
|
|
1020
|
+
*
|
|
1021
|
+
* `theirs === undefined` collapses to v1 — assume the remote party is
|
|
1022
|
+
* an older Payload that does not advertise a version.
|
|
1023
|
+
*
|
|
1024
|
+
* Non-finite or non-positive `theirs` values fall back to v1 as well —
|
|
1025
|
+
* we never trust an obviously-corrupted negotiation byte.
|
|
1026
|
+
*/
|
|
1027
|
+
declare function negotiateProtocol(theirs: number | undefined): ProtocolNegotiation;
|
|
1028
|
+
declare function hasCapability(negotiation: ProtocolNegotiation, capability: ProtocolCapability): boolean;
|
|
1029
|
+
|
|
1030
|
+
/**
|
|
1031
|
+
* Type-level path utilities — used by the typed binding DSL to give
|
|
1032
|
+
* callers compile-time autocomplete on field names.
|
|
1033
|
+
*
|
|
1034
|
+
* The same path strings the runtime reads from `data-payload-field`
|
|
1035
|
+
* are typed here, so a typo like `'heroTitlee'` is rejected at compile
|
|
1036
|
+
* time *and* the runtime side does not need to change.
|
|
1037
|
+
*
|
|
1038
|
+
* @module @dsl/paths
|
|
1039
|
+
*/
|
|
1040
|
+
/**
|
|
1041
|
+
* All string-typed top-level keys of `T`. Non-string keys (numeric
|
|
1042
|
+
* indices on arrays) are filtered out.
|
|
1043
|
+
*/
|
|
1044
|
+
type FieldName<T> = Extract<keyof T, string>;
|
|
1045
|
+
/**
|
|
1046
|
+
* Dotted paths into nested object structures, capped at three levels
|
|
1047
|
+
* deep — sufficient for Payload's `group`/`tab`/`array` nesting
|
|
1048
|
+
* without ballooning the IDE's IntelliSense list past the point of
|
|
1049
|
+
* usefulness.
|
|
1050
|
+
*/
|
|
1051
|
+
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 ? {
|
|
1052
|
+
[K in Extract<keyof T, string>]: K | (T[K] extends object ? `${K}.${FieldPath<T[K], Prev<Depth>>}` : never);
|
|
1053
|
+
}[Extract<keyof T, string>] : never;
|
|
1054
|
+
/** Decrement helper for depth-bounded recursion. */
|
|
1055
|
+
type Prev<N extends 0 | 1 | 2 | 3> = N extends 3 ? 2 : N extends 2 ? 1 : N extends 1 ? 0 : 0;
|
|
1056
|
+
/**
|
|
1057
|
+
* Resolve the value type at a dotted path. Returns `unknown` when the
|
|
1058
|
+
* path does not exist on `T`, so consumers can opt into stricter
|
|
1059
|
+
* `keyof`-based DSLs when they want absolute type safety.
|
|
1060
|
+
*/
|
|
1061
|
+
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;
|
|
1062
|
+
|
|
1063
|
+
/**
|
|
1064
|
+
* Typed binding helpers — emit the `data-payload-field` attribute pair
|
|
1065
|
+
* that the live-preview runtime reads, with compile-time validation
|
|
1066
|
+
* of the field name.
|
|
1067
|
+
*
|
|
1068
|
+
* Two forms are exposed:
|
|
1069
|
+
*
|
|
1070
|
+
* - **`bind<Schema>('heroTitle')`** — string-literal form. Lightest
|
|
1071
|
+
* possible runtime (no Proxy), but a rename in the IDE does NOT
|
|
1072
|
+
* follow into the string.
|
|
1073
|
+
* - **`bindByPath<Schema>(s => s.heroTitle)`** — proxy form. The
|
|
1074
|
+
* callback walks a Proxy that records the access path, returning
|
|
1075
|
+
* the same shape as `bind` so the two are interchangeable. Slightly
|
|
1076
|
+
* heavier runtime, but rename-safe and discourages typos.
|
|
1077
|
+
*
|
|
1078
|
+
* Both helpers also accept a richer return — `bind('image', 'src')` —
|
|
1079
|
+
* to bind an attribute other than the default `data-payload-field`,
|
|
1080
|
+
* matching the cache resolver's `data-payload-attribute` flow.
|
|
1081
|
+
*
|
|
1082
|
+
* @module @dsl/bind
|
|
1083
|
+
*/
|
|
1084
|
+
|
|
1085
|
+
/**
|
|
1086
|
+
* Shape returned by every binding helper — a spreadable HTML attribute
|
|
1087
|
+
* record. Astro / JSX / Svelte all accept this style directly:
|
|
1088
|
+
*
|
|
1089
|
+
* ```astro
|
|
1090
|
+
* <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1091
|
+
* ```
|
|
1092
|
+
*/
|
|
1093
|
+
interface FieldBindingAttributes {
|
|
1094
|
+
readonly 'data-payload-field': string;
|
|
1095
|
+
readonly 'data-payload-attribute'?: string;
|
|
1096
|
+
readonly 'data-payload-type'?: string;
|
|
1097
|
+
}
|
|
1098
|
+
interface BindOptions {
|
|
1099
|
+
/** Override the bound attribute (e.g., `'src'` for `<img>`). Default: textContent. */
|
|
1100
|
+
readonly attribute?: string;
|
|
1101
|
+
/** Explicit field-type override — bypasses schema detection. */
|
|
1102
|
+
readonly type?: string;
|
|
1103
|
+
}
|
|
1104
|
+
/**
|
|
1105
|
+
* String-literal binding helper.
|
|
1106
|
+
*
|
|
1107
|
+
* ```ts
|
|
1108
|
+
* import { bind } from 'payload-live-preview';
|
|
1109
|
+
* import type { Homepage } from './payload-types';
|
|
1110
|
+
*
|
|
1111
|
+
* <h1 {...bind<Homepage>('heroTitle')}>{data.heroTitle}</h1>
|
|
1112
|
+
* <img {...bind<Homepage>('heroImage', { attribute: 'src' })} />
|
|
1113
|
+
* ```
|
|
1114
|
+
*
|
|
1115
|
+
* The generic `T` is the schema interface emitted by `pll-codegen`.
|
|
1116
|
+
* `field` is constrained to `keyof T` so misspellings fail at compile
|
|
1117
|
+
* time. When `T` is omitted, the helper accepts any string — useful
|
|
1118
|
+
* for incremental adoption.
|
|
1119
|
+
*/
|
|
1120
|
+
declare function bind<T = Record<string, unknown>>(field: FieldName<T>, options?: BindOptions): FieldBindingAttributes;
|
|
1121
|
+
/**
|
|
1122
|
+
* Proxy-path binding helper.
|
|
1123
|
+
*
|
|
1124
|
+
* ```ts
|
|
1125
|
+
* <h1 {...bindByPath<Homepage>(d => d.heroTitle)}>{data.heroTitle}</h1>
|
|
1126
|
+
* ```
|
|
1127
|
+
*
|
|
1128
|
+
* The picker is invoked once with a recording Proxy; the recorded path
|
|
1129
|
+
* is joined with `.` and emitted as the binding name. Property access
|
|
1130
|
+
* on nested objects and arrays both work — `d => d.slides[0].title`
|
|
1131
|
+
* becomes `slides.title` (array indices are normalised away because the
|
|
1132
|
+
* runtime resolves bindings against schema, not against positional
|
|
1133
|
+
* indices).
|
|
1134
|
+
*/
|
|
1135
|
+
declare function bindByPath<T = Record<string, unknown>>(picker: (data: T) => unknown, options?: BindOptions): FieldBindingAttributes;
|
|
1136
|
+
|
|
1137
|
+
export { generateCspNonce as A, type BindOptions as B, type CachedElement as C, hasCapability as D, EventEmitter as E, type FieldBindingAttributes as F, initLivePreview as G, isDevMode as H, isExternalHttpUrl as I, isInIframe as J, isInPopup as K, LIBRARY_PROTOCOL_VERSION as L, isInPreviewContext as M, isSafeUrl as N, OriginDetector as O, type PayloadFieldSchema as P, mergeCspHeader as Q, type RenderContext as R, type SanitizerDocument as S, negotiateProtocol as T, type Unsubscribe as U, VERSION as V, sanitizeHtml as W, setCspCrypto as X, setSanitizerDocument as Y, type CspDirectiveMerge as a, type EventHandler as b, type FieldName as c, type FieldPath as d, type FieldRenderer as e, type FieldTransform as f, type FieldType as g, type FrameAncestorsOptions as h, LivePreviewClient as i, type LivePreviewClientConfig as j, type LivePreviewEventMap as k, type LivePreviewPlugin as l, type PayloadFieldType as m, type PayloadLivePreviewData as n, type PayloadLivePreviewMessage as o, type PluginContext as p, type ProtocolCapability as q, type ProtocolNegotiation as r, type ValueAt as s, bind as t, bindByPath as u, buildFrameAncestors as v, buildScriptSrcWithNonce as w, detectInitialLocale as x, escapeHtml as y, escapeHtmlAttribute as z };
|