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
package/dist/index.js
ADDED
|
@@ -0,0 +1,4040 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
version: "1.0.0"};
|
|
4
|
+
|
|
5
|
+
// src/version.ts
|
|
6
|
+
var VERSION = package_default.version;
|
|
7
|
+
|
|
8
|
+
// src/core/cache.ts
|
|
9
|
+
var FIELD_ATTRIBUTE = "data-payload-field";
|
|
10
|
+
var TYPE_ATTRIBUTE = "data-payload-type";
|
|
11
|
+
var TARGET_ATTRIBUTE_ATTRIBUTE = "data-payload-attribute";
|
|
12
|
+
var HREF_ATTRIBUTE = "data-payload-href";
|
|
13
|
+
var SRC_ATTRIBUTE = "data-payload-src";
|
|
14
|
+
var ALT_ATTRIBUTE = "data-payload-alt";
|
|
15
|
+
var ARRAY_TEMPLATE_ATTRIBUTE = "data-payload-array-template";
|
|
16
|
+
var ARRAY_SEPARATOR_ATTRIBUTE = "data-payload-array-separator";
|
|
17
|
+
var LOCALE_ATTRIBUTE = "data-payload-locale";
|
|
18
|
+
var FIELD_SELECTOR = `[${FIELD_ATTRIBUTE}]`;
|
|
19
|
+
var VALID_FIELD_TYPES = /* @__PURE__ */ new Set([
|
|
20
|
+
"text",
|
|
21
|
+
"textarea",
|
|
22
|
+
"richText",
|
|
23
|
+
"email",
|
|
24
|
+
"number",
|
|
25
|
+
"checkbox",
|
|
26
|
+
"date",
|
|
27
|
+
"select",
|
|
28
|
+
"radio",
|
|
29
|
+
"array",
|
|
30
|
+
"blocks",
|
|
31
|
+
"group",
|
|
32
|
+
"tabs",
|
|
33
|
+
"row",
|
|
34
|
+
"collapsible",
|
|
35
|
+
"relationship",
|
|
36
|
+
"upload",
|
|
37
|
+
"point",
|
|
38
|
+
"json",
|
|
39
|
+
"code",
|
|
40
|
+
"ui",
|
|
41
|
+
"html",
|
|
42
|
+
"url",
|
|
43
|
+
"image",
|
|
44
|
+
"structural-array"
|
|
45
|
+
]);
|
|
46
|
+
var ElementCache = class {
|
|
47
|
+
#entries = /* @__PURE__ */ new Map();
|
|
48
|
+
#elementToEntry = /* @__PURE__ */ new WeakMap();
|
|
49
|
+
#filter;
|
|
50
|
+
constructor(options = {}) {
|
|
51
|
+
this.#filter = options.filter ?? alwaysTrue;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Rebuild the cache from `root`'s descendants. Pre-existing entries
|
|
55
|
+
* are discarded. Returns build statistics so callers can emit
|
|
56
|
+
* cache-refresh events with the durations populated.
|
|
57
|
+
*/
|
|
58
|
+
buildFromRoot(root) {
|
|
59
|
+
const t0 = performance.now();
|
|
60
|
+
this.#entries.clear();
|
|
61
|
+
const elements = root.querySelectorAll(FIELD_SELECTOR);
|
|
62
|
+
let elementCount = 0;
|
|
63
|
+
for (const element of elements) {
|
|
64
|
+
if (!this.#filter(element)) continue;
|
|
65
|
+
const entry = this.#resolveBinding(element);
|
|
66
|
+
if (!entry) continue;
|
|
67
|
+
this.#append(entry);
|
|
68
|
+
this.#elementToEntry.set(element, entry);
|
|
69
|
+
elementCount += 1;
|
|
70
|
+
}
|
|
71
|
+
return {
|
|
72
|
+
elementCount,
|
|
73
|
+
fieldCount: this.#entries.size,
|
|
74
|
+
durationMs: performance.now() - t0
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Insert a single element into the cache, returning the new entry
|
|
79
|
+
* (or `undefined` if the element does not declare a valid binding).
|
|
80
|
+
*/
|
|
81
|
+
add(element) {
|
|
82
|
+
if (!this.#filter(element)) return void 0;
|
|
83
|
+
const entry = this.#resolveBinding(element);
|
|
84
|
+
if (!entry) return void 0;
|
|
85
|
+
this.#append(entry);
|
|
86
|
+
this.#elementToEntry.set(element, entry);
|
|
87
|
+
return entry;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Remove the entry bound to `element`, if any. Returns `true` when a
|
|
91
|
+
* binding was actually removed.
|
|
92
|
+
*/
|
|
93
|
+
remove(element) {
|
|
94
|
+
const entry = this.#elementToEntry.get(element);
|
|
95
|
+
if (!entry) return false;
|
|
96
|
+
this.#elementToEntry.delete(element);
|
|
97
|
+
const bucket = this.#entries.get(entry.fieldName);
|
|
98
|
+
if (!bucket) return false;
|
|
99
|
+
const index = bucket.indexOf(entry);
|
|
100
|
+
if (index < 0) return false;
|
|
101
|
+
bucket.splice(index, 1);
|
|
102
|
+
if (bucket.length === 0) this.#entries.delete(entry.fieldName);
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
/** Returns the cached bindings for `fieldName`, or `undefined`. */
|
|
106
|
+
get(fieldName) {
|
|
107
|
+
return this.#entries.get(fieldName);
|
|
108
|
+
}
|
|
109
|
+
/** Returns the cache entry for `element`, or `undefined`. */
|
|
110
|
+
getByElement(element) {
|
|
111
|
+
return this.#elementToEntry.get(element);
|
|
112
|
+
}
|
|
113
|
+
/** Number of distinct field names currently cached. */
|
|
114
|
+
get fieldCount() {
|
|
115
|
+
return this.#entries.size;
|
|
116
|
+
}
|
|
117
|
+
/** Total number of cached element bindings. */
|
|
118
|
+
get elementCount() {
|
|
119
|
+
let count = 0;
|
|
120
|
+
for (const bucket of this.#entries.values()) count += bucket.length;
|
|
121
|
+
return count;
|
|
122
|
+
}
|
|
123
|
+
/** Iterate `[fieldName, bindings]` pairs. */
|
|
124
|
+
entries() {
|
|
125
|
+
return this.#entries.entries();
|
|
126
|
+
}
|
|
127
|
+
/** Iterate every cached binding. */
|
|
128
|
+
*values() {
|
|
129
|
+
for (const bucket of this.#entries.values()) yield* bucket;
|
|
130
|
+
}
|
|
131
|
+
/** Test whether `element` is registered with the cache. */
|
|
132
|
+
has(element) {
|
|
133
|
+
return this.#elementToEntry.has(element);
|
|
134
|
+
}
|
|
135
|
+
/** Remove every entry. */
|
|
136
|
+
clear() {
|
|
137
|
+
this.#entries.clear();
|
|
138
|
+
}
|
|
139
|
+
#append(entry) {
|
|
140
|
+
const bucket = this.#entries.get(entry.fieldName);
|
|
141
|
+
if (bucket) {
|
|
142
|
+
bucket.push(entry);
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.#entries.set(entry.fieldName, [entry]);
|
|
146
|
+
}
|
|
147
|
+
#resolveBinding(element) {
|
|
148
|
+
const fieldName = element.getAttribute(FIELD_ATTRIBUTE);
|
|
149
|
+
if (fieldName === null || fieldName.length === 0) return void 0;
|
|
150
|
+
const explicit = element.getAttribute(TYPE_ATTRIBUTE);
|
|
151
|
+
const fieldType = resolveFieldType(element);
|
|
152
|
+
const targetAttribute = element.getAttribute(TARGET_ATTRIBUTE_ATTRIBUTE);
|
|
153
|
+
const hrefField = element.getAttribute(HREF_ATTRIBUTE);
|
|
154
|
+
const srcField = element.getAttribute(SRC_ATTRIBUTE);
|
|
155
|
+
const altField = element.getAttribute(ALT_ATTRIBUTE);
|
|
156
|
+
const arrayTemplate = element.getAttribute(ARRAY_TEMPLATE_ATTRIBUTE);
|
|
157
|
+
const arraySeparator = element.getAttribute(ARRAY_SEPARATOR_ATTRIBUTE);
|
|
158
|
+
const locale = element.getAttribute(LOCALE_ATTRIBUTE);
|
|
159
|
+
const entry = {
|
|
160
|
+
element,
|
|
161
|
+
fieldName,
|
|
162
|
+
fieldType,
|
|
163
|
+
explicitFieldType: explicit !== null && VALID_FIELD_TYPES.has(explicit),
|
|
164
|
+
...targetAttribute !== null && targetAttribute.length > 0 ? { targetAttribute } : {},
|
|
165
|
+
...hrefField !== null ? { hrefField } : {},
|
|
166
|
+
...srcField !== null ? { srcField } : {},
|
|
167
|
+
...altField !== null ? { altField } : {},
|
|
168
|
+
...arrayTemplate !== null ? { arrayTemplate } : {},
|
|
169
|
+
...arraySeparator !== null ? { arraySeparator } : {},
|
|
170
|
+
...locale !== null ? { locale } : {}
|
|
171
|
+
};
|
|
172
|
+
return entry;
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
function resolveFieldType(element) {
|
|
176
|
+
const explicit = element.getAttribute(TYPE_ATTRIBUTE);
|
|
177
|
+
if (explicit !== null && VALID_FIELD_TYPES.has(explicit)) {
|
|
178
|
+
return explicit;
|
|
179
|
+
}
|
|
180
|
+
if (element.hasAttribute("data-payload-richtext")) return "richText";
|
|
181
|
+
if (element.hasAttribute("data-payload-html")) return "html";
|
|
182
|
+
if (element.hasAttribute("data-payload-structural")) return "structural-array";
|
|
183
|
+
if (element.hasAttribute("data-payload-array")) return "array";
|
|
184
|
+
if (element.tagName === "IMG") return "image";
|
|
185
|
+
if (element.tagName === "A") return "url";
|
|
186
|
+
if (element.tagName === "TIME") return "date";
|
|
187
|
+
if (element.tagName === "INPUT") {
|
|
188
|
+
const inputType = element.type;
|
|
189
|
+
if (inputType === "checkbox") return "checkbox";
|
|
190
|
+
if (inputType === "number") return "number";
|
|
191
|
+
if (inputType === "date" || inputType === "datetime-local") return "date";
|
|
192
|
+
}
|
|
193
|
+
return "text";
|
|
194
|
+
}
|
|
195
|
+
function alwaysTrue() {
|
|
196
|
+
return true;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// src/core/observers.ts
|
|
200
|
+
var DEFAULT_DEBOUNCE_MS = 100;
|
|
201
|
+
var DEFAULT_ROOT_MARGIN = "200px";
|
|
202
|
+
var ObserverManager = class {
|
|
203
|
+
#callbacks;
|
|
204
|
+
#debounceMs;
|
|
205
|
+
#rootMargin;
|
|
206
|
+
#visible = /* @__PURE__ */ new Set();
|
|
207
|
+
#mutation = null;
|
|
208
|
+
#intersection = null;
|
|
209
|
+
#debounceTimer = null;
|
|
210
|
+
constructor(callbacks, options = {}) {
|
|
211
|
+
this.#callbacks = callbacks;
|
|
212
|
+
this.#debounceMs = options.mutationDebounceMs ?? DEFAULT_DEBOUNCE_MS;
|
|
213
|
+
this.#rootMargin = options.intersectionRootMargin ?? DEFAULT_ROOT_MARGIN;
|
|
214
|
+
}
|
|
215
|
+
/** Begin observing `root` for mutations and create the intersection observer. */
|
|
216
|
+
start(root) {
|
|
217
|
+
this.#mutation = new MutationObserver((mutations) => {
|
|
218
|
+
this.#handleMutations(mutations);
|
|
219
|
+
});
|
|
220
|
+
this.#mutation.observe(root, {
|
|
221
|
+
childList: true,
|
|
222
|
+
subtree: true,
|
|
223
|
+
attributes: true,
|
|
224
|
+
attributeFilter: [FIELD_ATTRIBUTE]
|
|
225
|
+
});
|
|
226
|
+
this.#intersection = new IntersectionObserver(
|
|
227
|
+
(entries) => {
|
|
228
|
+
this.#handleIntersection(entries);
|
|
229
|
+
},
|
|
230
|
+
{ rootMargin: this.#rootMargin, threshold: 0 }
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
/** Add an element to the intersection observer. */
|
|
234
|
+
observeElement(element) {
|
|
235
|
+
this.#intersection?.observe(element);
|
|
236
|
+
}
|
|
237
|
+
/** Remove an element from the intersection observer. */
|
|
238
|
+
unobserveElement(element) {
|
|
239
|
+
this.#intersection?.unobserve(element);
|
|
240
|
+
this.#visible.delete(element);
|
|
241
|
+
}
|
|
242
|
+
/** Mark an element as currently visible. Useful for tests/seeding. */
|
|
243
|
+
markVisible(element, visible) {
|
|
244
|
+
if (visible) this.#visible.add(element);
|
|
245
|
+
else this.#visible.delete(element);
|
|
246
|
+
}
|
|
247
|
+
/** Is `element` currently within the (margined) viewport? */
|
|
248
|
+
isVisible(element) {
|
|
249
|
+
return this.#visible.has(element);
|
|
250
|
+
}
|
|
251
|
+
/** Disconnect all observers and clear pending timers. */
|
|
252
|
+
stop() {
|
|
253
|
+
if (this.#debounceTimer !== null) {
|
|
254
|
+
clearTimeout(this.#debounceTimer);
|
|
255
|
+
this.#debounceTimer = null;
|
|
256
|
+
}
|
|
257
|
+
this.#mutation?.disconnect();
|
|
258
|
+
this.#intersection?.disconnect();
|
|
259
|
+
this.#mutation = null;
|
|
260
|
+
this.#intersection = null;
|
|
261
|
+
this.#visible.clear();
|
|
262
|
+
}
|
|
263
|
+
#handleMutations(mutations) {
|
|
264
|
+
if (!hasStructuralImpact(mutations)) return;
|
|
265
|
+
if (this.#debounceTimer !== null) clearTimeout(this.#debounceTimer);
|
|
266
|
+
this.#debounceTimer = setTimeout(() => {
|
|
267
|
+
this.#debounceTimer = null;
|
|
268
|
+
this.#callbacks.onStructuralChange();
|
|
269
|
+
}, this.#debounceMs);
|
|
270
|
+
}
|
|
271
|
+
#handleIntersection(entries) {
|
|
272
|
+
for (const entry of entries) {
|
|
273
|
+
const target = entry.target;
|
|
274
|
+
if (entry.isIntersecting) {
|
|
275
|
+
if (!this.#visible.has(target)) {
|
|
276
|
+
this.#visible.add(target);
|
|
277
|
+
this.#callbacks.onVisibilityChange(target, true);
|
|
278
|
+
}
|
|
279
|
+
} else if (this.#visible.has(target)) {
|
|
280
|
+
this.#visible.delete(target);
|
|
281
|
+
this.#callbacks.onVisibilityChange(target, false);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
function hasStructuralImpact(mutations) {
|
|
287
|
+
for (const m of mutations) {
|
|
288
|
+
if (m.type === "attributes" && m.attributeName === FIELD_ATTRIBUTE) return true;
|
|
289
|
+
for (const node of m.addedNodes) {
|
|
290
|
+
if (containsTrackedElement(node)) return true;
|
|
291
|
+
}
|
|
292
|
+
for (const node of m.removedNodes) {
|
|
293
|
+
if (containsTrackedElement(node)) return true;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return false;
|
|
297
|
+
}
|
|
298
|
+
function containsTrackedElement(node) {
|
|
299
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return false;
|
|
300
|
+
const element = node;
|
|
301
|
+
if (element.hasAttribute(FIELD_ATTRIBUTE)) return true;
|
|
302
|
+
return element.querySelector(`[${FIELD_ATTRIBUTE}]`) !== null;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// src/core/protocol-version.ts
|
|
306
|
+
var LIBRARY_PROTOCOL_VERSION = 4;
|
|
307
|
+
var CAPABILITY_REQUIREMENTS = {
|
|
308
|
+
basic: 1,
|
|
309
|
+
"schema-json": 2,
|
|
310
|
+
"preview-token": 3,
|
|
311
|
+
"nested-arrays": 4,
|
|
312
|
+
"recursive-diffs": 4
|
|
313
|
+
};
|
|
314
|
+
function negotiateProtocol(theirs) {
|
|
315
|
+
const sanitisedTheirs = typeof theirs === "number" && Number.isFinite(theirs) && theirs >= 1 ? Math.floor(theirs) : void 0;
|
|
316
|
+
const effectiveTheirs = sanitisedTheirs ?? 1;
|
|
317
|
+
const negotiated = Math.min(LIBRARY_PROTOCOL_VERSION, effectiveTheirs);
|
|
318
|
+
const capabilities = /* @__PURE__ */ new Set();
|
|
319
|
+
for (const [flag, minVersion] of Object.entries(CAPABILITY_REQUIREMENTS)) {
|
|
320
|
+
if (negotiated >= minVersion) capabilities.add(flag);
|
|
321
|
+
}
|
|
322
|
+
return {
|
|
323
|
+
ours: LIBRARY_PROTOCOL_VERSION,
|
|
324
|
+
...sanitisedTheirs !== void 0 ? { theirs: sanitisedTheirs } : { theirs: void 0 },
|
|
325
|
+
negotiated,
|
|
326
|
+
capabilities
|
|
327
|
+
};
|
|
328
|
+
}
|
|
329
|
+
function hasCapability(negotiation, capability) {
|
|
330
|
+
return negotiation.capabilities.has(capability);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// src/core/message-bus.ts
|
|
334
|
+
var MessageBus = class {
|
|
335
|
+
#matcher;
|
|
336
|
+
#handlers;
|
|
337
|
+
#boundListener;
|
|
338
|
+
#attached = false;
|
|
339
|
+
constructor(matcher, handlers) {
|
|
340
|
+
this.#matcher = matcher;
|
|
341
|
+
this.#handlers = handlers;
|
|
342
|
+
this.#boundListener = (event) => {
|
|
343
|
+
this.#receive(event);
|
|
344
|
+
};
|
|
345
|
+
}
|
|
346
|
+
/** Begin listening for window-level `message` events. Idempotent. */
|
|
347
|
+
attach(target = window) {
|
|
348
|
+
if (this.#attached) return;
|
|
349
|
+
target.addEventListener("message", this.#boundListener);
|
|
350
|
+
this.#attached = true;
|
|
351
|
+
}
|
|
352
|
+
/** Stop listening. Idempotent. */
|
|
353
|
+
detach(target = window) {
|
|
354
|
+
if (!this.#attached) return;
|
|
355
|
+
target.removeEventListener("message", this.#boundListener);
|
|
356
|
+
this.#attached = false;
|
|
357
|
+
}
|
|
358
|
+
/**
|
|
359
|
+
* Send a `ready` handshake message to one or more parent windows.
|
|
360
|
+
*
|
|
361
|
+
* `targets` are the windows to notify (parent and/or opener). For
|
|
362
|
+
* each target the message is sent to every `origin` so the Payload
|
|
363
|
+
* admin in the parent receives it regardless of which origin it
|
|
364
|
+
* happens to be served on.
|
|
365
|
+
*/
|
|
366
|
+
static sendReady(targets, origins) {
|
|
367
|
+
if (targets.length === 0 || origins.length === 0) return;
|
|
368
|
+
const payload = {
|
|
369
|
+
type: "payload-live-preview",
|
|
370
|
+
ready: true,
|
|
371
|
+
protocolVersion: LIBRARY_PROTOCOL_VERSION
|
|
372
|
+
};
|
|
373
|
+
for (const target of targets) {
|
|
374
|
+
for (const origin of origins) {
|
|
375
|
+
try {
|
|
376
|
+
target.postMessage(payload, origin);
|
|
377
|
+
} catch {
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
#receive(event) {
|
|
383
|
+
const origin = event.origin;
|
|
384
|
+
if (!this.#matcher(origin)) {
|
|
385
|
+
this.#handlers.onInvalid?.("origin", origin);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
const data = event.data;
|
|
389
|
+
if (!isObjectMessage(data)) {
|
|
390
|
+
this.#handlers.onInvalid?.("shape", origin);
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
switch (data.type) {
|
|
394
|
+
case "payload-live-preview":
|
|
395
|
+
this.#dispatchUpdate(data, origin);
|
|
396
|
+
return;
|
|
397
|
+
case "payload-document-event":
|
|
398
|
+
this.#handlers.onDocumentEvent(data, origin);
|
|
399
|
+
return;
|
|
400
|
+
default:
|
|
401
|
+
this.#handlers.onInvalid?.("type", origin);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
#dispatchUpdate(message, origin) {
|
|
405
|
+
const validator = this.#handlers.validateToken;
|
|
406
|
+
if (validator === void 0) {
|
|
407
|
+
this.#handlers.onUpdate(message, origin);
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
if (message.ready === true && message.data === void 0) {
|
|
411
|
+
this.#handlers.onUpdate(message, origin);
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
let approved;
|
|
415
|
+
try {
|
|
416
|
+
approved = validator(message.previewToken, origin);
|
|
417
|
+
} catch {
|
|
418
|
+
this.#handlers.onInvalid?.("token", origin);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (typeof approved === "boolean") {
|
|
422
|
+
if (approved) this.#handlers.onUpdate(message, origin);
|
|
423
|
+
else this.#handlers.onInvalid?.("token", origin);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
void approved.then(
|
|
427
|
+
(ok) => {
|
|
428
|
+
if (ok) this.#handlers.onUpdate(message, origin);
|
|
429
|
+
else this.#handlers.onInvalid?.("token", origin);
|
|
430
|
+
},
|
|
431
|
+
() => {
|
|
432
|
+
this.#handlers.onInvalid?.("token", origin);
|
|
433
|
+
}
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
};
|
|
437
|
+
function isObjectMessage(value) {
|
|
438
|
+
if (typeof value !== "object" || value === null) return false;
|
|
439
|
+
if (!("type" in value)) return false;
|
|
440
|
+
return typeof value.type === "string";
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// src/core/state.ts
|
|
444
|
+
var DEFAULT_TIMEOUT_MS = 0;
|
|
445
|
+
var HeartbeatTimer = class {
|
|
446
|
+
#timeoutMs;
|
|
447
|
+
#onTimeout;
|
|
448
|
+
#handle = null;
|
|
449
|
+
#lastKick = 0;
|
|
450
|
+
constructor(options) {
|
|
451
|
+
this.#timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
452
|
+
this.#onTimeout = options.onTimeout;
|
|
453
|
+
}
|
|
454
|
+
/** Reset the timer. Schedules `onTimeout` after `timeoutMs`. */
|
|
455
|
+
kick() {
|
|
456
|
+
this.#lastKick = Date.now();
|
|
457
|
+
if (this.#timeoutMs <= 0) return;
|
|
458
|
+
if (this.#handle !== null) clearTimeout(this.#handle);
|
|
459
|
+
this.#handle = setTimeout(() => {
|
|
460
|
+
this.#handle = null;
|
|
461
|
+
this.#onTimeout();
|
|
462
|
+
}, this.#timeoutMs);
|
|
463
|
+
}
|
|
464
|
+
/** Cancel any pending timeout. Safe to call repeatedly. */
|
|
465
|
+
stop() {
|
|
466
|
+
if (this.#handle === null) return;
|
|
467
|
+
clearTimeout(this.#handle);
|
|
468
|
+
this.#handle = null;
|
|
469
|
+
}
|
|
470
|
+
/** Test introspection: timestamp of the most recent kick (ms epoch). */
|
|
471
|
+
get lastKickAt() {
|
|
472
|
+
return this.#lastKick;
|
|
473
|
+
}
|
|
474
|
+
/** Test introspection: is a timeout currently scheduled? */
|
|
475
|
+
get pending() {
|
|
476
|
+
return this.#handle !== null;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
var ConnectionState = class {
|
|
480
|
+
#status = "disconnected";
|
|
481
|
+
#onChange;
|
|
482
|
+
constructor(onChange) {
|
|
483
|
+
this.#onChange = onChange;
|
|
484
|
+
}
|
|
485
|
+
get status() {
|
|
486
|
+
return this.#status;
|
|
487
|
+
}
|
|
488
|
+
/** Mark as `connected`; idempotent. Returns true if state transitioned. */
|
|
489
|
+
markConnected() {
|
|
490
|
+
return this.#transition("connected");
|
|
491
|
+
}
|
|
492
|
+
/** Mark as `connecting`; idempotent. */
|
|
493
|
+
markConnecting() {
|
|
494
|
+
return this.#transition("connecting");
|
|
495
|
+
}
|
|
496
|
+
/** Mark as `disconnected`; idempotent. */
|
|
497
|
+
markDisconnected() {
|
|
498
|
+
return this.#transition("disconnected");
|
|
499
|
+
}
|
|
500
|
+
#transition(next) {
|
|
501
|
+
if (this.#status === next) return false;
|
|
502
|
+
const previous = this.#status;
|
|
503
|
+
this.#status = next;
|
|
504
|
+
this.#onChange(next, previous);
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
// src/core/data-merger.ts
|
|
510
|
+
var noop = () => void 0;
|
|
511
|
+
var DataMerger = class {
|
|
512
|
+
#serverURL;
|
|
513
|
+
#apiRoute;
|
|
514
|
+
#depth;
|
|
515
|
+
#fetchFn;
|
|
516
|
+
#log;
|
|
517
|
+
#inflight = null;
|
|
518
|
+
constructor(options) {
|
|
519
|
+
this.#serverURL = options.serverURL.replace(/\/+$/, "");
|
|
520
|
+
const route = options.apiRoute ?? "/api";
|
|
521
|
+
this.#apiRoute = route.startsWith("/") ? route : `/${route}`;
|
|
522
|
+
this.#depth = options.depth ?? 1;
|
|
523
|
+
this.#fetchFn = options.fetchFn;
|
|
524
|
+
this.#log = options.log ?? noop;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Whether this message can be merged at all: collections need an
|
|
528
|
+
* `id` in the form values (the admin always includes one), globals
|
|
529
|
+
* only need their slug.
|
|
530
|
+
*/
|
|
531
|
+
canMerge(request) {
|
|
532
|
+
if (request.globalSlug !== void 0 && request.globalSlug !== "") return true;
|
|
533
|
+
if (request.collectionSlug === void 0 || request.collectionSlug === "") return false;
|
|
534
|
+
const id = request.data["id"];
|
|
535
|
+
return typeof id === "string" || typeof id === "number";
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Merge raw form values into the stored document via the Payload
|
|
539
|
+
* REST API. See {@link MergeResult} for the outcome semantics.
|
|
540
|
+
*/
|
|
541
|
+
async merge(request) {
|
|
542
|
+
if (!this.canMerge(request)) return { status: "unavailable" };
|
|
543
|
+
const fetchFn = this.#fetchFn ?? (typeof fetch === "function" ? fetch : void 0);
|
|
544
|
+
if (fetchFn === void 0) return { status: "unavailable" };
|
|
545
|
+
this.#inflight?.abort();
|
|
546
|
+
const controller = new AbortController();
|
|
547
|
+
this.#inflight = controller;
|
|
548
|
+
const endpoint = request.globalSlug !== void 0 && request.globalSlug !== "" ? `globals/${request.globalSlug}` : `${request.collectionSlug}/${String(request.data["id"])}`;
|
|
549
|
+
const url = `${this.#serverURL}${this.#apiRoute}/${encodeURI(endpoint)}`;
|
|
550
|
+
try {
|
|
551
|
+
const response = await fetchFn(url, {
|
|
552
|
+
method: "POST",
|
|
553
|
+
credentials: "include",
|
|
554
|
+
headers: {
|
|
555
|
+
"Content-Type": "application/json",
|
|
556
|
+
// Payload's REST layer treats this POST as a GET with a body:
|
|
557
|
+
// the body's `data` is merged into the stored document and the
|
|
558
|
+
// populated result is returned.
|
|
559
|
+
"X-Payload-HTTP-Method-Override": "GET"
|
|
560
|
+
},
|
|
561
|
+
body: JSON.stringify({
|
|
562
|
+
data: request.data,
|
|
563
|
+
depth: this.#depth,
|
|
564
|
+
// The admin already flattens locales before posting.
|
|
565
|
+
flattenLocales: false,
|
|
566
|
+
...request.locale !== void 0 ? { locale: request.locale } : {}
|
|
567
|
+
}),
|
|
568
|
+
signal: controller.signal
|
|
569
|
+
});
|
|
570
|
+
if (!response.ok) {
|
|
571
|
+
this.#log("merge fetch failed", response.status, url);
|
|
572
|
+
return { status: "unavailable" };
|
|
573
|
+
}
|
|
574
|
+
const merged = await response.json();
|
|
575
|
+
if (merged === null || typeof merged !== "object" || Array.isArray(merged)) {
|
|
576
|
+
this.#log("merge fetch returned non-object", url);
|
|
577
|
+
return { status: "unavailable" };
|
|
578
|
+
}
|
|
579
|
+
return { status: "merged", doc: merged };
|
|
580
|
+
} catch (error) {
|
|
581
|
+
if (controller.signal.aborted) return { status: "superseded" };
|
|
582
|
+
this.#log("merge fetch error", error);
|
|
583
|
+
return { status: "unavailable" };
|
|
584
|
+
} finally {
|
|
585
|
+
if (this.#inflight === controller) this.#inflight = null;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
/** Abort any in-flight merge request. */
|
|
589
|
+
destroy() {
|
|
590
|
+
this.#inflight?.abort();
|
|
591
|
+
this.#inflight = null;
|
|
592
|
+
}
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
// src/security/url-validator.ts
|
|
596
|
+
var SAFE_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:", "mailto:", "tel:"]);
|
|
597
|
+
var DANGEROUS_PROTOCOL_PATTERN = /^\s*(?:javascript|data|vbscript|file|blob|about)\s*:/i;
|
|
598
|
+
var RELATIVE_PATH = /^(?:\.{1,2}\/|[a-zA-Z0-9_-]+\/?)/;
|
|
599
|
+
function isSafeUrl(url) {
|
|
600
|
+
if (typeof url !== "string" || url.length === 0) return false;
|
|
601
|
+
const trimmed = url.trim();
|
|
602
|
+
if (trimmed.length === 0) return false;
|
|
603
|
+
if (DANGEROUS_PROTOCOL_PATTERN.test(trimmed)) return false;
|
|
604
|
+
if (trimmed.startsWith("#") || trimmed.startsWith("?")) return true;
|
|
605
|
+
if (trimmed.startsWith("//")) return true;
|
|
606
|
+
if (trimmed.startsWith("/")) return true;
|
|
607
|
+
try {
|
|
608
|
+
const parsed = new URL(trimmed);
|
|
609
|
+
return SAFE_PROTOCOLS.has(parsed.protocol);
|
|
610
|
+
} catch {
|
|
611
|
+
return RELATIVE_PATH.test(trimmed);
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function isExternalHttpUrl(url) {
|
|
615
|
+
if (!isSafeUrl(url)) return false;
|
|
616
|
+
const trimmed = url.trim();
|
|
617
|
+
return /^https?:\/\//i.test(trimmed) || /^\/\/[^/]/.test(trimmed);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
// src/core/attribute-binding.ts
|
|
621
|
+
var BLOCKED_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
622
|
+
"style",
|
|
623
|
+
"srcdoc",
|
|
624
|
+
"formaction",
|
|
625
|
+
"form",
|
|
626
|
+
"id",
|
|
627
|
+
"name",
|
|
628
|
+
"is",
|
|
629
|
+
"srcset",
|
|
630
|
+
"imagesrcset"
|
|
631
|
+
]);
|
|
632
|
+
var URL_ATTRIBUTES = /* @__PURE__ */ new Set([
|
|
633
|
+
"href",
|
|
634
|
+
"src",
|
|
635
|
+
"poster",
|
|
636
|
+
"cite",
|
|
637
|
+
"action",
|
|
638
|
+
"xlink:href",
|
|
639
|
+
"data"
|
|
640
|
+
]);
|
|
641
|
+
function applyAttributeBinding(element, attribute, value) {
|
|
642
|
+
const name = attribute.toLowerCase().trim();
|
|
643
|
+
if (name.length === 0) return "blocked";
|
|
644
|
+
if (name.startsWith("on")) return "blocked";
|
|
645
|
+
if (BLOCKED_ATTRIBUTES.has(name)) return "blocked";
|
|
646
|
+
if (value === null || value === void 0) {
|
|
647
|
+
element.removeAttribute(name);
|
|
648
|
+
return "applied";
|
|
649
|
+
}
|
|
650
|
+
if (typeof value !== "string" && typeof value !== "number" && typeof value !== "boolean") {
|
|
651
|
+
return "blocked";
|
|
652
|
+
}
|
|
653
|
+
const stringValue = String(value);
|
|
654
|
+
if (URL_ATTRIBUTES.has(name) && !isSafeUrl(stringValue)) return "blocked";
|
|
655
|
+
element.setAttribute(name, stringValue);
|
|
656
|
+
return "applied";
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// src/core/update-scheduler.ts
|
|
660
|
+
var DEFAULT_DEBOUNCE_MS2 = 50;
|
|
661
|
+
var DEFAULT_VISIBILITY_THRESHOLD = 50;
|
|
662
|
+
var UpdateScheduler = class {
|
|
663
|
+
#apply;
|
|
664
|
+
#debounceMs;
|
|
665
|
+
#isVisible;
|
|
666
|
+
#gateThreshold;
|
|
667
|
+
#gateDisabled;
|
|
668
|
+
#getCacheSize;
|
|
669
|
+
#onFlush;
|
|
670
|
+
#scheduleFrame;
|
|
671
|
+
#cancelFrame;
|
|
672
|
+
/** Per-element latest pending value, applied on next flush. */
|
|
673
|
+
#pending = /* @__PURE__ */ new Map();
|
|
674
|
+
/** Per-element value to apply when element becomes visible. */
|
|
675
|
+
#replay = /* @__PURE__ */ new Map();
|
|
676
|
+
#debounceTimer = null;
|
|
677
|
+
#frameHandle = null;
|
|
678
|
+
constructor(apply, options) {
|
|
679
|
+
this.#apply = apply;
|
|
680
|
+
this.#debounceMs = options.debounceMs ?? DEFAULT_DEBOUNCE_MS2;
|
|
681
|
+
this.#isVisible = options.isVisible;
|
|
682
|
+
this.#gateThreshold = options.visibilityGateThreshold ?? DEFAULT_VISIBILITY_THRESHOLD;
|
|
683
|
+
this.#gateDisabled = options.disableVisibilityGate ?? false;
|
|
684
|
+
this.#getCacheSize = options.getCacheSize;
|
|
685
|
+
this.#onFlush = options.onFlush;
|
|
686
|
+
this.#scheduleFrame = options.scheduleFrame ?? (typeof requestAnimationFrame === "function" ? requestAnimationFrame.bind(globalThis) : (cb) => {
|
|
687
|
+
return setTimeout(() => {
|
|
688
|
+
cb(performance.now());
|
|
689
|
+
}, 0);
|
|
690
|
+
});
|
|
691
|
+
this.#cancelFrame = options.cancelFrame ?? (typeof cancelAnimationFrame === "function" ? cancelAnimationFrame.bind(globalThis) : (handle) => {
|
|
692
|
+
clearTimeout(handle);
|
|
693
|
+
});
|
|
694
|
+
}
|
|
695
|
+
/**
|
|
696
|
+
* Queue an update. The same `(element)` pair is coalesced — only the
|
|
697
|
+
* most-recent value survives until the next flush.
|
|
698
|
+
*/
|
|
699
|
+
schedule(update) {
|
|
700
|
+
const existing = this.#pending.get(update.target.element);
|
|
701
|
+
if (existing) {
|
|
702
|
+
existing.value = update.value;
|
|
703
|
+
existing.allFields = update.allFields;
|
|
704
|
+
} else {
|
|
705
|
+
this.#pending.set(update.target.element, {
|
|
706
|
+
target: update.target,
|
|
707
|
+
value: update.value,
|
|
708
|
+
allFields: update.allFields
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
this.#armDebounce();
|
|
712
|
+
}
|
|
713
|
+
/**
|
|
714
|
+
* Flush every pending write immediately, bypassing the debounce.
|
|
715
|
+
* Useful for tests and for `destroy()` to drain in-flight work.
|
|
716
|
+
*/
|
|
717
|
+
flushNow() {
|
|
718
|
+
if (this.#debounceTimer !== null) {
|
|
719
|
+
clearTimeout(this.#debounceTimer);
|
|
720
|
+
this.#debounceTimer = null;
|
|
721
|
+
}
|
|
722
|
+
return this.#flush();
|
|
723
|
+
}
|
|
724
|
+
/**
|
|
725
|
+
* Signal that an element has become visible. Any buffered value for
|
|
726
|
+
* that element is applied immediately.
|
|
727
|
+
*/
|
|
728
|
+
notifyVisible(element) {
|
|
729
|
+
const entry = this.#replay.get(element);
|
|
730
|
+
if (!entry) return;
|
|
731
|
+
this.#replay.delete(element);
|
|
732
|
+
this.#apply(entry);
|
|
733
|
+
}
|
|
734
|
+
/**
|
|
735
|
+
* Discard any replay state for an element. Called when the element
|
|
736
|
+
* leaves the cache.
|
|
737
|
+
*/
|
|
738
|
+
forget(element) {
|
|
739
|
+
this.#pending.delete(element);
|
|
740
|
+
this.#replay.delete(element);
|
|
741
|
+
}
|
|
742
|
+
/** Cancel timers and drop buffered state. */
|
|
743
|
+
destroy() {
|
|
744
|
+
if (this.#debounceTimer !== null) {
|
|
745
|
+
clearTimeout(this.#debounceTimer);
|
|
746
|
+
this.#debounceTimer = null;
|
|
747
|
+
}
|
|
748
|
+
if (this.#frameHandle !== null) {
|
|
749
|
+
this.#cancelFrame(this.#frameHandle);
|
|
750
|
+
this.#frameHandle = null;
|
|
751
|
+
}
|
|
752
|
+
this.#pending.clear();
|
|
753
|
+
this.#replay.clear();
|
|
754
|
+
}
|
|
755
|
+
/** Test introspection: number of pending writes. */
|
|
756
|
+
get pendingCount() {
|
|
757
|
+
return this.#pending.size;
|
|
758
|
+
}
|
|
759
|
+
/** Test introspection: number of buffered offscreen replays. */
|
|
760
|
+
get replayCount() {
|
|
761
|
+
return this.#replay.size;
|
|
762
|
+
}
|
|
763
|
+
#armDebounce() {
|
|
764
|
+
if (this.#debounceTimer !== null) clearTimeout(this.#debounceTimer);
|
|
765
|
+
this.#debounceTimer = setTimeout(() => {
|
|
766
|
+
this.#debounceTimer = null;
|
|
767
|
+
this.#requestFrame();
|
|
768
|
+
}, this.#debounceMs);
|
|
769
|
+
}
|
|
770
|
+
#requestFrame() {
|
|
771
|
+
if (this.#frameHandle !== null) this.#cancelFrame(this.#frameHandle);
|
|
772
|
+
this.#frameHandle = this.#scheduleFrame(() => {
|
|
773
|
+
this.#frameHandle = null;
|
|
774
|
+
this.#flush();
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
#flush() {
|
|
778
|
+
const t0 = performance.now();
|
|
779
|
+
const gateActive = !this.#gateDisabled && this.#getCacheSize() > this.#gateThreshold;
|
|
780
|
+
const pending = this.#pending;
|
|
781
|
+
this.#pending = /* @__PURE__ */ new Map();
|
|
782
|
+
let applied = 0;
|
|
783
|
+
let deferred = 0;
|
|
784
|
+
for (const entry of pending.values()) {
|
|
785
|
+
if (gateActive && !this.#isVisible(entry.target.element)) {
|
|
786
|
+
this.#replay.set(entry.target.element, entry);
|
|
787
|
+
deferred += 1;
|
|
788
|
+
continue;
|
|
789
|
+
}
|
|
790
|
+
this.#apply(entry);
|
|
791
|
+
applied += 1;
|
|
792
|
+
}
|
|
793
|
+
const stats = {
|
|
794
|
+
applied,
|
|
795
|
+
deferred,
|
|
796
|
+
durationMs: performance.now() - t0
|
|
797
|
+
};
|
|
798
|
+
this.#onFlush?.(stats);
|
|
799
|
+
return stats;
|
|
800
|
+
}
|
|
801
|
+
};
|
|
802
|
+
|
|
803
|
+
// src/detection/locale.ts
|
|
804
|
+
var FALLBACK_LOCALE = "en";
|
|
805
|
+
function detectInitialLocale() {
|
|
806
|
+
const fromHtml = readHtmlLang();
|
|
807
|
+
if (fromHtml !== void 0) return fromHtml;
|
|
808
|
+
const fromNavigator = readNavigatorLanguage();
|
|
809
|
+
if (fromNavigator !== void 0) return fromNavigator;
|
|
810
|
+
return FALLBACK_LOCALE;
|
|
811
|
+
}
|
|
812
|
+
function readHtmlLang() {
|
|
813
|
+
if (typeof document === "undefined") return void 0;
|
|
814
|
+
const lang = document.documentElement.getAttribute("lang");
|
|
815
|
+
return lang === null || lang.length === 0 ? void 0 : lang;
|
|
816
|
+
}
|
|
817
|
+
function readNavigatorLanguage() {
|
|
818
|
+
if (typeof navigator === "undefined") return void 0;
|
|
819
|
+
const lang = navigator.language;
|
|
820
|
+
return typeof lang === "string" && lang.length > 0 ? lang : void 0;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/core/a11y.ts
|
|
824
|
+
var ELEMENT_ID = "payload-live-preview-a11y";
|
|
825
|
+
var CLEAR_DELAY_MS = 1500;
|
|
826
|
+
var STYLE = [
|
|
827
|
+
"position:absolute",
|
|
828
|
+
"width:1px",
|
|
829
|
+
"height:1px",
|
|
830
|
+
"padding:0",
|
|
831
|
+
"margin:-1px",
|
|
832
|
+
"overflow:hidden",
|
|
833
|
+
"clip:rect(0 0 0 0)",
|
|
834
|
+
"clip-path:inset(50%)",
|
|
835
|
+
"white-space:nowrap",
|
|
836
|
+
"border:0"
|
|
837
|
+
].join(";");
|
|
838
|
+
var FALLBACK_STRINGS = {
|
|
839
|
+
connected: "Live preview connected",
|
|
840
|
+
disconnected: "Live preview disconnected",
|
|
841
|
+
updated: (n) => `${String(n)} ${n === 1 ? "change" : "changes"} applied`
|
|
842
|
+
};
|
|
843
|
+
var STRINGS = {
|
|
844
|
+
en: FALLBACK_STRINGS,
|
|
845
|
+
de: {
|
|
846
|
+
connected: "Vorschau verbunden",
|
|
847
|
+
disconnected: "Vorschau getrennt",
|
|
848
|
+
updated: (n) => `${String(n)} ${n === 1 ? "\xC4nderung" : "\xC4nderungen"} angewendet`
|
|
849
|
+
}
|
|
850
|
+
};
|
|
851
|
+
function resolveStrings(locale) {
|
|
852
|
+
const lang = locale.split(/[-_]/)[0]?.toLowerCase() ?? "en";
|
|
853
|
+
return STRINGS[lang] ?? FALLBACK_STRINGS;
|
|
854
|
+
}
|
|
855
|
+
var A11yAnnouncer = class {
|
|
856
|
+
#element = null;
|
|
857
|
+
#strings;
|
|
858
|
+
#clearTimer = null;
|
|
859
|
+
constructor(locale = detectInitialLocale()) {
|
|
860
|
+
this.#strings = resolveStrings(locale);
|
|
861
|
+
}
|
|
862
|
+
/**
|
|
863
|
+
* Create (or adopt) the shared live-region element. Returns `null`
|
|
864
|
+
* when no DOM is available yet — announcements are then dropped,
|
|
865
|
+
* which is fine: they are progress niceties, not state.
|
|
866
|
+
*/
|
|
867
|
+
#mount() {
|
|
868
|
+
if (this.#element !== null && this.#element.isConnected) return this.#element;
|
|
869
|
+
if (typeof document === "undefined" || document.body === null) {
|
|
870
|
+
return null;
|
|
871
|
+
}
|
|
872
|
+
let element = document.getElementById(ELEMENT_ID);
|
|
873
|
+
if (!element) {
|
|
874
|
+
element = document.createElement("div");
|
|
875
|
+
element.id = ELEMENT_ID;
|
|
876
|
+
element.setAttribute("role", "status");
|
|
877
|
+
element.setAttribute("aria-live", "polite");
|
|
878
|
+
element.setAttribute("aria-atomic", "true");
|
|
879
|
+
element.setAttribute("style", STYLE);
|
|
880
|
+
document.body.appendChild(element);
|
|
881
|
+
}
|
|
882
|
+
this.#element = element;
|
|
883
|
+
return element;
|
|
884
|
+
}
|
|
885
|
+
/** Announce that the preview successfully connected. */
|
|
886
|
+
announceConnected() {
|
|
887
|
+
this.#say(this.#strings.connected);
|
|
888
|
+
}
|
|
889
|
+
/** Announce that the preview lost its connection. */
|
|
890
|
+
announceDisconnected() {
|
|
891
|
+
this.#say(this.#strings.disconnected);
|
|
892
|
+
}
|
|
893
|
+
/** Announce that `count` updates were applied. */
|
|
894
|
+
announceUpdate(count) {
|
|
895
|
+
if (count <= 0) return;
|
|
896
|
+
this.#say(this.#strings.updated(count));
|
|
897
|
+
}
|
|
898
|
+
/**
|
|
899
|
+
* Remove the live region from the document. Called by the runtime
|
|
900
|
+
* during `destroy()`. Idempotent.
|
|
901
|
+
*/
|
|
902
|
+
detach() {
|
|
903
|
+
if (this.#clearTimer !== null) {
|
|
904
|
+
clearTimeout(this.#clearTimer);
|
|
905
|
+
this.#clearTimer = null;
|
|
906
|
+
}
|
|
907
|
+
if (this.#element?.parentNode) {
|
|
908
|
+
this.#element.parentNode.removeChild(this.#element);
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
#say(message) {
|
|
912
|
+
const element = this.#mount();
|
|
913
|
+
if (!element) return;
|
|
914
|
+
element.textContent = message;
|
|
915
|
+
if (this.#clearTimer !== null) clearTimeout(this.#clearTimer);
|
|
916
|
+
this.#clearTimer = setTimeout(() => {
|
|
917
|
+
this.#clearTimer = null;
|
|
918
|
+
if (this.#element) this.#element.textContent = "";
|
|
919
|
+
}, CLEAR_DELAY_MS);
|
|
920
|
+
}
|
|
921
|
+
};
|
|
922
|
+
|
|
923
|
+
// src/schema/walker.ts
|
|
924
|
+
var STRUCTURAL_TYPES = /* @__PURE__ */ new Set([
|
|
925
|
+
"tabs",
|
|
926
|
+
"row",
|
|
927
|
+
"collapsible"
|
|
928
|
+
]);
|
|
929
|
+
function buildSchemaIndex(schemas, basePath = "") {
|
|
930
|
+
const out = /* @__PURE__ */ new Map();
|
|
931
|
+
walk(schemas, basePath, out);
|
|
932
|
+
return out;
|
|
933
|
+
}
|
|
934
|
+
function walk(schemas, basePath, out) {
|
|
935
|
+
for (const schema of schemas) {
|
|
936
|
+
if (STRUCTURAL_TYPES.has(schema.type)) {
|
|
937
|
+
if (schema.fields) walk(schema.fields, basePath, out);
|
|
938
|
+
continue;
|
|
939
|
+
}
|
|
940
|
+
if (schema.type === "group") {
|
|
941
|
+
const path2 = basePath === "" ? schema.name : `${basePath}.${schema.name}`;
|
|
942
|
+
out.set(path2, schema);
|
|
943
|
+
if (schema.fields) walk(schema.fields, path2, out);
|
|
944
|
+
continue;
|
|
945
|
+
}
|
|
946
|
+
const path = basePath === "" ? schema.name : `${basePath}.${schema.name}`;
|
|
947
|
+
out.set(path, schema);
|
|
948
|
+
if (schema.type === "array" && schema.fields) {
|
|
949
|
+
walk(schema.fields, `${path}.*`, out);
|
|
950
|
+
} else if (schema.type === "blocks" && schema.blocks) {
|
|
951
|
+
for (const block of schema.blocks) {
|
|
952
|
+
walk(block.fields, `${path}.*.${block.slug}`, out);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
}
|
|
956
|
+
}
|
|
957
|
+
function lookupSchema(index, fieldPath) {
|
|
958
|
+
if (index.has(fieldPath)) return index.get(fieldPath);
|
|
959
|
+
const wildcardPath = fieldPath.replace(/\.\d+(?=\.|$)/g, ".*");
|
|
960
|
+
if (index.has(wildcardPath)) return index.get(wildcardPath);
|
|
961
|
+
return void 0;
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/schema/diff.ts
|
|
965
|
+
function getId(item) {
|
|
966
|
+
if (typeof item !== "object" || item === null) return void 0;
|
|
967
|
+
const id = item["id"];
|
|
968
|
+
if (typeof id === "string" || typeof id === "number") return id;
|
|
969
|
+
return void 0;
|
|
970
|
+
}
|
|
971
|
+
function getBlockType(item) {
|
|
972
|
+
if (typeof item !== "object" || item === null) return void 0;
|
|
973
|
+
const blockType = item["blockType"];
|
|
974
|
+
return typeof blockType === "string" ? blockType : void 0;
|
|
975
|
+
}
|
|
976
|
+
function diffArray(prev, next) {
|
|
977
|
+
const haveIds = next.every((item) => getId(item) !== void 0);
|
|
978
|
+
if (haveIds) return diffById(prev, next);
|
|
979
|
+
return diffByPosition(prev, next);
|
|
980
|
+
}
|
|
981
|
+
function diffById(prev, next) {
|
|
982
|
+
const patches = [];
|
|
983
|
+
const prevIndexById = /* @__PURE__ */ new Map();
|
|
984
|
+
for (let i = 0; i < prev.length; i += 1) {
|
|
985
|
+
const id = getId(prev[i]);
|
|
986
|
+
if (id !== void 0) prevIndexById.set(id, i);
|
|
987
|
+
}
|
|
988
|
+
const consumedFromPrev = /* @__PURE__ */ new Set();
|
|
989
|
+
for (let to = 0; to < next.length; to += 1) {
|
|
990
|
+
const item = next[to];
|
|
991
|
+
const id = getId(item);
|
|
992
|
+
if (id === void 0) {
|
|
993
|
+
patches.push({ kind: "insert", index: to, value: item });
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const from = prevIndexById.get(id);
|
|
997
|
+
if (from === void 0) {
|
|
998
|
+
patches.push({ kind: "insert", index: to, value: item });
|
|
999
|
+
continue;
|
|
1000
|
+
}
|
|
1001
|
+
consumedFromPrev.add(from);
|
|
1002
|
+
const prevItem = prev[from];
|
|
1003
|
+
if (getBlockType(prevItem) !== getBlockType(item)) {
|
|
1004
|
+
patches.push({ kind: "replace", index: to, oldValue: prevItem, value: item });
|
|
1005
|
+
continue;
|
|
1006
|
+
}
|
|
1007
|
+
if (from !== to) {
|
|
1008
|
+
patches.push({ kind: "move", from, to, value: item });
|
|
1009
|
+
}
|
|
1010
|
+
if (!shallowEqual(prevItem, item)) {
|
|
1011
|
+
patches.push({ kind: "update", index: to, value: item });
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
for (let i = 0; i < prev.length; i += 1) {
|
|
1015
|
+
if (consumedFromPrev.has(i)) continue;
|
|
1016
|
+
patches.push({ kind: "remove", index: i, value: prev[i] });
|
|
1017
|
+
}
|
|
1018
|
+
return patches;
|
|
1019
|
+
}
|
|
1020
|
+
function diffByPosition(prev, next) {
|
|
1021
|
+
const patches = [];
|
|
1022
|
+
const min = Math.min(prev.length, next.length);
|
|
1023
|
+
for (let i = 0; i < min; i += 1) {
|
|
1024
|
+
if (!shallowEqual(prev[i], next[i])) {
|
|
1025
|
+
patches.push({ kind: "update", index: i, value: next[i] });
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
if (next.length > prev.length) {
|
|
1029
|
+
for (let i = prev.length; i < next.length; i += 1) {
|
|
1030
|
+
patches.push({ kind: "insert", index: i, value: next[i] });
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (prev.length > next.length) {
|
|
1034
|
+
for (let i = next.length; i < prev.length; i += 1) {
|
|
1035
|
+
patches.push({ kind: "remove", index: i, value: prev[i] });
|
|
1036
|
+
}
|
|
1037
|
+
}
|
|
1038
|
+
return patches;
|
|
1039
|
+
}
|
|
1040
|
+
function shallowEqual(a, b) {
|
|
1041
|
+
if (Object.is(a, b)) return true;
|
|
1042
|
+
if (typeof a !== "object" || typeof b !== "object" || a === null || b === null) return false;
|
|
1043
|
+
const aRec = a;
|
|
1044
|
+
const bRec = b;
|
|
1045
|
+
const aKeys = Object.keys(aRec);
|
|
1046
|
+
const bKeys = Object.keys(bRec);
|
|
1047
|
+
if (aKeys.length !== bKeys.length) return false;
|
|
1048
|
+
for (const key of aKeys) {
|
|
1049
|
+
if (!Object.is(aRec[key], bRec[key])) return false;
|
|
1050
|
+
}
|
|
1051
|
+
return true;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
1054
|
+
// src/schema/field-type-map.ts
|
|
1055
|
+
var MAP = {
|
|
1056
|
+
text: "text",
|
|
1057
|
+
textarea: "textarea",
|
|
1058
|
+
richText: "richText",
|
|
1059
|
+
email: "email",
|
|
1060
|
+
number: "number",
|
|
1061
|
+
checkbox: "checkbox",
|
|
1062
|
+
date: "date",
|
|
1063
|
+
select: "select",
|
|
1064
|
+
radio: "radio",
|
|
1065
|
+
array: "array",
|
|
1066
|
+
blocks: "blocks",
|
|
1067
|
+
group: "text",
|
|
1068
|
+
// group itself has no display; bound element shows nothing useful
|
|
1069
|
+
tabs: "text",
|
|
1070
|
+
// structural — walker should have stripped this
|
|
1071
|
+
row: "text",
|
|
1072
|
+
collapsible: "text",
|
|
1073
|
+
relationship: "relationship",
|
|
1074
|
+
upload: "upload",
|
|
1075
|
+
point: "text",
|
|
1076
|
+
json: "text",
|
|
1077
|
+
code: "text",
|
|
1078
|
+
ui: "text"
|
|
1079
|
+
};
|
|
1080
|
+
function payloadTypeToRenderer(type) {
|
|
1081
|
+
if (Object.prototype.hasOwnProperty.call(MAP, type)) {
|
|
1082
|
+
return MAP[type];
|
|
1083
|
+
}
|
|
1084
|
+
return void 0;
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// src/core/lifecycle.ts
|
|
1088
|
+
var READY_RETRY_DELAYS_MS = [0, 500, 1e3, 2e3];
|
|
1089
|
+
var LivePreviewRuntime = class {
|
|
1090
|
+
#emitter;
|
|
1091
|
+
#cache;
|
|
1092
|
+
#observers;
|
|
1093
|
+
#scheduler;
|
|
1094
|
+
#bus;
|
|
1095
|
+
#state;
|
|
1096
|
+
#heartbeat;
|
|
1097
|
+
#renderers;
|
|
1098
|
+
#root;
|
|
1099
|
+
#readyTargets;
|
|
1100
|
+
#sendReady;
|
|
1101
|
+
#onHeartbeatTimeoutHook;
|
|
1102
|
+
#log;
|
|
1103
|
+
#warn;
|
|
1104
|
+
#readyTimers = [];
|
|
1105
|
+
#a11y;
|
|
1106
|
+
#merger;
|
|
1107
|
+
#currentLocale;
|
|
1108
|
+
#currentSchema;
|
|
1109
|
+
#schemaIndex;
|
|
1110
|
+
#protocolNegotiation = negotiateProtocol(void 0);
|
|
1111
|
+
#started = false;
|
|
1112
|
+
#deferredStart = null;
|
|
1113
|
+
#updateCount = 0;
|
|
1114
|
+
/**
|
|
1115
|
+
* Field names we've already warned about as "orphan updates" — updates
|
|
1116
|
+
* arrived for them but no `[data-payload-field=…]` anchor exists.
|
|
1117
|
+
* Set-membership prevents the diagnostic from spamming the console
|
|
1118
|
+
* during continuous editing.
|
|
1119
|
+
*/
|
|
1120
|
+
#warnedOrphanFields = /* @__PURE__ */ new Set();
|
|
1121
|
+
constructor(options) {
|
|
1122
|
+
this.#emitter = options.emitter;
|
|
1123
|
+
this.#renderers = options.renderers;
|
|
1124
|
+
this.#root = options.root ?? (typeof document !== "undefined" ? document : null);
|
|
1125
|
+
this.#readyTargets = options.readyTargets;
|
|
1126
|
+
this.#sendReady = options.sendReady ?? defaultSendReady;
|
|
1127
|
+
this.#onHeartbeatTimeoutHook = options.onHeartbeatTimeout;
|
|
1128
|
+
this.#log = options.log ?? noopLogger;
|
|
1129
|
+
this.#warn = options.warn ?? defaultWarn;
|
|
1130
|
+
this.#a11y = createA11y(options);
|
|
1131
|
+
this.#merger = options.dataMerge !== void 0 ? new DataMerger({
|
|
1132
|
+
serverURL: options.dataMerge.serverURL,
|
|
1133
|
+
...options.dataMerge.apiRoute !== void 0 ? { apiRoute: options.dataMerge.apiRoute } : {},
|
|
1134
|
+
...options.dataMerge.depth !== void 0 ? { depth: options.dataMerge.depth } : {},
|
|
1135
|
+
...options.dataMerge.fetchFn !== void 0 ? { fetchFn: options.dataMerge.fetchFn } : {},
|
|
1136
|
+
log: (...args) => {
|
|
1137
|
+
this.#log(...args);
|
|
1138
|
+
}
|
|
1139
|
+
}) : null;
|
|
1140
|
+
this.#cache = new ElementCache();
|
|
1141
|
+
this.#observers = new ObserverManager(
|
|
1142
|
+
{
|
|
1143
|
+
onStructuralChange: () => {
|
|
1144
|
+
this.#rebuildCache();
|
|
1145
|
+
},
|
|
1146
|
+
onVisibilityChange: (element, visible) => {
|
|
1147
|
+
if (visible) this.#scheduler.notifyVisible(element);
|
|
1148
|
+
}
|
|
1149
|
+
},
|
|
1150
|
+
{
|
|
1151
|
+
...options.intersectionRootMargin !== void 0 ? { intersectionRootMargin: options.intersectionRootMargin } : {}
|
|
1152
|
+
}
|
|
1153
|
+
);
|
|
1154
|
+
this.#scheduler = new UpdateScheduler(
|
|
1155
|
+
(update) => {
|
|
1156
|
+
this.#applyUpdate(update);
|
|
1157
|
+
},
|
|
1158
|
+
{
|
|
1159
|
+
...options.debounceMs !== void 0 ? { debounceMs: options.debounceMs } : {},
|
|
1160
|
+
...options.disableVisibilityGate !== void 0 ? { disableVisibilityGate: options.disableVisibilityGate } : {},
|
|
1161
|
+
...options.visibilityGateThreshold !== void 0 ? { visibilityGateThreshold: options.visibilityGateThreshold } : {},
|
|
1162
|
+
isVisible: (element) => this.#observers.isVisible(element),
|
|
1163
|
+
getCacheSize: () => this.#cache.elementCount,
|
|
1164
|
+
onFlush: (stats) => {
|
|
1165
|
+
this.#onFlush(stats);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
);
|
|
1169
|
+
this.#bus = new MessageBus(options.originMatcher, {
|
|
1170
|
+
onUpdate: (msg, origin) => {
|
|
1171
|
+
this.#handleUpdate(msg, origin);
|
|
1172
|
+
},
|
|
1173
|
+
onDocumentEvent: (msg, origin) => {
|
|
1174
|
+
this.#handleDocumentEvent(msg, origin);
|
|
1175
|
+
},
|
|
1176
|
+
onInvalid: (reason, origin) => {
|
|
1177
|
+
if (reason === "token") {
|
|
1178
|
+
const error = new Error(`Preview token rejected (origin: ${origin})`);
|
|
1179
|
+
void this.#emitter.emit("error", { error, context: "token" });
|
|
1180
|
+
}
|
|
1181
|
+
this.#log("message rejected:", reason, origin);
|
|
1182
|
+
},
|
|
1183
|
+
...options.validateToken !== void 0 ? { validateToken: options.validateToken } : {}
|
|
1184
|
+
});
|
|
1185
|
+
this.#state = new ConnectionState((next, prev) => {
|
|
1186
|
+
this.#log("connection", prev, "\u2192", next);
|
|
1187
|
+
});
|
|
1188
|
+
this.#heartbeat = new HeartbeatTimer({
|
|
1189
|
+
...options.heartbeatMs !== void 0 ? { timeoutMs: options.heartbeatMs } : {},
|
|
1190
|
+
onTimeout: () => {
|
|
1191
|
+
this.#onHeartbeatTimeout();
|
|
1192
|
+
}
|
|
1193
|
+
});
|
|
1194
|
+
}
|
|
1195
|
+
/**
|
|
1196
|
+
* Start the runtime: build cache, attach observers, listen for
|
|
1197
|
+
* messages, broadcast `ready`. Returns `true` if it actually started
|
|
1198
|
+
* (it will refuse to start a second time).
|
|
1199
|
+
*
|
|
1200
|
+
* When the script executes while the document is still parsing
|
|
1201
|
+
* (e.g. injected via `<script>` in `<head>` — Astro's `head-inline`
|
|
1202
|
+
* stage), `document.body` does not exist yet and attaching the
|
|
1203
|
+
* MutationObserver would throw. In that case the actual startup is
|
|
1204
|
+
* deferred until `DOMContentLoaded`; `start()` still returns `true`
|
|
1205
|
+
* and `destroy()` cancels the pending startup.
|
|
1206
|
+
*/
|
|
1207
|
+
start() {
|
|
1208
|
+
if (this.#started) return false;
|
|
1209
|
+
this.#started = true;
|
|
1210
|
+
const root = this.#root;
|
|
1211
|
+
if (root instanceof Document && root.readyState === "loading") {
|
|
1212
|
+
const onReady = () => {
|
|
1213
|
+
this.#deferredStart = null;
|
|
1214
|
+
this.#startNow();
|
|
1215
|
+
};
|
|
1216
|
+
this.#deferredStart = onReady;
|
|
1217
|
+
root.addEventListener("DOMContentLoaded", onReady, { once: true });
|
|
1218
|
+
return true;
|
|
1219
|
+
}
|
|
1220
|
+
this.#startNow();
|
|
1221
|
+
return true;
|
|
1222
|
+
}
|
|
1223
|
+
#startNow() {
|
|
1224
|
+
this.#buildCacheAndObserve();
|
|
1225
|
+
this.#observers.start(this.#root instanceof Document ? this.#root.body : this.#root);
|
|
1226
|
+
this.#bus.attach();
|
|
1227
|
+
this.#emitInit();
|
|
1228
|
+
for (const delay of READY_RETRY_DELAYS_MS) {
|
|
1229
|
+
if (delay === 0) {
|
|
1230
|
+
this.#sendReady(this.#readyTargets);
|
|
1231
|
+
} else {
|
|
1232
|
+
const handle = setTimeout(() => {
|
|
1233
|
+
this.#sendReady(this.#readyTargets);
|
|
1234
|
+
}, delay);
|
|
1235
|
+
this.#readyTimers.push(handle);
|
|
1236
|
+
}
|
|
1237
|
+
}
|
|
1238
|
+
}
|
|
1239
|
+
/** Tear down all observers, timers, and listeners. Idempotent. */
|
|
1240
|
+
destroy() {
|
|
1241
|
+
if (!this.#started) return;
|
|
1242
|
+
this.#started = false;
|
|
1243
|
+
if (this.#deferredStart !== null) {
|
|
1244
|
+
const root = this.#root;
|
|
1245
|
+
if (root instanceof Document) {
|
|
1246
|
+
root.removeEventListener("DOMContentLoaded", this.#deferredStart);
|
|
1247
|
+
}
|
|
1248
|
+
this.#deferredStart = null;
|
|
1249
|
+
void this.#emitter.emit("destroy", { timestamp: Date.now() });
|
|
1250
|
+
return;
|
|
1251
|
+
}
|
|
1252
|
+
for (const handle of this.#readyTimers) clearTimeout(handle);
|
|
1253
|
+
this.#readyTimers.length = 0;
|
|
1254
|
+
this.#heartbeat.stop();
|
|
1255
|
+
this.#bus.detach();
|
|
1256
|
+
this.#observers.stop();
|
|
1257
|
+
this.#scheduler.destroy();
|
|
1258
|
+
this.#merger?.destroy();
|
|
1259
|
+
this.#cache.clear();
|
|
1260
|
+
const wasConnected = this.#state.status === "connected";
|
|
1261
|
+
this.#state.markDisconnected();
|
|
1262
|
+
if (wasConnected) {
|
|
1263
|
+
this.#a11y?.announceDisconnected();
|
|
1264
|
+
void this.#emitter.emit("disconnect", { reason: "destroy", timestamp: Date.now() });
|
|
1265
|
+
}
|
|
1266
|
+
this.#a11y?.detach();
|
|
1267
|
+
void this.#emitter.emit("destroy", { timestamp: Date.now() });
|
|
1268
|
+
}
|
|
1269
|
+
/** Re-scan the DOM and re-register every binding. */
|
|
1270
|
+
refreshCache() {
|
|
1271
|
+
this.#rebuildCache();
|
|
1272
|
+
}
|
|
1273
|
+
/** Current connection status, exposed for the high-level client. */
|
|
1274
|
+
get status() {
|
|
1275
|
+
return this.#state.status;
|
|
1276
|
+
}
|
|
1277
|
+
/** Read-only view of the element cache. */
|
|
1278
|
+
get cache() {
|
|
1279
|
+
return this.#cache;
|
|
1280
|
+
}
|
|
1281
|
+
/** Read-only view of how many updates have been received. */
|
|
1282
|
+
get updateCount() {
|
|
1283
|
+
return this.#updateCount;
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* Negotiated protocol view — `min(library, remote)`. Useful for
|
|
1287
|
+
* consumers that want to branch on protocol capabilities without
|
|
1288
|
+
* hard-coding version numbers. Updates lazily as the remote party's
|
|
1289
|
+
* version arrives on incoming messages.
|
|
1290
|
+
*/
|
|
1291
|
+
get protocol() {
|
|
1292
|
+
return this.#protocolNegotiation;
|
|
1293
|
+
}
|
|
1294
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
1295
|
+
// Internals
|
|
1296
|
+
// ──────────────────────────────────────────────────────────────────────────
|
|
1297
|
+
#buildCacheAndObserve() {
|
|
1298
|
+
const stats = this.#cache.buildFromRoot(this.#root);
|
|
1299
|
+
this.#log("cache built", stats);
|
|
1300
|
+
void this.#emitter.emit("cacheRefresh", stats);
|
|
1301
|
+
for (const entry of this.#cache.values()) {
|
|
1302
|
+
this.#observers.observeElement(entry.element);
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
#rebuildCache() {
|
|
1306
|
+
for (const entry of this.#cache.values()) {
|
|
1307
|
+
this.#observers.unobserveElement(entry.element);
|
|
1308
|
+
this.#scheduler.forget(entry.element);
|
|
1309
|
+
}
|
|
1310
|
+
this.#buildCacheAndObserve();
|
|
1311
|
+
}
|
|
1312
|
+
#emitInit() {
|
|
1313
|
+
void this.#emitter.emit("init", { timestamp: Date.now() });
|
|
1314
|
+
}
|
|
1315
|
+
#applyNegotiation(remoteVersion) {
|
|
1316
|
+
const next = negotiateProtocol(remoteVersion);
|
|
1317
|
+
if (next.negotiated === this.#protocolNegotiation.negotiated) return;
|
|
1318
|
+
this.#protocolNegotiation = next;
|
|
1319
|
+
this.#log(
|
|
1320
|
+
"protocol negotiated",
|
|
1321
|
+
`ours=${LIBRARY_PROTOCOL_VERSION}`,
|
|
1322
|
+
`theirs=${remoteVersion}`,
|
|
1323
|
+
`negotiated=${next.negotiated}`
|
|
1324
|
+
);
|
|
1325
|
+
}
|
|
1326
|
+
#handleUpdate(message, origin) {
|
|
1327
|
+
this.#heartbeat.kick();
|
|
1328
|
+
if (message.protocolVersion !== void 0) {
|
|
1329
|
+
this.#applyNegotiation(message.protocolVersion);
|
|
1330
|
+
}
|
|
1331
|
+
if (message.data === void 0) {
|
|
1332
|
+
return;
|
|
1333
|
+
}
|
|
1334
|
+
if (this.#state.markConnected()) {
|
|
1335
|
+
this.#a11y?.announceConnected();
|
|
1336
|
+
void this.#emitter.emit("connect", { origin, timestamp: Date.now() });
|
|
1337
|
+
}
|
|
1338
|
+
this.#updateCount += 1;
|
|
1339
|
+
if (message.locale !== void 0) this.#currentLocale = message.locale;
|
|
1340
|
+
if (message.fieldSchemaJSON !== void 0) {
|
|
1341
|
+
this.#currentSchema = message.fieldSchemaJSON;
|
|
1342
|
+
this.#schemaIndex = buildSchemaIndex(message.fieldSchemaJSON);
|
|
1343
|
+
}
|
|
1344
|
+
void this.#resolveIncomingFields(message).then((fields) => {
|
|
1345
|
+
if (fields === null) return;
|
|
1346
|
+
const data = {
|
|
1347
|
+
fields,
|
|
1348
|
+
...this.#currentSchema !== void 0 ? { schema: this.#currentSchema } : {},
|
|
1349
|
+
...message.globalSlug !== void 0 ? { globalSlug: message.globalSlug } : {},
|
|
1350
|
+
...message.collectionSlug !== void 0 ? { collectionSlug: message.collectionSlug } : {},
|
|
1351
|
+
...this.#currentLocale !== void 0 ? { locale: this.#currentLocale } : {}
|
|
1352
|
+
};
|
|
1353
|
+
let cancelled = false;
|
|
1354
|
+
void this.#emitter.emit("beforeUpdate", {
|
|
1355
|
+
data,
|
|
1356
|
+
cancel: () => {
|
|
1357
|
+
cancelled = true;
|
|
1358
|
+
}
|
|
1359
|
+
}).then(() => {
|
|
1360
|
+
if (cancelled) return;
|
|
1361
|
+
this.#scheduleAllFields(data);
|
|
1362
|
+
});
|
|
1363
|
+
});
|
|
1364
|
+
}
|
|
1365
|
+
/**
|
|
1366
|
+
* Resolve the field values to render for an incoming update. With a
|
|
1367
|
+
* configured `DataMerger` the raw form values are exchanged for the
|
|
1368
|
+
* server-populated document; without one (or on failure) the raw
|
|
1369
|
+
* values pass through unchanged. Returns `null` when a newer update
|
|
1370
|
+
* superseded this one mid-flight.
|
|
1371
|
+
*/
|
|
1372
|
+
async #resolveIncomingFields(message) {
|
|
1373
|
+
const raw = message.data ?? {};
|
|
1374
|
+
if (this.#merger === null) return raw;
|
|
1375
|
+
const result = await this.#merger.merge({
|
|
1376
|
+
collectionSlug: message.collectionSlug,
|
|
1377
|
+
globalSlug: message.globalSlug,
|
|
1378
|
+
data: raw,
|
|
1379
|
+
locale: this.#currentLocale
|
|
1380
|
+
});
|
|
1381
|
+
if (result.status === "merged") return result.doc;
|
|
1382
|
+
if (result.status === "superseded") return null;
|
|
1383
|
+
return raw;
|
|
1384
|
+
}
|
|
1385
|
+
#handleDocumentEvent(_message, _origin) {
|
|
1386
|
+
void this.#emitter.emit("documentSave", { timestamp: Date.now() });
|
|
1387
|
+
}
|
|
1388
|
+
#scheduleAllFields(data) {
|
|
1389
|
+
for (const [fieldName, bindings] of this.#cache.entries()) {
|
|
1390
|
+
const value = resolveFieldValue(data.fields, fieldName, this.#currentLocale);
|
|
1391
|
+
if (value === void 0) continue;
|
|
1392
|
+
for (const target of bindings) {
|
|
1393
|
+
const update = {
|
|
1394
|
+
target,
|
|
1395
|
+
value,
|
|
1396
|
+
allFields: data.fields
|
|
1397
|
+
};
|
|
1398
|
+
this.#scheduler.schedule(update);
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
this.#diagnoseOrphanFields(data.fields);
|
|
1402
|
+
}
|
|
1403
|
+
/**
|
|
1404
|
+
* Walk the incoming `data` payload and warn (via `this.#log`) when an
|
|
1405
|
+
* editable-looking field arrives for which **no `[data-payload-field]`
|
|
1406
|
+
* anchor exists** in the page. This is the most common live-preview
|
|
1407
|
+
* footgun: an SSR template renders the binding only when the field is
|
|
1408
|
+
* non-empty, so editing a previously-empty field has nowhere to land.
|
|
1409
|
+
*
|
|
1410
|
+
* The warning is gated by:
|
|
1411
|
+
* - `this.#log` being a real logger (no-op when debug disabled)
|
|
1412
|
+
* - per-field deduplication via `#warnedOrphanFields`
|
|
1413
|
+
* - scalar value heuristic (objects/arrays don't get warned)
|
|
1414
|
+
* - a small ignore-list of system fields Payload always ships
|
|
1415
|
+
*
|
|
1416
|
+
* The method intentionally lives on the runtime — not on the cache —
|
|
1417
|
+
* because it depends on the locale and on schema knowledge that the
|
|
1418
|
+
* cache does not own.
|
|
1419
|
+
*/
|
|
1420
|
+
#diagnoseOrphanFields(fields) {
|
|
1421
|
+
if (this.#cache.elementCount === 0) return;
|
|
1422
|
+
const boundNames = /* @__PURE__ */ new Set();
|
|
1423
|
+
for (const [name] of this.#cache.entries()) boundNames.add(name);
|
|
1424
|
+
for (const [rawName, value] of Object.entries(fields)) {
|
|
1425
|
+
if (this.#warnedOrphanFields.has(rawName)) continue;
|
|
1426
|
+
if (SYSTEM_FIELD_NAMES.has(rawName)) continue;
|
|
1427
|
+
if (!isLiveBindableScalar(value)) continue;
|
|
1428
|
+
const baseName = stripLocaleSuffix(rawName, this.#currentLocale);
|
|
1429
|
+
if (boundNames.has(rawName) || boundNames.has(baseName)) continue;
|
|
1430
|
+
this.#warnedOrphanFields.add(rawName);
|
|
1431
|
+
this.#warn(
|
|
1432
|
+
`[live-preview] update arrived for field "${rawName}" but no <\u2026 data-payload-field="${baseName}"> element exists on this page. Render the binding anchor unconditionally in your template so live edits to an initially-empty field have somewhere to land.`
|
|
1433
|
+
);
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
#applyUpdate(update) {
|
|
1437
|
+
const schemaEntry = this.#schemaIndex !== void 0 ? lookupSchema(this.#schemaIndex, update.target.fieldName) : void 0;
|
|
1438
|
+
let resolvedType = this.#resolveFieldType(update.target, schemaEntry?.type);
|
|
1439
|
+
if (resolvedType === "text" && update.target.explicitFieldType !== true && looksLikeLexicalRoot(update.value)) {
|
|
1440
|
+
resolvedType = "richText";
|
|
1441
|
+
}
|
|
1442
|
+
const renderer = this.#renderers[resolvedType];
|
|
1443
|
+
const previous = readElementSnapshot(update.target.element);
|
|
1444
|
+
const context = {
|
|
1445
|
+
allFields: update.allFields,
|
|
1446
|
+
locale: this.#currentLocale,
|
|
1447
|
+
schema: schemaEntry
|
|
1448
|
+
};
|
|
1449
|
+
try {
|
|
1450
|
+
if (update.target.targetAttribute !== void 0) {
|
|
1451
|
+
const outcome = applyAttributeBinding(
|
|
1452
|
+
update.target.element,
|
|
1453
|
+
update.target.targetAttribute,
|
|
1454
|
+
update.value
|
|
1455
|
+
);
|
|
1456
|
+
if (outcome === "blocked") {
|
|
1457
|
+
this.#warn(
|
|
1458
|
+
`[live-preview] refused to write field "${update.target.fieldName}" into attribute "${update.target.targetAttribute}" (unsafe attribute or value)`
|
|
1459
|
+
);
|
|
1460
|
+
}
|
|
1461
|
+
} else if (renderer) {
|
|
1462
|
+
renderer.render(update.target, update.value, context);
|
|
1463
|
+
} else {
|
|
1464
|
+
this.#log("no renderer for", resolvedType);
|
|
1465
|
+
}
|
|
1466
|
+
} catch (err) {
|
|
1467
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
1468
|
+
void this.#emitter.emit("error", { error, context: "renderer" });
|
|
1469
|
+
return;
|
|
1470
|
+
}
|
|
1471
|
+
void this.#emitter.emit("elementUpdate", {
|
|
1472
|
+
element: update.target.element,
|
|
1473
|
+
fieldName: update.target.fieldName,
|
|
1474
|
+
previousValue: previous,
|
|
1475
|
+
nextValue: update.value
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Resolve the renderer name for an update. Explicit consumer DOM
|
|
1480
|
+
* attributes (`data-payload-type`) always win; otherwise the schema
|
|
1481
|
+
* type maps to a renderer; otherwise the cache's tag-based heuristic
|
|
1482
|
+
* stands.
|
|
1483
|
+
*/
|
|
1484
|
+
#resolveFieldType(target, schemaType) {
|
|
1485
|
+
if (target.explicitFieldType) return target.fieldType;
|
|
1486
|
+
if (schemaType !== void 0) {
|
|
1487
|
+
const mapped = payloadTypeToRenderer(schemaType);
|
|
1488
|
+
if (mapped !== void 0) return mapped;
|
|
1489
|
+
}
|
|
1490
|
+
return target.fieldType;
|
|
1491
|
+
}
|
|
1492
|
+
#onFlush(stats) {
|
|
1493
|
+
if (stats.applied === 0 && stats.deferred === 0) return;
|
|
1494
|
+
if (stats.applied > 0) this.#a11y?.announceUpdate(stats.applied);
|
|
1495
|
+
void this.#emitter.emit("afterUpdate", {
|
|
1496
|
+
data: {
|
|
1497
|
+
fields: {},
|
|
1498
|
+
...this.#currentLocale !== void 0 ? { locale: this.#currentLocale } : {}
|
|
1499
|
+
},
|
|
1500
|
+
updatedCount: stats.applied,
|
|
1501
|
+
durationMs: stats.durationMs
|
|
1502
|
+
});
|
|
1503
|
+
}
|
|
1504
|
+
#onHeartbeatTimeout() {
|
|
1505
|
+
if (this.#state.markDisconnected()) {
|
|
1506
|
+
this.#a11y?.announceDisconnected();
|
|
1507
|
+
void this.#emitter.emit("disconnect", { reason: "timeout", timestamp: Date.now() });
|
|
1508
|
+
}
|
|
1509
|
+
try {
|
|
1510
|
+
this.#onHeartbeatTimeoutHook?.();
|
|
1511
|
+
} catch (err) {
|
|
1512
|
+
this.#log("onHeartbeatTimeout hook threw:", err);
|
|
1513
|
+
}
|
|
1514
|
+
this.#sendReady(this.#readyTargets);
|
|
1515
|
+
}
|
|
1516
|
+
};
|
|
1517
|
+
function createA11y(options) {
|
|
1518
|
+
if (options.enableA11y === false) return null;
|
|
1519
|
+
return options.a11yLocale !== void 0 ? new A11yAnnouncer(options.a11yLocale) : new A11yAnnouncer();
|
|
1520
|
+
}
|
|
1521
|
+
function noopLogger() {
|
|
1522
|
+
}
|
|
1523
|
+
function looksLikeLexicalRoot(value) {
|
|
1524
|
+
if (typeof value !== "object" || value === null) return false;
|
|
1525
|
+
if (!("root" in value)) return false;
|
|
1526
|
+
const root = value.root;
|
|
1527
|
+
if (typeof root !== "object" || root === null) return false;
|
|
1528
|
+
return Array.isArray(root.children);
|
|
1529
|
+
}
|
|
1530
|
+
function defaultWarn(...args) {
|
|
1531
|
+
if (typeof console !== "undefined" && typeof console.warn === "function") {
|
|
1532
|
+
console.warn(...args);
|
|
1533
|
+
}
|
|
1534
|
+
}
|
|
1535
|
+
function defaultSendReady(origins) {
|
|
1536
|
+
if (typeof window === "undefined") return;
|
|
1537
|
+
const targets = [];
|
|
1538
|
+
if (window.parent !== window) targets.push(window.parent);
|
|
1539
|
+
if (window.opener instanceof Window) targets.push(window.opener);
|
|
1540
|
+
MessageBus.sendReady(targets, origins);
|
|
1541
|
+
}
|
|
1542
|
+
var BLOCKED_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
|
|
1543
|
+
var SYSTEM_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
1544
|
+
"id",
|
|
1545
|
+
"_id",
|
|
1546
|
+
"createdAt",
|
|
1547
|
+
"updatedAt",
|
|
1548
|
+
"createdBy",
|
|
1549
|
+
"updatedBy",
|
|
1550
|
+
"_status",
|
|
1551
|
+
"globalType",
|
|
1552
|
+
"collection",
|
|
1553
|
+
"locale",
|
|
1554
|
+
"localized"
|
|
1555
|
+
]);
|
|
1556
|
+
function isLiveBindableScalar(value) {
|
|
1557
|
+
if (value === null || value === void 0) return false;
|
|
1558
|
+
return typeof value === "string" || typeof value === "number" || typeof value === "boolean" || typeof value === "bigint";
|
|
1559
|
+
}
|
|
1560
|
+
function stripLocaleSuffix(name, locale) {
|
|
1561
|
+
if (!locale) return name;
|
|
1562
|
+
const suffix = `_${locale}`;
|
|
1563
|
+
return name.endsWith(suffix) ? name.slice(0, -suffix.length) : name;
|
|
1564
|
+
}
|
|
1565
|
+
function resolveFieldValue(fields, path, locale) {
|
|
1566
|
+
const direct = readSafe(fields, path);
|
|
1567
|
+
if (direct !== void 0) return direct;
|
|
1568
|
+
if (path.includes(".")) {
|
|
1569
|
+
const segments = path.split(".");
|
|
1570
|
+
let current = fields;
|
|
1571
|
+
for (const segment of segments) {
|
|
1572
|
+
if (BLOCKED_KEYS.has(segment)) return void 0;
|
|
1573
|
+
if (current === null || typeof current !== "object") return void 0;
|
|
1574
|
+
current = current[segment];
|
|
1575
|
+
}
|
|
1576
|
+
if (current !== void 0) return current;
|
|
1577
|
+
}
|
|
1578
|
+
if (locale !== void 0) {
|
|
1579
|
+
const localised = readSafe(fields, `${path}_${locale}`);
|
|
1580
|
+
if (localised !== void 0) return localised;
|
|
1581
|
+
}
|
|
1582
|
+
return void 0;
|
|
1583
|
+
}
|
|
1584
|
+
function readSafe(obj, key) {
|
|
1585
|
+
if (BLOCKED_KEYS.has(key)) return void 0;
|
|
1586
|
+
if (!Object.prototype.hasOwnProperty.call(obj, key)) return void 0;
|
|
1587
|
+
return obj[key];
|
|
1588
|
+
}
|
|
1589
|
+
function readElementSnapshot(element) {
|
|
1590
|
+
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
|
|
1591
|
+
return element.value;
|
|
1592
|
+
}
|
|
1593
|
+
if (element.tagName === "IMG") {
|
|
1594
|
+
return element.src;
|
|
1595
|
+
}
|
|
1596
|
+
return element.textContent;
|
|
1597
|
+
}
|
|
1598
|
+
|
|
1599
|
+
// src/events/emitter.ts
|
|
1600
|
+
var EventEmitter = class {
|
|
1601
|
+
#regular = /* @__PURE__ */ new Map();
|
|
1602
|
+
#once = /* @__PURE__ */ new Map();
|
|
1603
|
+
/**
|
|
1604
|
+
* Subscribe `handler` to `event`. Returns an unsubscribe function;
|
|
1605
|
+
* call it to remove the handler. The handler is invoked for every
|
|
1606
|
+
* subsequent emit until unsubscribed.
|
|
1607
|
+
*/
|
|
1608
|
+
on(event, handler) {
|
|
1609
|
+
return this.#register(this.#regular, event, handler);
|
|
1610
|
+
}
|
|
1611
|
+
/**
|
|
1612
|
+
* Subscribe `handler` for a single emit. After the first dispatch,
|
|
1613
|
+
* the handler is removed automatically.
|
|
1614
|
+
*/
|
|
1615
|
+
once(event, handler) {
|
|
1616
|
+
return this.#register(this.#once, event, handler);
|
|
1617
|
+
}
|
|
1618
|
+
/**
|
|
1619
|
+
* Remove `handler` from `event`. Works for both `on`- and
|
|
1620
|
+
* `once`-registered handlers. No-op when the handler isn't registered.
|
|
1621
|
+
*/
|
|
1622
|
+
off(event, handler) {
|
|
1623
|
+
this.#regular.get(event)?.delete(handler);
|
|
1624
|
+
this.#once.get(event)?.delete(handler);
|
|
1625
|
+
}
|
|
1626
|
+
/**
|
|
1627
|
+
* Dispatch `payload` to every handler registered for `event`.
|
|
1628
|
+
*
|
|
1629
|
+
* Handlers run sequentially in registration order — regular ones
|
|
1630
|
+
* first, then `once` handlers. Async handlers are awaited. Synchronous
|
|
1631
|
+
* exceptions are caught, logged, and isolated to the offending handler.
|
|
1632
|
+
*/
|
|
1633
|
+
async emit(event, payload) {
|
|
1634
|
+
const regular = this.#regular.get(event);
|
|
1635
|
+
if (regular) {
|
|
1636
|
+
for (const handler of [...regular]) await this.#invoke(handler, payload, event);
|
|
1637
|
+
}
|
|
1638
|
+
const once = this.#once.get(event);
|
|
1639
|
+
if (once && once.size > 0) {
|
|
1640
|
+
const snapshot = [...once];
|
|
1641
|
+
this.#once.delete(event);
|
|
1642
|
+
for (const handler of snapshot) await this.#invoke(handler, payload, event);
|
|
1643
|
+
}
|
|
1644
|
+
}
|
|
1645
|
+
/**
|
|
1646
|
+
* Number of handlers currently registered for `event`.
|
|
1647
|
+
* Sum of `on` and `once` registrations.
|
|
1648
|
+
*/
|
|
1649
|
+
listenerCount(event) {
|
|
1650
|
+
return (this.#regular.get(event)?.size ?? 0) + (this.#once.get(event)?.size ?? 0);
|
|
1651
|
+
}
|
|
1652
|
+
/**
|
|
1653
|
+
* Remove every handler. Pass an event name to limit the removal to
|
|
1654
|
+
* one event; omit it to clear every event on this emitter.
|
|
1655
|
+
*
|
|
1656
|
+
* Crucially this only affects the current instance — there is no
|
|
1657
|
+
* shared global state.
|
|
1658
|
+
*/
|
|
1659
|
+
removeAllListeners(event) {
|
|
1660
|
+
if (event === void 0) {
|
|
1661
|
+
this.#regular.clear();
|
|
1662
|
+
this.#once.clear();
|
|
1663
|
+
return;
|
|
1664
|
+
}
|
|
1665
|
+
this.#regular.delete(event);
|
|
1666
|
+
this.#once.delete(event);
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Snapshot of all event names that currently have at least one
|
|
1670
|
+
* registered handler. Useful for debugging.
|
|
1671
|
+
*/
|
|
1672
|
+
eventNames() {
|
|
1673
|
+
const names = /* @__PURE__ */ new Set();
|
|
1674
|
+
for (const key of this.#regular.keys()) names.add(key);
|
|
1675
|
+
for (const key of this.#once.keys()) names.add(key);
|
|
1676
|
+
return [...names];
|
|
1677
|
+
}
|
|
1678
|
+
#register(bucket, event, handler) {
|
|
1679
|
+
let set = bucket.get(event);
|
|
1680
|
+
if (!set) {
|
|
1681
|
+
set = /* @__PURE__ */ new Set();
|
|
1682
|
+
bucket.set(event, set);
|
|
1683
|
+
}
|
|
1684
|
+
set.add(handler);
|
|
1685
|
+
return () => {
|
|
1686
|
+
bucket.get(event)?.delete(handler);
|
|
1687
|
+
};
|
|
1688
|
+
}
|
|
1689
|
+
async #invoke(handler, payload, event) {
|
|
1690
|
+
try {
|
|
1691
|
+
await handler(payload);
|
|
1692
|
+
} catch (err) {
|
|
1693
|
+
const label = typeof event === "string" || typeof event === "number" ? String(event) : "<event>";
|
|
1694
|
+
console.error(`[live-preview] handler for "${label}" threw:`, err);
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
};
|
|
1698
|
+
|
|
1699
|
+
// src/detection/environment.ts
|
|
1700
|
+
function isInIframe() {
|
|
1701
|
+
if (typeof window === "undefined") return false;
|
|
1702
|
+
try {
|
|
1703
|
+
return window.self !== window.top;
|
|
1704
|
+
} catch {
|
|
1705
|
+
return true;
|
|
1706
|
+
}
|
|
1707
|
+
}
|
|
1708
|
+
function isInPopup() {
|
|
1709
|
+
if (typeof window === "undefined") return false;
|
|
1710
|
+
return window.opener != null;
|
|
1711
|
+
}
|
|
1712
|
+
function isInPreviewContext() {
|
|
1713
|
+
return isInIframe() || isInPopup();
|
|
1714
|
+
}
|
|
1715
|
+
function isDevMode() {
|
|
1716
|
+
const nodeEnv = readNodeEnv();
|
|
1717
|
+
if (nodeEnv !== void 0) return nodeEnv !== "production";
|
|
1718
|
+
const importMetaDev = readImportMetaDev();
|
|
1719
|
+
if (importMetaDev !== void 0) return importMetaDev;
|
|
1720
|
+
if (typeof window !== "undefined") {
|
|
1721
|
+
const host = window.location.hostname;
|
|
1722
|
+
if (host === "localhost" || host === "127.0.0.1") return true;
|
|
1723
|
+
}
|
|
1724
|
+
return false;
|
|
1725
|
+
}
|
|
1726
|
+
function getEnvVar(name) {
|
|
1727
|
+
if (typeof process !== "undefined") {
|
|
1728
|
+
const value = process.env[name];
|
|
1729
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1730
|
+
}
|
|
1731
|
+
const meta = tryReadImportMetaEnv();
|
|
1732
|
+
if (meta) {
|
|
1733
|
+
const value = meta[name];
|
|
1734
|
+
if (typeof value === "string" && value.length > 0) return value;
|
|
1735
|
+
}
|
|
1736
|
+
return void 0;
|
|
1737
|
+
}
|
|
1738
|
+
function readNodeEnv() {
|
|
1739
|
+
if (typeof process === "undefined") return void 0;
|
|
1740
|
+
const value = process.env["NODE_ENV"];
|
|
1741
|
+
return typeof value === "string" ? value : void 0;
|
|
1742
|
+
}
|
|
1743
|
+
function readImportMetaDev() {
|
|
1744
|
+
const meta = tryReadImportMetaEnv();
|
|
1745
|
+
if (!meta) return void 0;
|
|
1746
|
+
const dev = meta["DEV"];
|
|
1747
|
+
return typeof dev === "boolean" ? dev : void 0;
|
|
1748
|
+
}
|
|
1749
|
+
var importMetaEnvCache;
|
|
1750
|
+
function tryReadImportMetaEnv() {
|
|
1751
|
+
if (typeof __INLINE_RUNTIME_BUILD__ !== "undefined" && __INLINE_RUNTIME_BUILD__) {
|
|
1752
|
+
return void 0;
|
|
1753
|
+
}
|
|
1754
|
+
if (importMetaEnvCache !== void 0) return importMetaEnvCache ?? void 0;
|
|
1755
|
+
importMetaEnvCache = null;
|
|
1756
|
+
try {
|
|
1757
|
+
const reader = new Function(
|
|
1758
|
+
"try { return import.meta && import.meta.env; } catch (_) { return undefined; }"
|
|
1759
|
+
);
|
|
1760
|
+
const result = reader();
|
|
1761
|
+
if (result && typeof result === "object") {
|
|
1762
|
+
importMetaEnvCache = result;
|
|
1763
|
+
return importMetaEnvCache;
|
|
1764
|
+
}
|
|
1765
|
+
} catch {
|
|
1766
|
+
}
|
|
1767
|
+
return void 0;
|
|
1768
|
+
}
|
|
1769
|
+
|
|
1770
|
+
// src/detection/origin.ts
|
|
1771
|
+
var LOCALHOST_PATTERN = /^https?:\/\/(localhost|127\.0\.0\.1)(?::\d+)?$/i;
|
|
1772
|
+
var VALID_ORIGIN_PATTERN = /^https?:\/\/[^/?#\s]+$/i;
|
|
1773
|
+
var ENV_VAR_NAMES = [
|
|
1774
|
+
"PAYLOAD_ADMIN_ORIGIN",
|
|
1775
|
+
"PUBLIC_PAYLOAD_ADMIN_ORIGIN",
|
|
1776
|
+
"NEXT_PUBLIC_PAYLOAD_ADMIN_ORIGIN"
|
|
1777
|
+
];
|
|
1778
|
+
var OriginDetector = class {
|
|
1779
|
+
#explicitOrigins;
|
|
1780
|
+
#referrerOrigin;
|
|
1781
|
+
#allowLocalhost;
|
|
1782
|
+
#referrerWasAvailable;
|
|
1783
|
+
#lockedOrigin;
|
|
1784
|
+
constructor(options = {}) {
|
|
1785
|
+
const explicit = /* @__PURE__ */ new Set();
|
|
1786
|
+
for (const origin of options.additionalOrigins ?? []) {
|
|
1787
|
+
const normalised = normaliseOrigin(origin);
|
|
1788
|
+
if (normalised !== void 0) explicit.add(normalised);
|
|
1789
|
+
}
|
|
1790
|
+
for (const envName of ENV_VAR_NAMES) {
|
|
1791
|
+
const value = getEnvVar(envName);
|
|
1792
|
+
if (value === void 0) continue;
|
|
1793
|
+
for (const raw of value.split(",")) {
|
|
1794
|
+
const normalised = normaliseOrigin(raw);
|
|
1795
|
+
if (normalised !== void 0) explicit.add(normalised);
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
this.#explicitOrigins = explicit;
|
|
1799
|
+
const useReferrer = options.enableReferrerDetection ?? true;
|
|
1800
|
+
let referrerOrigin;
|
|
1801
|
+
if (useReferrer) {
|
|
1802
|
+
const referrer = options.referrer ?? (typeof document !== "undefined" ? document.referrer : "");
|
|
1803
|
+
if (referrer.length > 0) {
|
|
1804
|
+
try {
|
|
1805
|
+
const origin = new URL(referrer).origin;
|
|
1806
|
+
if (origin && origin !== "null") referrerOrigin = origin;
|
|
1807
|
+
} catch {
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
this.#referrerOrigin = referrerOrigin;
|
|
1812
|
+
this.#referrerWasAvailable = referrerOrigin !== void 0;
|
|
1813
|
+
const allowLocalhost = (options.enableLocalhostMatching ?? true) && (options.forceDevMode ?? isDevMode());
|
|
1814
|
+
this.#allowLocalhost = allowLocalhost;
|
|
1815
|
+
}
|
|
1816
|
+
/**
|
|
1817
|
+
* Returns `true` when `origin` is trusted under the current policy.
|
|
1818
|
+
*
|
|
1819
|
+
* If an origin has been locked, only that exact value matches.
|
|
1820
|
+
* Otherwise explicit origins and (in dev mode) localhost-pattern
|
|
1821
|
+
* matches are accepted. The referrer-derived origin is a **fallback,
|
|
1822
|
+
* not a union member**: it only counts when NO explicit origins are
|
|
1823
|
+
* configured. `document.referrer` always names whoever actually
|
|
1824
|
+
* framed the page — if it were unioned with the explicit list, any
|
|
1825
|
+
* site embedding the page in an iframe would become a trusted
|
|
1826
|
+
* postMessage sender despite the consumer having pinned the admin
|
|
1827
|
+
* origin explicitly.
|
|
1828
|
+
*/
|
|
1829
|
+
matches(origin) {
|
|
1830
|
+
if (origin.length === 0 || origin === "null") return false;
|
|
1831
|
+
if (this.#lockedOrigin !== void 0) return origin === this.#lockedOrigin;
|
|
1832
|
+
if (this.#explicitOrigins.has(origin)) return true;
|
|
1833
|
+
if (this.#explicitOrigins.size === 0 && this.#referrerOrigin !== void 0 && origin === this.#referrerOrigin) {
|
|
1834
|
+
return true;
|
|
1835
|
+
}
|
|
1836
|
+
if (this.#allowLocalhost && LOCALHOST_PATTERN.test(origin)) return true;
|
|
1837
|
+
return false;
|
|
1838
|
+
}
|
|
1839
|
+
/**
|
|
1840
|
+
* Narrow the trusted set to a single origin after a successful
|
|
1841
|
+
* handshake. Subsequent calls to `matches()` only allow `origin`.
|
|
1842
|
+
*
|
|
1843
|
+
* Returns `true` if locking succeeded; `false` if the candidate is
|
|
1844
|
+
* not currently considered trusted (which would defeat the purpose
|
|
1845
|
+
* of locking).
|
|
1846
|
+
*/
|
|
1847
|
+
lockOrigin(origin) {
|
|
1848
|
+
if (!this.matches(origin)) return false;
|
|
1849
|
+
this.#lockedOrigin = origin;
|
|
1850
|
+
return true;
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Release the origin lock so any allow-listed origin is trusted
|
|
1854
|
+
* again until the next successful handshake. The lifecycle calls
|
|
1855
|
+
* this on heartbeat timeout — after the trusted parent disappears,
|
|
1856
|
+
* a different (still-allowed) origin should be free to reconnect.
|
|
1857
|
+
*
|
|
1858
|
+
* Returns the previously-locked origin, or `undefined` when nothing
|
|
1859
|
+
* was locked.
|
|
1860
|
+
*/
|
|
1861
|
+
unlockOrigin() {
|
|
1862
|
+
const previous = this.#lockedOrigin;
|
|
1863
|
+
this.#lockedOrigin = void 0;
|
|
1864
|
+
return previous;
|
|
1865
|
+
}
|
|
1866
|
+
/** The currently locked origin, or `undefined`. */
|
|
1867
|
+
get lockedOrigin() {
|
|
1868
|
+
return this.#lockedOrigin;
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* Snapshot of every origin currently allowed by the policy. Used by
|
|
1872
|
+
* the message bus to broadcast `ready` messages.
|
|
1873
|
+
*
|
|
1874
|
+
* Localhost pattern matches are expanded into the common dev ports
|
|
1875
|
+
* so the handshake can find a Payload running on its usual port.
|
|
1876
|
+
*/
|
|
1877
|
+
enumerate() {
|
|
1878
|
+
if (this.#lockedOrigin !== void 0) return [this.#lockedOrigin];
|
|
1879
|
+
const result = new Set(this.#explicitOrigins);
|
|
1880
|
+
if (this.#explicitOrigins.size === 0 && this.#referrerOrigin !== void 0) {
|
|
1881
|
+
result.add(this.#referrerOrigin);
|
|
1882
|
+
}
|
|
1883
|
+
if (this.#allowLocalhost) {
|
|
1884
|
+
for (const port of LOCALHOST_HANDSHAKE_PORTS) {
|
|
1885
|
+
result.add(`http://localhost:${String(port)}`);
|
|
1886
|
+
result.add(`http://127.0.0.1:${String(port)}`);
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
return [...result];
|
|
1890
|
+
}
|
|
1891
|
+
/**
|
|
1892
|
+
* Reports whether `document.referrer` contributed at least one
|
|
1893
|
+
* origin. Useful for surfacing a console warning when running in
|
|
1894
|
+
* production without an explicit origin and the referrer is absent
|
|
1895
|
+
* (because of a `Referrer-Policy: no-referrer` header).
|
|
1896
|
+
*/
|
|
1897
|
+
get referrerWasAvailable() {
|
|
1898
|
+
return this.#referrerWasAvailable;
|
|
1899
|
+
}
|
|
1900
|
+
/**
|
|
1901
|
+
* Indicates whether the detector is operating in a "production-without-
|
|
1902
|
+
* explicit-origin" configuration. When `true`, the host should log a
|
|
1903
|
+
* loud warning — silent connection failure is the worst outcome.
|
|
1904
|
+
*/
|
|
1905
|
+
get isProductionUnconfigured() {
|
|
1906
|
+
if (!isInIframe()) return false;
|
|
1907
|
+
if (this.#explicitOrigins.size > 0) return false;
|
|
1908
|
+
if (this.#allowLocalhost) return false;
|
|
1909
|
+
if (this.#referrerOrigin !== void 0) return false;
|
|
1910
|
+
return true;
|
|
1911
|
+
}
|
|
1912
|
+
/**
|
|
1913
|
+
* `true` when the referrer is the ONLY trust source (no explicit
|
|
1914
|
+
* origins, no localhost matching). In that mode any site that frames
|
|
1915
|
+
* the page becomes a trusted postMessage sender — `document.referrer`
|
|
1916
|
+
* always names the actual embedder. Content is still sanitized, but
|
|
1917
|
+
* the origin allow-list no longer restricts who may drive it. Hosts
|
|
1918
|
+
* should surface a warning suggesting explicit `allowedOrigins` and
|
|
1919
|
+
* a `frame-ancestors` policy in production.
|
|
1920
|
+
*/
|
|
1921
|
+
get isReferrerOnlyTrust() {
|
|
1922
|
+
if (this.#explicitOrigins.size > 0) return false;
|
|
1923
|
+
if (this.#allowLocalhost) return false;
|
|
1924
|
+
return this.#referrerOrigin !== void 0;
|
|
1925
|
+
}
|
|
1926
|
+
};
|
|
1927
|
+
var LOCALHOST_HANDSHAKE_PORTS = [
|
|
1928
|
+
3e3,
|
|
1929
|
+
3001,
|
|
1930
|
+
3333,
|
|
1931
|
+
4e3,
|
|
1932
|
+
4321,
|
|
1933
|
+
5e3,
|
|
1934
|
+
5173,
|
|
1935
|
+
5174,
|
|
1936
|
+
8e3,
|
|
1937
|
+
8080,
|
|
1938
|
+
8888,
|
|
1939
|
+
9e3
|
|
1940
|
+
];
|
|
1941
|
+
function normaliseOrigin(value) {
|
|
1942
|
+
const trimmed = value.trim();
|
|
1943
|
+
if (trimmed.length === 0) return void 0;
|
|
1944
|
+
try {
|
|
1945
|
+
const url = new URL(trimmed);
|
|
1946
|
+
if (url.protocol !== "http:" && url.protocol !== "https:") return void 0;
|
|
1947
|
+
if (!VALID_ORIGIN_PATTERN.test(url.origin)) return void 0;
|
|
1948
|
+
return url.origin;
|
|
1949
|
+
} catch {
|
|
1950
|
+
return void 0;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
|
|
1954
|
+
// src/plugins/manager.ts
|
|
1955
|
+
var PluginManager = class {
|
|
1956
|
+
#events;
|
|
1957
|
+
#config;
|
|
1958
|
+
#registerRenderer;
|
|
1959
|
+
#log;
|
|
1960
|
+
#plugins = /* @__PURE__ */ new Map();
|
|
1961
|
+
#transforms = /* @__PURE__ */ new Map();
|
|
1962
|
+
constructor(options) {
|
|
1963
|
+
this.#events = options.events;
|
|
1964
|
+
this.#config = options.config;
|
|
1965
|
+
this.#registerRenderer = options.registerFieldRenderer;
|
|
1966
|
+
this.#log = options.log;
|
|
1967
|
+
}
|
|
1968
|
+
/**
|
|
1969
|
+
* Register a plugin and run its `init` hook. Duplicate names are
|
|
1970
|
+
* ignored (with a debug warning). Errors thrown by `init` are
|
|
1971
|
+
* caught and surfaced via the error logger; the plugin is *not*
|
|
1972
|
+
* added to the registry in that case.
|
|
1973
|
+
*/
|
|
1974
|
+
async register(plugin) {
|
|
1975
|
+
if (this.#plugins.has(plugin.name)) {
|
|
1976
|
+
this.#log(`plugin "${plugin.name}" already registered`);
|
|
1977
|
+
return;
|
|
1978
|
+
}
|
|
1979
|
+
const context = {
|
|
1980
|
+
events: this.#events,
|
|
1981
|
+
registerFieldRenderer: (renderer) => {
|
|
1982
|
+
this.#registerRenderer(renderer);
|
|
1983
|
+
},
|
|
1984
|
+
registerTransform: (fieldName, transform) => {
|
|
1985
|
+
this.#addTransform(fieldName, transform);
|
|
1986
|
+
},
|
|
1987
|
+
getConfig: () => this.#config,
|
|
1988
|
+
log: (...args) => {
|
|
1989
|
+
this.#log(`[${plugin.name}]`, ...args);
|
|
1990
|
+
}
|
|
1991
|
+
};
|
|
1992
|
+
try {
|
|
1993
|
+
await plugin.init(context);
|
|
1994
|
+
this.#plugins.set(plugin.name, plugin);
|
|
1995
|
+
this.#log(`plugin "${plugin.name}" registered`);
|
|
1996
|
+
} catch (err) {
|
|
1997
|
+
this.#log(`plugin "${plugin.name}" init failed:`, err);
|
|
1998
|
+
}
|
|
1999
|
+
}
|
|
2000
|
+
/**
|
|
2001
|
+
* Remove a plugin and call its `destroy` hook.
|
|
2002
|
+
*/
|
|
2003
|
+
async unregister(name) {
|
|
2004
|
+
const plugin = this.#plugins.get(name);
|
|
2005
|
+
if (!plugin) return;
|
|
2006
|
+
this.#plugins.delete(name);
|
|
2007
|
+
if (plugin.destroy) {
|
|
2008
|
+
try {
|
|
2009
|
+
await plugin.destroy();
|
|
2010
|
+
} catch (err) {
|
|
2011
|
+
this.#log(`plugin "${name}" destroy failed:`, err);
|
|
2012
|
+
}
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* Destroy every registered plugin. Used by `LivePreviewClient.destroy`.
|
|
2017
|
+
* Errors from individual plugins are isolated.
|
|
2018
|
+
*/
|
|
2019
|
+
async destroyAll() {
|
|
2020
|
+
const names = [...this.#plugins.keys()];
|
|
2021
|
+
for (const name of names) await this.unregister(name);
|
|
2022
|
+
this.#transforms.clear();
|
|
2023
|
+
}
|
|
2024
|
+
/**
|
|
2025
|
+
* Apply every transform registered for `fieldName` to `value`, in
|
|
2026
|
+
* registration order. Returns the transformed value.
|
|
2027
|
+
*/
|
|
2028
|
+
applyTransforms(fieldName, value, context) {
|
|
2029
|
+
const transforms = this.#transforms.get(fieldName);
|
|
2030
|
+
if (!transforms || transforms.length === 0) return value;
|
|
2031
|
+
let result = value;
|
|
2032
|
+
for (const transform of transforms) {
|
|
2033
|
+
try {
|
|
2034
|
+
result = transform(result, {
|
|
2035
|
+
fieldName,
|
|
2036
|
+
element: context.element,
|
|
2037
|
+
allFields: context.allFields
|
|
2038
|
+
});
|
|
2039
|
+
} catch (err) {
|
|
2040
|
+
this.#log(`transform for "${fieldName}" threw:`, err);
|
|
2041
|
+
return value;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
return result;
|
|
2045
|
+
}
|
|
2046
|
+
/** Names of currently registered plugins. */
|
|
2047
|
+
list() {
|
|
2048
|
+
return [...this.#plugins.keys()];
|
|
2049
|
+
}
|
|
2050
|
+
/** Test introspection — number of plugins. */
|
|
2051
|
+
get size() {
|
|
2052
|
+
return this.#plugins.size;
|
|
2053
|
+
}
|
|
2054
|
+
#addTransform(fieldName, transform) {
|
|
2055
|
+
const existing = this.#transforms.get(fieldName);
|
|
2056
|
+
if (existing) existing.push(transform);
|
|
2057
|
+
else this.#transforms.set(fieldName, [transform]);
|
|
2058
|
+
}
|
|
2059
|
+
};
|
|
2060
|
+
|
|
2061
|
+
// src/security/sanitizer.ts
|
|
2062
|
+
var ALLOWED_TAGS = /* @__PURE__ */ new Set([
|
|
2063
|
+
"p",
|
|
2064
|
+
"br",
|
|
2065
|
+
"strong",
|
|
2066
|
+
"b",
|
|
2067
|
+
"em",
|
|
2068
|
+
"i",
|
|
2069
|
+
"u",
|
|
2070
|
+
"s",
|
|
2071
|
+
"strike",
|
|
2072
|
+
"mark",
|
|
2073
|
+
"small",
|
|
2074
|
+
"h1",
|
|
2075
|
+
"h2",
|
|
2076
|
+
"h3",
|
|
2077
|
+
"h4",
|
|
2078
|
+
"h5",
|
|
2079
|
+
"h6",
|
|
2080
|
+
"ul",
|
|
2081
|
+
"ol",
|
|
2082
|
+
"li",
|
|
2083
|
+
"dl",
|
|
2084
|
+
"dt",
|
|
2085
|
+
"dd",
|
|
2086
|
+
"blockquote",
|
|
2087
|
+
"code",
|
|
2088
|
+
"pre",
|
|
2089
|
+
"kbd",
|
|
2090
|
+
"samp",
|
|
2091
|
+
"var",
|
|
2092
|
+
"a",
|
|
2093
|
+
"span",
|
|
2094
|
+
"div",
|
|
2095
|
+
"section",
|
|
2096
|
+
"article",
|
|
2097
|
+
"aside",
|
|
2098
|
+
"header",
|
|
2099
|
+
"footer",
|
|
2100
|
+
"nav",
|
|
2101
|
+
"figure",
|
|
2102
|
+
"figcaption",
|
|
2103
|
+
"img",
|
|
2104
|
+
"picture",
|
|
2105
|
+
"source",
|
|
2106
|
+
"audio",
|
|
2107
|
+
"video",
|
|
2108
|
+
"sub",
|
|
2109
|
+
"sup",
|
|
2110
|
+
"hr",
|
|
2111
|
+
"time",
|
|
2112
|
+
"abbr",
|
|
2113
|
+
"cite",
|
|
2114
|
+
"q",
|
|
2115
|
+
"table",
|
|
2116
|
+
"thead",
|
|
2117
|
+
"tbody",
|
|
2118
|
+
"tfoot",
|
|
2119
|
+
"tr",
|
|
2120
|
+
"th",
|
|
2121
|
+
"td",
|
|
2122
|
+
"caption",
|
|
2123
|
+
"colgroup",
|
|
2124
|
+
"col"
|
|
2125
|
+
]);
|
|
2126
|
+
var REMOVE_COMPLETELY = /* @__PURE__ */ new Set([
|
|
2127
|
+
"script",
|
|
2128
|
+
"style",
|
|
2129
|
+
"iframe",
|
|
2130
|
+
"object",
|
|
2131
|
+
"embed",
|
|
2132
|
+
"link",
|
|
2133
|
+
"meta",
|
|
2134
|
+
"base",
|
|
2135
|
+
"form",
|
|
2136
|
+
"input",
|
|
2137
|
+
"button",
|
|
2138
|
+
"select",
|
|
2139
|
+
"textarea",
|
|
2140
|
+
"svg",
|
|
2141
|
+
"math",
|
|
2142
|
+
"template",
|
|
2143
|
+
"frame",
|
|
2144
|
+
"frameset",
|
|
2145
|
+
"noframes",
|
|
2146
|
+
"noscript"
|
|
2147
|
+
]);
|
|
2148
|
+
var ATTR_GLOBAL = /* @__PURE__ */ new Set([
|
|
2149
|
+
"id",
|
|
2150
|
+
"class",
|
|
2151
|
+
"lang",
|
|
2152
|
+
"dir",
|
|
2153
|
+
"title",
|
|
2154
|
+
"role",
|
|
2155
|
+
"tabindex"
|
|
2156
|
+
]);
|
|
2157
|
+
var ATTR_ARIA_PREFIX = "aria-";
|
|
2158
|
+
var ATTR_DATA_PREFIX = "data-";
|
|
2159
|
+
var ATTR_BY_TAG = {
|
|
2160
|
+
a: /* @__PURE__ */ new Set(["href", "target", "rel", "download", "hreflang", "type"]),
|
|
2161
|
+
img: /* @__PURE__ */ new Set(["src", "srcset", "sizes", "alt", "width", "height", "loading", "decoding"]),
|
|
2162
|
+
picture: /* @__PURE__ */ new Set([]),
|
|
2163
|
+
source: /* @__PURE__ */ new Set(["src", "srcset", "sizes", "type", "media"]),
|
|
2164
|
+
audio: /* @__PURE__ */ new Set(["src", "controls", "autoplay", "loop", "muted", "preload"]),
|
|
2165
|
+
video: /* @__PURE__ */ new Set([
|
|
2166
|
+
"src",
|
|
2167
|
+
"poster",
|
|
2168
|
+
"controls",
|
|
2169
|
+
"autoplay",
|
|
2170
|
+
"loop",
|
|
2171
|
+
"muted",
|
|
2172
|
+
"preload",
|
|
2173
|
+
"width",
|
|
2174
|
+
"height",
|
|
2175
|
+
"playsinline"
|
|
2176
|
+
]),
|
|
2177
|
+
time: /* @__PURE__ */ new Set(["datetime"]),
|
|
2178
|
+
abbr: /* @__PURE__ */ new Set(["title"]),
|
|
2179
|
+
q: /* @__PURE__ */ new Set(["cite"]),
|
|
2180
|
+
blockquote: /* @__PURE__ */ new Set(["cite"]),
|
|
2181
|
+
table: /* @__PURE__ */ new Set(["summary"]),
|
|
2182
|
+
th: /* @__PURE__ */ new Set(["colspan", "rowspan", "scope", "headers", "abbr"]),
|
|
2183
|
+
td: /* @__PURE__ */ new Set(["colspan", "rowspan", "headers"]),
|
|
2184
|
+
col: /* @__PURE__ */ new Set(["span"]),
|
|
2185
|
+
colgroup: /* @__PURE__ */ new Set(["span"]),
|
|
2186
|
+
ol: /* @__PURE__ */ new Set(["start", "reversed", "type"]),
|
|
2187
|
+
li: /* @__PURE__ */ new Set(["value"]),
|
|
2188
|
+
code: /* @__PURE__ */ new Set(["class"]),
|
|
2189
|
+
pre: /* @__PURE__ */ new Set(["class"])
|
|
2190
|
+
};
|
|
2191
|
+
var URL_ATTRIBUTES2 = /* @__PURE__ */ new Set(["href", "src", "cite", "poster"]);
|
|
2192
|
+
var SanitizerEnvironmentError = class extends Error {
|
|
2193
|
+
name = "SanitizerEnvironmentError";
|
|
2194
|
+
};
|
|
2195
|
+
function resolvePolicy(options) {
|
|
2196
|
+
if (!options) {
|
|
2197
|
+
const attrMap2 = /* @__PURE__ */ new Map();
|
|
2198
|
+
for (const [tag, attrs] of Object.entries(ATTR_BY_TAG)) attrMap2.set(tag, attrs);
|
|
2199
|
+
return { allowedTags: ALLOWED_TAGS, attrByTag: attrMap2 };
|
|
2200
|
+
}
|
|
2201
|
+
const allowed = new Set(ALLOWED_TAGS);
|
|
2202
|
+
for (const tag of options.additionalAllowedTags ?? []) allowed.add(tag);
|
|
2203
|
+
const attrMap = /* @__PURE__ */ new Map();
|
|
2204
|
+
for (const [tag, attrs] of Object.entries(ATTR_BY_TAG)) attrMap.set(tag, attrs);
|
|
2205
|
+
for (const [tag, attrs] of Object.entries(options.additionalAllowedAttributes ?? {})) {
|
|
2206
|
+
const existing = attrMap.get(tag);
|
|
2207
|
+
const merged = new Set(existing ?? []);
|
|
2208
|
+
for (const attr of attrs) merged.add(attr);
|
|
2209
|
+
attrMap.set(tag, merged);
|
|
2210
|
+
}
|
|
2211
|
+
return { allowedTags: allowed, attrByTag: attrMap };
|
|
2212
|
+
}
|
|
2213
|
+
var documentOverride;
|
|
2214
|
+
function setSanitizerDocument(doc) {
|
|
2215
|
+
documentOverride = doc ?? void 0;
|
|
2216
|
+
}
|
|
2217
|
+
function sanitizeHtml(html, options) {
|
|
2218
|
+
const doc = resolveDocument();
|
|
2219
|
+
if (!doc) {
|
|
2220
|
+
throw new SanitizerEnvironmentError(
|
|
2221
|
+
"sanitizeHtml requires a DOM. Inject one via setSanitizerDocument() for SSR (linkedom, jsdom, happy-dom, \u2026) or use the plain-text path on the server."
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
if (html === "") return "";
|
|
2225
|
+
const policy = resolvePolicy(options);
|
|
2226
|
+
const template = doc.createElement("template");
|
|
2227
|
+
template.innerHTML = html;
|
|
2228
|
+
sanitizeFragment(template.content, policy);
|
|
2229
|
+
return template.innerHTML;
|
|
2230
|
+
}
|
|
2231
|
+
function resolveDocument() {
|
|
2232
|
+
if (documentOverride) return documentOverride;
|
|
2233
|
+
if (typeof document === "undefined") return void 0;
|
|
2234
|
+
return document;
|
|
2235
|
+
}
|
|
2236
|
+
function hasSanitizerDocument() {
|
|
2237
|
+
return resolveDocument() !== void 0;
|
|
2238
|
+
}
|
|
2239
|
+
function sanitizeFragment(node, policy) {
|
|
2240
|
+
for (const child of Array.from(node.childNodes)) {
|
|
2241
|
+
if (child.nodeType === Node.ELEMENT_NODE) {
|
|
2242
|
+
sanitizeElement(child, policy);
|
|
2243
|
+
} else if (child.nodeType === Node.COMMENT_NODE) {
|
|
2244
|
+
child.remove();
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
}
|
|
2248
|
+
function sanitizeElement(element, policy) {
|
|
2249
|
+
const tag = element.tagName.toLowerCase();
|
|
2250
|
+
if (REMOVE_COMPLETELY.has(tag)) {
|
|
2251
|
+
element.remove();
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
if (!policy.allowedTags.has(tag)) {
|
|
2255
|
+
sanitizeFragment(element, policy);
|
|
2256
|
+
const parent = element.parentNode;
|
|
2257
|
+
if (parent) {
|
|
2258
|
+
while (element.firstChild) parent.insertBefore(element.firstChild, element);
|
|
2259
|
+
element.remove();
|
|
2260
|
+
}
|
|
2261
|
+
return;
|
|
2262
|
+
}
|
|
2263
|
+
sanitizeAttributes(element, tag, policy);
|
|
2264
|
+
if (tag === "a") hardenAnchor(element);
|
|
2265
|
+
sanitizeFragment(element, policy);
|
|
2266
|
+
}
|
|
2267
|
+
function sanitizeAttributes(element, tag, policy) {
|
|
2268
|
+
const tagAttrs = policy.attrByTag.get(tag);
|
|
2269
|
+
for (const attr of Array.from(element.attributes)) {
|
|
2270
|
+
const name = attr.name.toLowerCase();
|
|
2271
|
+
if (name.startsWith("on")) {
|
|
2272
|
+
element.removeAttribute(attr.name);
|
|
2273
|
+
continue;
|
|
2274
|
+
}
|
|
2275
|
+
if (name === "style") {
|
|
2276
|
+
element.removeAttribute(attr.name);
|
|
2277
|
+
continue;
|
|
2278
|
+
}
|
|
2279
|
+
if (ATTR_GLOBAL.has(name) || name.startsWith(ATTR_ARIA_PREFIX) || name.startsWith(ATTR_DATA_PREFIX)) {
|
|
2280
|
+
continue;
|
|
2281
|
+
}
|
|
2282
|
+
if (tagAttrs?.has(name)) {
|
|
2283
|
+
if (URL_ATTRIBUTES2.has(name) && !isSafeUrl(attr.value)) {
|
|
2284
|
+
element.removeAttribute(attr.name);
|
|
2285
|
+
} else if (name === "srcset" && !isSafeSrcset(attr.value)) {
|
|
2286
|
+
element.removeAttribute(attr.name);
|
|
2287
|
+
}
|
|
2288
|
+
continue;
|
|
2289
|
+
}
|
|
2290
|
+
element.removeAttribute(attr.name);
|
|
2291
|
+
}
|
|
2292
|
+
}
|
|
2293
|
+
function isSafeSrcset(value) {
|
|
2294
|
+
const candidates = value.split(",");
|
|
2295
|
+
for (const candidate of candidates) {
|
|
2296
|
+
const trimmed = candidate.trim();
|
|
2297
|
+
if (trimmed.length === 0) continue;
|
|
2298
|
+
const url = trimmed.split(/\s+/, 1)[0];
|
|
2299
|
+
if (url === void 0 || url.length === 0 || !isSafeUrl(url)) return false;
|
|
2300
|
+
}
|
|
2301
|
+
return true;
|
|
2302
|
+
}
|
|
2303
|
+
function hardenAnchor(anchor) {
|
|
2304
|
+
const href = anchor.getAttribute("href");
|
|
2305
|
+
if (!href) return;
|
|
2306
|
+
if (!isExternalHttpUrl(href)) return;
|
|
2307
|
+
anchor.setAttribute("rel", "noopener noreferrer");
|
|
2308
|
+
if (!anchor.hasAttribute("target")) anchor.setAttribute("target", "_blank");
|
|
2309
|
+
}
|
|
2310
|
+
|
|
2311
|
+
// src/lexical/registry.ts
|
|
2312
|
+
var registry = /* @__PURE__ */ new Map();
|
|
2313
|
+
function register(type, renderer) {
|
|
2314
|
+
registry.set(type, renderer);
|
|
2315
|
+
}
|
|
2316
|
+
function lookup(type) {
|
|
2317
|
+
return registry.get(type);
|
|
2318
|
+
}
|
|
2319
|
+
|
|
2320
|
+
// src/lexical/utils.ts
|
|
2321
|
+
var ALIGNMENT_NUMERIC = {
|
|
2322
|
+
1: "left",
|
|
2323
|
+
2: "center",
|
|
2324
|
+
3: "right",
|
|
2325
|
+
4: "justify"
|
|
2326
|
+
};
|
|
2327
|
+
var ALIGNMENT_STRING = /* @__PURE__ */ new Set(["left", "center", "right", "justify", "start", "end"]);
|
|
2328
|
+
function resolveAlignment(node) {
|
|
2329
|
+
const fmt = node.format;
|
|
2330
|
+
if (typeof fmt === "string" && ALIGNMENT_STRING.has(fmt)) return fmt;
|
|
2331
|
+
if (typeof fmt === "number") {
|
|
2332
|
+
const mapped = ALIGNMENT_NUMERIC[fmt];
|
|
2333
|
+
if (mapped !== void 0) return mapped;
|
|
2334
|
+
}
|
|
2335
|
+
return void 0;
|
|
2336
|
+
}
|
|
2337
|
+
function resolveIndent(node) {
|
|
2338
|
+
const indent = node.indent;
|
|
2339
|
+
return typeof indent === "number" && indent > 0 ? indent : 0;
|
|
2340
|
+
}
|
|
2341
|
+
function dirAttribute(node) {
|
|
2342
|
+
if (node.direction === "rtl") return ' dir="rtl"';
|
|
2343
|
+
if (node.direction === "ltr") return ' dir="ltr"';
|
|
2344
|
+
return "";
|
|
2345
|
+
}
|
|
2346
|
+
function styleAttribute(node) {
|
|
2347
|
+
const align = resolveAlignment(node);
|
|
2348
|
+
const indent = resolveIndent(node);
|
|
2349
|
+
const styles = [];
|
|
2350
|
+
if (align !== void 0) styles.push(`text-align:${align}`);
|
|
2351
|
+
if (indent > 0) styles.push(`padding-inline-start:${String(indent * 40)}px`);
|
|
2352
|
+
if (styles.length === 0) return "";
|
|
2353
|
+
return ` style="${styles.join(";")}"`;
|
|
2354
|
+
}
|
|
2355
|
+
|
|
2356
|
+
// src/security/escape.ts
|
|
2357
|
+
var HTML_ESCAPES = Object.freeze({
|
|
2358
|
+
"&": "&",
|
|
2359
|
+
"<": "<",
|
|
2360
|
+
">": ">",
|
|
2361
|
+
'"': """,
|
|
2362
|
+
"'": "'",
|
|
2363
|
+
"/": "/",
|
|
2364
|
+
"`": "`",
|
|
2365
|
+
"=": "="
|
|
2366
|
+
});
|
|
2367
|
+
var HTML_ESCAPE_PATTERN = /[&<>"'/`=]/g;
|
|
2368
|
+
function escapeHtml(text) {
|
|
2369
|
+
if (text === "") return "";
|
|
2370
|
+
return text.replace(HTML_ESCAPE_PATTERN, (char) => HTML_ESCAPES[char]);
|
|
2371
|
+
}
|
|
2372
|
+
function escapeCssUrl(value) {
|
|
2373
|
+
return value.replace(/['"()\\]/g, "\\$&");
|
|
2374
|
+
}
|
|
2375
|
+
var ATTR_ESCAPES = Object.freeze({
|
|
2376
|
+
"&": "&",
|
|
2377
|
+
"<": "<",
|
|
2378
|
+
">": ">",
|
|
2379
|
+
'"': """,
|
|
2380
|
+
"'": "'"
|
|
2381
|
+
});
|
|
2382
|
+
var ATTR_ESCAPE_PATTERN = /[&<>"']/g;
|
|
2383
|
+
function escapeHtmlAttribute(value) {
|
|
2384
|
+
if (value === "") return "";
|
|
2385
|
+
return value.replace(ATTR_ESCAPE_PATTERN, (char) => ATTR_ESCAPES[char]);
|
|
2386
|
+
}
|
|
2387
|
+
function escapeAndLinebreak(text) {
|
|
2388
|
+
return escapeHtml(text).replace(/\r\n|\r|\n/g, "<br>");
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
// src/lexical/types.ts
|
|
2392
|
+
var TextFormat = {
|
|
2393
|
+
BOLD: 1,
|
|
2394
|
+
ITALIC: 2,
|
|
2395
|
+
STRIKETHROUGH: 4,
|
|
2396
|
+
UNDERLINE: 8,
|
|
2397
|
+
CODE: 16,
|
|
2398
|
+
SUBSCRIPT: 32,
|
|
2399
|
+
SUPERSCRIPT: 64,
|
|
2400
|
+
HIGHLIGHT: 128
|
|
2401
|
+
};
|
|
2402
|
+
|
|
2403
|
+
// src/lexical/nodes/text.ts
|
|
2404
|
+
var textRenderer = (node) => {
|
|
2405
|
+
const raw = typeof node.text === "string" ? node.text : "";
|
|
2406
|
+
let out = escapeHtml(raw);
|
|
2407
|
+
const format2 = typeof node.format === "number" ? node.format : 0;
|
|
2408
|
+
if ((format2 & TextFormat.CODE) !== 0) out = `<code>${out}</code>`;
|
|
2409
|
+
if ((format2 & TextFormat.BOLD) !== 0) out = `<strong>${out}</strong>`;
|
|
2410
|
+
if ((format2 & TextFormat.ITALIC) !== 0) out = `<em>${out}</em>`;
|
|
2411
|
+
if ((format2 & TextFormat.UNDERLINE) !== 0) out = `<u>${out}</u>`;
|
|
2412
|
+
if ((format2 & TextFormat.STRIKETHROUGH) !== 0) out = `<s>${out}</s>`;
|
|
2413
|
+
if ((format2 & TextFormat.SUBSCRIPT) !== 0) out = `<sub>${out}</sub>`;
|
|
2414
|
+
if ((format2 & TextFormat.SUPERSCRIPT) !== 0) out = `<sup>${out}</sup>`;
|
|
2415
|
+
if ((format2 & TextFormat.HIGHLIGHT) !== 0) out = `<mark>${out}</mark>`;
|
|
2416
|
+
return out;
|
|
2417
|
+
};
|
|
2418
|
+
register("text", textRenderer);
|
|
2419
|
+
|
|
2420
|
+
// src/lexical/nodes/paragraph.ts
|
|
2421
|
+
var paragraphRenderer = (node, ctx) => {
|
|
2422
|
+
const children = ctx.renderChildren(node.children ?? []);
|
|
2423
|
+
return `<p${dirAttribute(node)}${styleAttribute(node)}>${children}</p>`;
|
|
2424
|
+
};
|
|
2425
|
+
register("paragraph", paragraphRenderer);
|
|
2426
|
+
|
|
2427
|
+
// src/lexical/nodes/heading.ts
|
|
2428
|
+
var VALID_TAGS = /* @__PURE__ */ new Set(["h1", "h2", "h3", "h4", "h5", "h6"]);
|
|
2429
|
+
var headingRenderer = (node, ctx) => {
|
|
2430
|
+
const tagRaw = typeof node["tag"] === "string" ? node["tag"].toLowerCase() : "h2";
|
|
2431
|
+
const tag = VALID_TAGS.has(tagRaw) ? tagRaw : "h2";
|
|
2432
|
+
const children = ctx.renderChildren(node.children ?? []);
|
|
2433
|
+
return `<${tag}${dirAttribute(node)}${styleAttribute(node)}>${children}</${tag}>`;
|
|
2434
|
+
};
|
|
2435
|
+
register("heading", headingRenderer);
|
|
2436
|
+
|
|
2437
|
+
// src/lexical/nodes/list.ts
|
|
2438
|
+
var listRenderer = (node, ctx) => {
|
|
2439
|
+
const listType = typeof node["listType"] === "string" ? node["listType"] : "bullet";
|
|
2440
|
+
const tag = listType === "number" ? "ol" : "ul";
|
|
2441
|
+
const startAttr = listType === "number" && typeof node["start"] === "number" && node["start"] > 1 ? ` start="${String(node["start"])}"` : "";
|
|
2442
|
+
const checkAttr = listType === "check" ? ' role="list"' : "";
|
|
2443
|
+
const children = ctx.renderChildren(node.children ?? []);
|
|
2444
|
+
return `<${tag}${dirAttribute(node)}${styleAttribute(node)}${startAttr}${checkAttr}>${children}</${tag}>`;
|
|
2445
|
+
};
|
|
2446
|
+
var listItemRenderer = (node, ctx) => {
|
|
2447
|
+
const children = ctx.renderChildren(node.children ?? []);
|
|
2448
|
+
if (typeof node["checked"] === "boolean") {
|
|
2449
|
+
const state = node["checked"] ? "true" : "false";
|
|
2450
|
+
return `<li role="checkbox" aria-checked="${state}"${styleAttribute(node)}>${children}</li>`;
|
|
2451
|
+
}
|
|
2452
|
+
const valueAttr = typeof node["value"] === "number" ? ` value="${escapeHtml(String(node["value"]))}"` : "";
|
|
2453
|
+
return `<li${dirAttribute(node)}${styleAttribute(node)}${valueAttr}>${children}</li>`;
|
|
2454
|
+
};
|
|
2455
|
+
register("list", listRenderer);
|
|
2456
|
+
register("listitem", listItemRenderer);
|
|
2457
|
+
|
|
2458
|
+
// src/lexical/nodes/link.ts
|
|
2459
|
+
var linkRenderer = (node, ctx) => {
|
|
2460
|
+
const url = typeof node["url"] === "string" ? node["url"] : "";
|
|
2461
|
+
const children = ctx.renderChildren(node.children ?? []);
|
|
2462
|
+
if (url.length === 0 || !isSafeUrl(url)) return children;
|
|
2463
|
+
const targetAttr = isExternalHttpUrl(url) ? ' target="_blank" rel="noopener noreferrer"' : buildOptionalAttrs(node);
|
|
2464
|
+
const titleAttr = typeof node["title"] === "string" ? ` title="${escapeHtml(node["title"])}"` : "";
|
|
2465
|
+
return `<a href="${escapeHtmlAttribute(url)}"${targetAttr}${titleAttr}>${children}</a>`;
|
|
2466
|
+
};
|
|
2467
|
+
function buildOptionalAttrs(node) {
|
|
2468
|
+
const target = typeof node["target"] === "string" ? node["target"] : "";
|
|
2469
|
+
if (target.length === 0) return "";
|
|
2470
|
+
const safeTarget = escapeHtml(target);
|
|
2471
|
+
if (target === "_blank") return ` target="_blank" rel="noopener noreferrer"`;
|
|
2472
|
+
return ` target="${safeTarget}"`;
|
|
2473
|
+
}
|
|
2474
|
+
register("link", linkRenderer);
|
|
2475
|
+
register("autolink", linkRenderer);
|
|
2476
|
+
|
|
2477
|
+
// src/lexical/nodes/quote.ts
|
|
2478
|
+
var quoteRenderer = (node, ctx) => {
|
|
2479
|
+
const children = ctx.renderChildren(node.children ?? []);
|
|
2480
|
+
return `<blockquote${dirAttribute(node)}${styleAttribute(node)}>${children}</blockquote>`;
|
|
2481
|
+
};
|
|
2482
|
+
register("quote", quoteRenderer);
|
|
2483
|
+
|
|
2484
|
+
// src/lexical/nodes/code.ts
|
|
2485
|
+
var codeRenderer = (node) => {
|
|
2486
|
+
const language = sanitizeLangClass(typeof node["language"] === "string" ? node["language"] : "");
|
|
2487
|
+
const inner = extractText(node);
|
|
2488
|
+
const langClass = language === "" ? "" : ` class="language-${language}"`;
|
|
2489
|
+
return `<pre><code${langClass}>${escapeHtml(inner)}</code></pre>`;
|
|
2490
|
+
};
|
|
2491
|
+
var codeHighlightRenderer = (node) => {
|
|
2492
|
+
const text = typeof node.text === "string" ? node.text : "";
|
|
2493
|
+
const highlight = typeof node["highlightType"] === "string" ? ` class="token-${sanitizeLangClass(node["highlightType"])}"` : "";
|
|
2494
|
+
return `<span${highlight}>${escapeHtml(text)}</span>`;
|
|
2495
|
+
};
|
|
2496
|
+
register("code", codeRenderer);
|
|
2497
|
+
register("code-highlight", codeHighlightRenderer);
|
|
2498
|
+
function extractText(node) {
|
|
2499
|
+
if (typeof node.text === "string") return node.text;
|
|
2500
|
+
if (!node.children) return "";
|
|
2501
|
+
let out = "";
|
|
2502
|
+
for (const child of node.children) out += extractText(child);
|
|
2503
|
+
return out;
|
|
2504
|
+
}
|
|
2505
|
+
function sanitizeLangClass(value) {
|
|
2506
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2507
|
+
}
|
|
2508
|
+
|
|
2509
|
+
// src/lexical/nodes/linebreak.ts
|
|
2510
|
+
var linebreakRenderer = () => "<br>";
|
|
2511
|
+
var horizontalRuleRenderer = () => "<hr>";
|
|
2512
|
+
var tabRenderer = () => " ";
|
|
2513
|
+
register("linebreak", linebreakRenderer);
|
|
2514
|
+
register("horizontalrule", horizontalRuleRenderer);
|
|
2515
|
+
register("tab", tabRenderer);
|
|
2516
|
+
|
|
2517
|
+
// src/lexical/nodes/upload.ts
|
|
2518
|
+
var uploadRenderer = (node) => {
|
|
2519
|
+
const value = readUploadValue(node);
|
|
2520
|
+
if (value === void 0) return "";
|
|
2521
|
+
const url = value.url;
|
|
2522
|
+
if (typeof url !== "string" || !isSafeUrl(url)) return "";
|
|
2523
|
+
const mime = typeof value.mimeType === "string" ? value.mimeType : "";
|
|
2524
|
+
if (mime.startsWith("video/")) return renderVideo(value, url);
|
|
2525
|
+
if (mime.startsWith("audio/")) return renderAudio(value, url);
|
|
2526
|
+
if (mime === "" || mime.startsWith("image/")) return renderImage(value, url);
|
|
2527
|
+
return renderFallbackLink(value, url);
|
|
2528
|
+
};
|
|
2529
|
+
register("upload", uploadRenderer);
|
|
2530
|
+
function readUploadValue(node) {
|
|
2531
|
+
const raw = node["value"];
|
|
2532
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
2533
|
+
return raw;
|
|
2534
|
+
}
|
|
2535
|
+
function renderImage(value, url) {
|
|
2536
|
+
const alt = typeof value.alt === "string" ? escapeHtml(value.alt) : "";
|
|
2537
|
+
const width = typeof value.width === "number" ? ` width="${String(value.width)}"` : "";
|
|
2538
|
+
const height = typeof value.height === "number" ? ` height="${String(value.height)}"` : "";
|
|
2539
|
+
return `<img src="${escapeHtmlAttribute(url)}" alt="${alt}"${width}${height} loading="lazy" decoding="async">`;
|
|
2540
|
+
}
|
|
2541
|
+
function renderVideo(value, url) {
|
|
2542
|
+
const width = typeof value.width === "number" ? ` width="${String(value.width)}"` : "";
|
|
2543
|
+
const height = typeof value.height === "number" ? ` height="${String(value.height)}"` : "";
|
|
2544
|
+
const type = typeof value.mimeType === "string" ? ` type="${escapeHtmlAttribute(value.mimeType)}"` : "";
|
|
2545
|
+
return `<video controls${width}${height}><source src="${escapeHtmlAttribute(url)}"${type}></video>`;
|
|
2546
|
+
}
|
|
2547
|
+
function renderAudio(value, url) {
|
|
2548
|
+
const type = typeof value.mimeType === "string" ? ` type="${escapeHtmlAttribute(value.mimeType)}"` : "";
|
|
2549
|
+
return `<audio controls><source src="${escapeHtmlAttribute(url)}"${type}></audio>`;
|
|
2550
|
+
}
|
|
2551
|
+
function renderFallbackLink(value, url) {
|
|
2552
|
+
const label = typeof value.filename === "string" ? escapeHtml(value.filename) : escapeHtml(url);
|
|
2553
|
+
return `<a href="${escapeHtmlAttribute(url)}" target="_blank" rel="noopener noreferrer">${label}</a>`;
|
|
2554
|
+
}
|
|
2555
|
+
|
|
2556
|
+
// src/lexical/nodes/relationship.ts
|
|
2557
|
+
var relationshipRenderer = (node) => {
|
|
2558
|
+
const relationTo = typeof node["relationTo"] === "string" ? node["relationTo"] : "";
|
|
2559
|
+
const value = readValue(node);
|
|
2560
|
+
const label = pickLabel(value, relationTo);
|
|
2561
|
+
const safeLabel = escapeHtml(label);
|
|
2562
|
+
const relAttr = relationTo === "" ? "" : ` data-relation-to="${escapeHtml(relationTo)}"`;
|
|
2563
|
+
const url = value?.url;
|
|
2564
|
+
if (typeof url === "string" && isSafeUrl(url)) {
|
|
2565
|
+
return `<a href="${escapeHtmlAttribute(url)}"${relAttr}>${safeLabel}</a>`;
|
|
2566
|
+
}
|
|
2567
|
+
return `<span${relAttr}>${safeLabel}</span>`;
|
|
2568
|
+
};
|
|
2569
|
+
register("relationship", relationshipRenderer);
|
|
2570
|
+
function readValue(node) {
|
|
2571
|
+
const raw = node["value"];
|
|
2572
|
+
if (typeof raw !== "object" || raw === null) return void 0;
|
|
2573
|
+
return raw;
|
|
2574
|
+
}
|
|
2575
|
+
function pickLabel(value, relationTo) {
|
|
2576
|
+
if (value === void 0) return relationTo === "" ? "#" : `#${relationTo}`;
|
|
2577
|
+
if (typeof value.title === "string" && value.title.length > 0) return value.title;
|
|
2578
|
+
if (typeof value.name === "string" && value.name.length > 0) return value.name;
|
|
2579
|
+
if (typeof value.slug === "string" && value.slug.length > 0) return value.slug;
|
|
2580
|
+
if (value.id !== void 0) return String(value.id);
|
|
2581
|
+
return relationTo === "" ? "#" : `#${relationTo}`;
|
|
2582
|
+
}
|
|
2583
|
+
|
|
2584
|
+
// src/lexical/blocks/registry.ts
|
|
2585
|
+
var registry2 = /* @__PURE__ */ new Map();
|
|
2586
|
+
function registerBlockRenderer(blockType, renderer) {
|
|
2587
|
+
registry2.set(blockType, renderer);
|
|
2588
|
+
}
|
|
2589
|
+
function lookupBlockRenderer(blockType) {
|
|
2590
|
+
return registry2.get(blockType);
|
|
2591
|
+
}
|
|
2592
|
+
|
|
2593
|
+
// src/lexical/nodes/block.ts
|
|
2594
|
+
var blockRenderer = (node, ctx) => {
|
|
2595
|
+
const fields = readFields(node);
|
|
2596
|
+
const blockTypeRaw = typeof fields["blockType"] === "string" ? fields["blockType"] : "";
|
|
2597
|
+
const slug = sanitizeIdent(blockTypeRaw);
|
|
2598
|
+
if (blockTypeRaw !== "") {
|
|
2599
|
+
const custom = lookupBlockRenderer(blockTypeRaw) ?? lookupBlockRenderer(slug);
|
|
2600
|
+
if (custom) {
|
|
2601
|
+
return custom(fields, { renderChildren: ctx.renderChildren });
|
|
2602
|
+
}
|
|
2603
|
+
}
|
|
2604
|
+
const attrs = [];
|
|
2605
|
+
if (slug !== "") attrs.push(`data-block-type="${escapeHtml(slug)}"`);
|
|
2606
|
+
for (const [key, value] of Object.entries(fields)) {
|
|
2607
|
+
if (key === "blockType" || key === "id") continue;
|
|
2608
|
+
if (value === null || value === void 0) continue;
|
|
2609
|
+
const stringValue = typeof value === "string" || typeof value === "number" || typeof value === "boolean" ? String(value) : JSON.stringify(value);
|
|
2610
|
+
attrs.push(`data-block-${sanitizeIdent(key)}="${escapeHtml(stringValue)}"`);
|
|
2611
|
+
}
|
|
2612
|
+
const attrString = attrs.length === 0 ? "" : ` ${attrs.join(" ")}`;
|
|
2613
|
+
return `<div${attrString}></div>`;
|
|
2614
|
+
};
|
|
2615
|
+
register("block", blockRenderer);
|
|
2616
|
+
function readFields(node) {
|
|
2617
|
+
const raw = node["fields"];
|
|
2618
|
+
if (typeof raw !== "object" || raw === null) return {};
|
|
2619
|
+
return raw;
|
|
2620
|
+
}
|
|
2621
|
+
function sanitizeIdent(value) {
|
|
2622
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, "-").toLowerCase();
|
|
2623
|
+
}
|
|
2624
|
+
|
|
2625
|
+
// src/lexical/render.ts
|
|
2626
|
+
register("text", textRenderer);
|
|
2627
|
+
register("paragraph", paragraphRenderer);
|
|
2628
|
+
register("heading", headingRenderer);
|
|
2629
|
+
register("list", listRenderer);
|
|
2630
|
+
register("listitem", listItemRenderer);
|
|
2631
|
+
register("link", linkRenderer);
|
|
2632
|
+
register("autolink", linkRenderer);
|
|
2633
|
+
register("quote", quoteRenderer);
|
|
2634
|
+
register("code", codeRenderer);
|
|
2635
|
+
register("code-highlight", codeHighlightRenderer);
|
|
2636
|
+
register("linebreak", linebreakRenderer);
|
|
2637
|
+
register("horizontalrule", horizontalRuleRenderer);
|
|
2638
|
+
register("tab", tabRenderer);
|
|
2639
|
+
register("upload", uploadRenderer);
|
|
2640
|
+
register("relationship", relationshipRenderer);
|
|
2641
|
+
register("block", blockRenderer);
|
|
2642
|
+
var RENDER_CONTEXT = {
|
|
2643
|
+
renderChildren,
|
|
2644
|
+
resolveAlignment,
|
|
2645
|
+
resolveIndent
|
|
2646
|
+
};
|
|
2647
|
+
function isLexicalContent(value) {
|
|
2648
|
+
if (typeof value !== "object" || value === null) return false;
|
|
2649
|
+
if (!("root" in value)) return false;
|
|
2650
|
+
const root = value.root;
|
|
2651
|
+
if (typeof root !== "object" || root === null) return false;
|
|
2652
|
+
if (!("children" in root)) return false;
|
|
2653
|
+
return Array.isArray(root.children);
|
|
2654
|
+
}
|
|
2655
|
+
function lexicalToHtml(content, options = {}) {
|
|
2656
|
+
if (!isLexicalContent(content)) return "";
|
|
2657
|
+
const html = renderChildren(content.root.children);
|
|
2658
|
+
if (options.sanitize === false) return html;
|
|
2659
|
+
if (!hasSanitizerDocument()) return html;
|
|
2660
|
+
return sanitizeHtml(html);
|
|
2661
|
+
}
|
|
2662
|
+
function lexicalToPlainText(content) {
|
|
2663
|
+
if (!isLexicalContent(content)) return "";
|
|
2664
|
+
return content.root.children.map(extractPlainText).join("\n").trim();
|
|
2665
|
+
}
|
|
2666
|
+
function renderChildren(children) {
|
|
2667
|
+
let out = "";
|
|
2668
|
+
for (const child of children) out += renderNode(child);
|
|
2669
|
+
return out;
|
|
2670
|
+
}
|
|
2671
|
+
function renderNode(node) {
|
|
2672
|
+
const renderer = lookup(node.type);
|
|
2673
|
+
if (renderer) return renderer(node, RENDER_CONTEXT);
|
|
2674
|
+
return node.children !== void 0 ? renderChildren(node.children) : "";
|
|
2675
|
+
}
|
|
2676
|
+
function extractPlainText(node) {
|
|
2677
|
+
if (typeof node.text === "string") return node.text;
|
|
2678
|
+
if (node.type === "linebreak") return "\n";
|
|
2679
|
+
if (node.children === void 0) return "";
|
|
2680
|
+
let out = "";
|
|
2681
|
+
for (const child of node.children) out += extractPlainText(child);
|
|
2682
|
+
return out;
|
|
2683
|
+
}
|
|
2684
|
+
|
|
2685
|
+
// src/field-types/registry.ts
|
|
2686
|
+
var builtinRenderers = /* @__PURE__ */ new Map();
|
|
2687
|
+
function buildBuiltinRenderers(extras = []) {
|
|
2688
|
+
for (const renderer of extras) builtinRenderers.set(renderer.name, renderer);
|
|
2689
|
+
const out = {};
|
|
2690
|
+
for (const [type, renderer] of builtinRenderers) out[type] = renderer;
|
|
2691
|
+
return Object.freeze(out);
|
|
2692
|
+
}
|
|
2693
|
+
function registerBuiltinRenderer(renderer) {
|
|
2694
|
+
builtinRenderers.set(renderer.name, renderer);
|
|
2695
|
+
}
|
|
2696
|
+
|
|
2697
|
+
// src/field-types/text.ts
|
|
2698
|
+
var TEXT_OPT_IN_ATTRIBUTE = "data-payload-text";
|
|
2699
|
+
var warnedElements = /* @__PURE__ */ new WeakSet();
|
|
2700
|
+
var textRenderer2 = {
|
|
2701
|
+
name: "text",
|
|
2702
|
+
render(target, value) {
|
|
2703
|
+
const element = target.element;
|
|
2704
|
+
const text = toPlainString(value);
|
|
2705
|
+
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
|
|
2706
|
+
element.value = text;
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
2709
|
+
if (hasStructuredChildren(element) && !element.hasAttribute(TEXT_OPT_IN_ATTRIBUTE)) {
|
|
2710
|
+
warnOnce(element, target.fieldName);
|
|
2711
|
+
return;
|
|
2712
|
+
}
|
|
2713
|
+
if (text.includes("\n") || text.includes("\r")) {
|
|
2714
|
+
element.innerHTML = escapeAndLinebreak(text);
|
|
2715
|
+
return;
|
|
2716
|
+
}
|
|
2717
|
+
element.textContent = text;
|
|
2718
|
+
}
|
|
2719
|
+
};
|
|
2720
|
+
function hasStructuredChildren(element) {
|
|
2721
|
+
return element.firstElementChild !== null;
|
|
2722
|
+
}
|
|
2723
|
+
function warnOnce(element, fieldName) {
|
|
2724
|
+
if (warnedElements.has(element)) return;
|
|
2725
|
+
warnedElements.add(element);
|
|
2726
|
+
console.warn(
|
|
2727
|
+
`[live-preview] Skipping text update for "${fieldName}" \u2014 the bound <${element.tagName.toLowerCase()}> has structured child elements. Either move data-payload-field to the element whose textContent is the field value, or opt in with data-payload-text to replace the structure.`
|
|
2728
|
+
);
|
|
2729
|
+
}
|
|
2730
|
+
function toPlainString(value) {
|
|
2731
|
+
if (value === null || value === void 0) return "";
|
|
2732
|
+
if (isLexicalContent(value)) return lexicalToPlainText(value);
|
|
2733
|
+
if (typeof value === "string") return value;
|
|
2734
|
+
if (typeof value === "number" || typeof value === "boolean") return String(value);
|
|
2735
|
+
return JSON.stringify(value);
|
|
2736
|
+
}
|
|
2737
|
+
registerBuiltinRenderer(textRenderer2);
|
|
2738
|
+
|
|
2739
|
+
// src/field-types/utils.ts
|
|
2740
|
+
function safeStringify(value) {
|
|
2741
|
+
if (value === null || value === void 0) return "";
|
|
2742
|
+
if (typeof value === "string") return value;
|
|
2743
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
2744
|
+
return String(value);
|
|
2745
|
+
}
|
|
2746
|
+
try {
|
|
2747
|
+
return JSON.stringify(value);
|
|
2748
|
+
} catch {
|
|
2749
|
+
return "";
|
|
2750
|
+
}
|
|
2751
|
+
}
|
|
2752
|
+
|
|
2753
|
+
// src/field-types/textarea.ts
|
|
2754
|
+
var textareaRenderer = {
|
|
2755
|
+
name: "textarea",
|
|
2756
|
+
render(target, value) {
|
|
2757
|
+
const element = target.element;
|
|
2758
|
+
const text = isLexicalContent(value) ? lexicalToPlainText(value) : safeStringify(value);
|
|
2759
|
+
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
|
|
2760
|
+
element.value = text;
|
|
2761
|
+
return;
|
|
2762
|
+
}
|
|
2763
|
+
element.innerHTML = escapeAndLinebreak(text);
|
|
2764
|
+
}
|
|
2765
|
+
};
|
|
2766
|
+
registerBuiltinRenderer(textareaRenderer);
|
|
2767
|
+
|
|
2768
|
+
// src/field-types/rich-text.ts
|
|
2769
|
+
var richTextRenderer = {
|
|
2770
|
+
name: "richText",
|
|
2771
|
+
render(target, value) {
|
|
2772
|
+
if (isLexicalContent(value)) {
|
|
2773
|
+
target.element.innerHTML = lexicalToHtml(value);
|
|
2774
|
+
return;
|
|
2775
|
+
}
|
|
2776
|
+
if (typeof value === "string") {
|
|
2777
|
+
target.element.innerHTML = sanitizeHtml(value);
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2780
|
+
};
|
|
2781
|
+
registerBuiltinRenderer(richTextRenderer);
|
|
2782
|
+
|
|
2783
|
+
// src/field-types/html.ts
|
|
2784
|
+
var htmlRenderer = {
|
|
2785
|
+
name: "html",
|
|
2786
|
+
render(target, value) {
|
|
2787
|
+
if (value === null || value === void 0) {
|
|
2788
|
+
target.element.textContent = "";
|
|
2789
|
+
return;
|
|
2790
|
+
}
|
|
2791
|
+
const html = typeof value === "string" ? value : safeStringify(value);
|
|
2792
|
+
target.element.innerHTML = sanitizeHtml(html);
|
|
2793
|
+
}
|
|
2794
|
+
};
|
|
2795
|
+
registerBuiltinRenderer(htmlRenderer);
|
|
2796
|
+
|
|
2797
|
+
// src/field-types/url.ts
|
|
2798
|
+
var urlRenderer = {
|
|
2799
|
+
name: "url",
|
|
2800
|
+
render(target, value, context) {
|
|
2801
|
+
const element = target.element;
|
|
2802
|
+
const text = safeStringify(value);
|
|
2803
|
+
if (element.tagName === "A") {
|
|
2804
|
+
const anchor = element;
|
|
2805
|
+
const hrefField = target.hrefField;
|
|
2806
|
+
const hrefSource = hrefField ? context.allFields[hrefField] : value;
|
|
2807
|
+
if (typeof hrefSource === "string" && isSafeUrl(hrefSource)) {
|
|
2808
|
+
anchor.href = hrefSource;
|
|
2809
|
+
}
|
|
2810
|
+
anchor.textContent = text;
|
|
2811
|
+
return;
|
|
2812
|
+
}
|
|
2813
|
+
if (element.tagName === "INPUT" || element.tagName === "TEXTAREA") {
|
|
2814
|
+
element.value = text;
|
|
2815
|
+
return;
|
|
2816
|
+
}
|
|
2817
|
+
element.textContent = text;
|
|
2818
|
+
}
|
|
2819
|
+
};
|
|
2820
|
+
registerBuiltinRenderer(urlRenderer);
|
|
2821
|
+
registerBuiltinRenderer({ ...urlRenderer, name: "email" });
|
|
2822
|
+
|
|
2823
|
+
// src/field-types/image.ts
|
|
2824
|
+
var imageRenderer = {
|
|
2825
|
+
name: "image",
|
|
2826
|
+
render(target, value, context) {
|
|
2827
|
+
const element = target.element;
|
|
2828
|
+
const media = readMedia(value);
|
|
2829
|
+
const url = pickUrl(media, value);
|
|
2830
|
+
if (url === void 0) return;
|
|
2831
|
+
if (element.tagName === "IMG") {
|
|
2832
|
+
const img = element;
|
|
2833
|
+
img.src = url;
|
|
2834
|
+
const alt = pickAlt(media, target.altField, context.allFields);
|
|
2835
|
+
if (alt !== void 0) img.alt = alt;
|
|
2836
|
+
return;
|
|
2837
|
+
}
|
|
2838
|
+
element.style.backgroundImage = `url('${escapeCssUrl(url)}')`;
|
|
2839
|
+
}
|
|
2840
|
+
};
|
|
2841
|
+
function readMedia(value) {
|
|
2842
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2843
|
+
return value;
|
|
2844
|
+
}
|
|
2845
|
+
function pickUrl(media, value) {
|
|
2846
|
+
if (media?.url !== void 0 && isSafeUrl(media.url)) return media.url;
|
|
2847
|
+
if (typeof value === "string" && isSafeUrl(value)) return value;
|
|
2848
|
+
return void 0;
|
|
2849
|
+
}
|
|
2850
|
+
function pickAlt(media, altField, allFields) {
|
|
2851
|
+
if (media?.alt !== void 0) return media.alt;
|
|
2852
|
+
if (altField !== void 0) {
|
|
2853
|
+
const sibling = allFields[altField];
|
|
2854
|
+
if (typeof sibling === "string") return sibling;
|
|
2855
|
+
}
|
|
2856
|
+
return void 0;
|
|
2857
|
+
}
|
|
2858
|
+
registerBuiltinRenderer(imageRenderer);
|
|
2859
|
+
|
|
2860
|
+
// src/field-types/upload.ts
|
|
2861
|
+
var uploadRenderer2 = {
|
|
2862
|
+
name: "upload",
|
|
2863
|
+
render(target, value) {
|
|
2864
|
+
const element = target.element;
|
|
2865
|
+
const media = readMedia2(value);
|
|
2866
|
+
if (media === void 0) return;
|
|
2867
|
+
const url = media.url;
|
|
2868
|
+
if (typeof url !== "string" || !isSafeUrl(url)) return;
|
|
2869
|
+
if (element.tagName === "IMG") {
|
|
2870
|
+
const img = element;
|
|
2871
|
+
img.src = url;
|
|
2872
|
+
if (media.alt !== void 0) img.alt = media.alt;
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
if (element.tagName === "A") {
|
|
2876
|
+
const anchor = element;
|
|
2877
|
+
anchor.href = url;
|
|
2878
|
+
anchor.textContent = media.filename ?? url;
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
const label = media.filename !== void 0 ? escapeHtml(media.filename) : escapeHtml(url);
|
|
2882
|
+
element.innerHTML = `<a href="${escapeHtmlAttribute(url)}">${label}</a>`;
|
|
2883
|
+
}
|
|
2884
|
+
};
|
|
2885
|
+
function readMedia2(value) {
|
|
2886
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
2887
|
+
return value;
|
|
2888
|
+
}
|
|
2889
|
+
registerBuiltinRenderer(uploadRenderer2);
|
|
2890
|
+
|
|
2891
|
+
// src/field-types/relationship.ts
|
|
2892
|
+
var relationshipRenderer2 = {
|
|
2893
|
+
name: "relationship",
|
|
2894
|
+
render(target, value) {
|
|
2895
|
+
const element = target.element;
|
|
2896
|
+
const labels = collectLabels(value);
|
|
2897
|
+
const text = labels.join(", ");
|
|
2898
|
+
if (element.tagName === "A" && !Array.isArray(value)) {
|
|
2899
|
+
const anchor = element;
|
|
2900
|
+
const url = value?.url;
|
|
2901
|
+
if (typeof url === "string" && isSafeUrl(url)) anchor.href = url;
|
|
2902
|
+
anchor.textContent = text;
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
element.textContent = text;
|
|
2906
|
+
}
|
|
2907
|
+
};
|
|
2908
|
+
function collectLabels(value) {
|
|
2909
|
+
if (value === null || value === void 0) return [""];
|
|
2910
|
+
if (Array.isArray(value)) return value.map(pickLabel2);
|
|
2911
|
+
return [pickLabel2(value)];
|
|
2912
|
+
}
|
|
2913
|
+
function pickLabel2(value) {
|
|
2914
|
+
if (typeof value === "object" && value !== null) {
|
|
2915
|
+
const rel = value;
|
|
2916
|
+
if (typeof rel.title === "string" && rel.title.length > 0) return rel.title;
|
|
2917
|
+
if (typeof rel.name === "string" && rel.name.length > 0) return rel.name;
|
|
2918
|
+
if (typeof rel.slug === "string" && rel.slug.length > 0) return rel.slug;
|
|
2919
|
+
if (rel.id !== void 0) return String(rel.id);
|
|
2920
|
+
return "";
|
|
2921
|
+
}
|
|
2922
|
+
return safeStringify(value);
|
|
2923
|
+
}
|
|
2924
|
+
registerBuiltinRenderer(relationshipRenderer2);
|
|
2925
|
+
|
|
2926
|
+
// src/field-types/select.ts
|
|
2927
|
+
var selectRenderer = {
|
|
2928
|
+
name: "select",
|
|
2929
|
+
render(target, value) {
|
|
2930
|
+
const element = target.element;
|
|
2931
|
+
const text = stringify(value);
|
|
2932
|
+
if (element.tagName === "SELECT") {
|
|
2933
|
+
element.value = text;
|
|
2934
|
+
return;
|
|
2935
|
+
}
|
|
2936
|
+
if (element.tagName === "INPUT" && element.type === "radio") {
|
|
2937
|
+
const radio = element;
|
|
2938
|
+
radio.checked = radio.value === text;
|
|
2939
|
+
return;
|
|
2940
|
+
}
|
|
2941
|
+
element.textContent = text;
|
|
2942
|
+
}
|
|
2943
|
+
};
|
|
2944
|
+
function stringify(value) {
|
|
2945
|
+
if (Array.isArray(value)) return value.map((v) => stringify(v)).join(", ");
|
|
2946
|
+
return safeStringify(value);
|
|
2947
|
+
}
|
|
2948
|
+
registerBuiltinRenderer(selectRenderer);
|
|
2949
|
+
registerBuiltinRenderer({ ...selectRenderer, name: "radio" });
|
|
2950
|
+
|
|
2951
|
+
// src/field-types/checkbox.ts
|
|
2952
|
+
var checkboxRenderer = {
|
|
2953
|
+
name: "checkbox",
|
|
2954
|
+
render(target, value) {
|
|
2955
|
+
const element = target.element;
|
|
2956
|
+
const checked = Boolean(value);
|
|
2957
|
+
if (element.tagName === "INPUT" && element.type === "checkbox") {
|
|
2958
|
+
element.checked = checked;
|
|
2959
|
+
return;
|
|
2960
|
+
}
|
|
2961
|
+
if (element.hasAttribute("aria-checked")) {
|
|
2962
|
+
element.setAttribute("aria-checked", checked ? "true" : "false");
|
|
2963
|
+
return;
|
|
2964
|
+
}
|
|
2965
|
+
element.textContent = checked ? "true" : "false";
|
|
2966
|
+
}
|
|
2967
|
+
};
|
|
2968
|
+
registerBuiltinRenderer(checkboxRenderer);
|
|
2969
|
+
|
|
2970
|
+
// src/core/intl-cache.ts
|
|
2971
|
+
var NUMBER_CACHE = /* @__PURE__ */ new Map();
|
|
2972
|
+
var DATE_CACHE = /* @__PURE__ */ new Map();
|
|
2973
|
+
var DEFAULT_MAX_ENTRIES = 64;
|
|
2974
|
+
var maxEntries = DEFAULT_MAX_ENTRIES;
|
|
2975
|
+
function getNumberFormat(locale, options) {
|
|
2976
|
+
const key = buildKey(locale, options);
|
|
2977
|
+
const cached = NUMBER_CACHE.get(key);
|
|
2978
|
+
if (cached !== void 0) {
|
|
2979
|
+
NUMBER_CACHE.delete(key);
|
|
2980
|
+
NUMBER_CACHE.set(key, cached);
|
|
2981
|
+
return cached;
|
|
2982
|
+
}
|
|
2983
|
+
const formatter = new Intl.NumberFormat(locale, options);
|
|
2984
|
+
NUMBER_CACHE.set(key, formatter);
|
|
2985
|
+
trimToLimit(NUMBER_CACHE);
|
|
2986
|
+
return formatter;
|
|
2987
|
+
}
|
|
2988
|
+
function getDateTimeFormat(locale, options) {
|
|
2989
|
+
const key = buildKey(locale, options);
|
|
2990
|
+
const cached = DATE_CACHE.get(key);
|
|
2991
|
+
if (cached !== void 0) {
|
|
2992
|
+
DATE_CACHE.delete(key);
|
|
2993
|
+
DATE_CACHE.set(key, cached);
|
|
2994
|
+
return cached;
|
|
2995
|
+
}
|
|
2996
|
+
const formatter = new Intl.DateTimeFormat(locale, options);
|
|
2997
|
+
DATE_CACHE.set(key, formatter);
|
|
2998
|
+
trimToLimit(DATE_CACHE);
|
|
2999
|
+
return formatter;
|
|
3000
|
+
}
|
|
3001
|
+
function buildKey(locale, options) {
|
|
3002
|
+
const localePart = locale ?? "";
|
|
3003
|
+
if (options === void 0 || options === null) return localePart;
|
|
3004
|
+
const serialised = stableStringify(options);
|
|
3005
|
+
if (serialised === "{}") return localePart;
|
|
3006
|
+
return `${localePart}|${serialised}`;
|
|
3007
|
+
}
|
|
3008
|
+
function stableStringify(value) {
|
|
3009
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
3010
|
+
if (Array.isArray(value)) {
|
|
3011
|
+
return `[${value.map((v) => stableStringify(v)).join(",")}]`;
|
|
3012
|
+
}
|
|
3013
|
+
const record = value;
|
|
3014
|
+
const keys = Object.keys(record).sort();
|
|
3015
|
+
const parts = [];
|
|
3016
|
+
for (const key of keys) {
|
|
3017
|
+
const inner = record[key];
|
|
3018
|
+
if (inner === void 0) continue;
|
|
3019
|
+
parts.push(`${JSON.stringify(key)}:${stableStringify(inner)}`);
|
|
3020
|
+
}
|
|
3021
|
+
return `{${parts.join(",")}}`;
|
|
3022
|
+
}
|
|
3023
|
+
function trimToLimit(map) {
|
|
3024
|
+
while (map.size > maxEntries) {
|
|
3025
|
+
const oldest = map.keys().next().value;
|
|
3026
|
+
if (oldest === void 0) break;
|
|
3027
|
+
map.delete(oldest);
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
|
|
3031
|
+
// src/field-types/date.ts
|
|
3032
|
+
var dateRenderer = {
|
|
3033
|
+
name: "date",
|
|
3034
|
+
render(target, value, context) {
|
|
3035
|
+
const element = target.element;
|
|
3036
|
+
if (value === null || value === void 0 || value === "") {
|
|
3037
|
+
if (element.tagName === "INPUT") {
|
|
3038
|
+
element.value = "";
|
|
3039
|
+
} else if (element.tagName === "TIME") {
|
|
3040
|
+
element.removeAttribute("datetime");
|
|
3041
|
+
element.textContent = "";
|
|
3042
|
+
} else {
|
|
3043
|
+
element.textContent = "";
|
|
3044
|
+
}
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
const iso = typeof value === "string" ? value : safeStringify(value);
|
|
3048
|
+
const date = new Date(iso);
|
|
3049
|
+
const isValid = !Number.isNaN(date.getTime());
|
|
3050
|
+
if (element.tagName === "INPUT") {
|
|
3051
|
+
element.value = isValid ? toIsoForInput(element.type, date) : iso;
|
|
3052
|
+
return;
|
|
3053
|
+
}
|
|
3054
|
+
if (element.tagName === "TIME") {
|
|
3055
|
+
element.setAttribute("datetime", isValid ? date.toISOString() : iso);
|
|
3056
|
+
element.textContent = isValid ? formatDate(date, context) : iso;
|
|
3057
|
+
return;
|
|
3058
|
+
}
|
|
3059
|
+
element.textContent = isValid ? formatDate(date, context) : iso;
|
|
3060
|
+
}
|
|
3061
|
+
};
|
|
3062
|
+
function formatDate(date, context) {
|
|
3063
|
+
const locale = context.locale ?? detectInitialLocale();
|
|
3064
|
+
try {
|
|
3065
|
+
return getDateTimeFormat(locale, { dateStyle: "medium", timeStyle: "short" }).format(date);
|
|
3066
|
+
} catch {
|
|
3067
|
+
return date.toISOString();
|
|
3068
|
+
}
|
|
3069
|
+
}
|
|
3070
|
+
function toIsoForInput(type, date) {
|
|
3071
|
+
if (type === "date") return date.toISOString().slice(0, 10);
|
|
3072
|
+
if (type === "datetime-local") return date.toISOString().slice(0, 16);
|
|
3073
|
+
return date.toISOString();
|
|
3074
|
+
}
|
|
3075
|
+
registerBuiltinRenderer(dateRenderer);
|
|
3076
|
+
|
|
3077
|
+
// src/field-types/number.ts
|
|
3078
|
+
var numberRenderer = {
|
|
3079
|
+
name: "number",
|
|
3080
|
+
render(target, value, context) {
|
|
3081
|
+
const element = target.element;
|
|
3082
|
+
if (value === null || value === void 0 || value === "") {
|
|
3083
|
+
if (element.tagName === "INPUT") element.value = "";
|
|
3084
|
+
else element.textContent = "";
|
|
3085
|
+
return;
|
|
3086
|
+
}
|
|
3087
|
+
const num = typeof value === "number" ? value : Number(safeStringify(value));
|
|
3088
|
+
if (Number.isNaN(num)) {
|
|
3089
|
+
element.textContent = safeStringify(value);
|
|
3090
|
+
return;
|
|
3091
|
+
}
|
|
3092
|
+
if (element.tagName === "INPUT") {
|
|
3093
|
+
element.value = String(num);
|
|
3094
|
+
return;
|
|
3095
|
+
}
|
|
3096
|
+
element.textContent = format(num, context);
|
|
3097
|
+
}
|
|
3098
|
+
};
|
|
3099
|
+
function format(num, context) {
|
|
3100
|
+
const locale = context.locale ?? detectInitialLocale();
|
|
3101
|
+
try {
|
|
3102
|
+
return getNumberFormat(locale).format(num);
|
|
3103
|
+
} catch {
|
|
3104
|
+
return String(num);
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
registerBuiltinRenderer(numberRenderer);
|
|
3108
|
+
|
|
3109
|
+
// src/field-types/array.ts
|
|
3110
|
+
var arrayRenderer = {
|
|
3111
|
+
name: "array",
|
|
3112
|
+
render(target, value) {
|
|
3113
|
+
const element = target.element;
|
|
3114
|
+
if (!Array.isArray(value)) return;
|
|
3115
|
+
const template = target.arrayTemplate;
|
|
3116
|
+
if (template !== void 0 && template.length > 0) {
|
|
3117
|
+
const html = renderTemplate(template, value);
|
|
3118
|
+
element.innerHTML = sanitizeHtml(html);
|
|
3119
|
+
return;
|
|
3120
|
+
}
|
|
3121
|
+
const separator = target.arraySeparator ?? ", ";
|
|
3122
|
+
element.textContent = value.map(stringify2).join(separator);
|
|
3123
|
+
}
|
|
3124
|
+
};
|
|
3125
|
+
function renderTemplate(template, items) {
|
|
3126
|
+
let out = "";
|
|
3127
|
+
for (let i = 0; i < items.length; i += 1) {
|
|
3128
|
+
out += interpolate(template, items[i], i);
|
|
3129
|
+
}
|
|
3130
|
+
return out;
|
|
3131
|
+
}
|
|
3132
|
+
function interpolate(template, item, index) {
|
|
3133
|
+
let out = template;
|
|
3134
|
+
if (typeof item === "object" && item !== null) {
|
|
3135
|
+
for (const [key, value] of Object.entries(item)) {
|
|
3136
|
+
const pattern = new RegExp(`\\{\\{${escapeRegex(key)}\\}\\}`, "g");
|
|
3137
|
+
out = out.replace(pattern, escapeHtml(safeStringify(value)));
|
|
3138
|
+
}
|
|
3139
|
+
} else {
|
|
3140
|
+
out = out.replace(/\{\{value\}\}/g, escapeHtml(safeStringify(item)));
|
|
3141
|
+
}
|
|
3142
|
+
out = out.replace(/\{\{index\}\}/g, String(index));
|
|
3143
|
+
return out;
|
|
3144
|
+
}
|
|
3145
|
+
function escapeRegex(value) {
|
|
3146
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3147
|
+
}
|
|
3148
|
+
function stringify2(value) {
|
|
3149
|
+
return safeStringify(value);
|
|
3150
|
+
}
|
|
3151
|
+
registerBuiltinRenderer(arrayRenderer);
|
|
3152
|
+
registerBuiltinRenderer({ ...arrayRenderer, name: "blocks" });
|
|
3153
|
+
|
|
3154
|
+
// src/core/view-transitions.ts
|
|
3155
|
+
function viewTransitionsSupported() {
|
|
3156
|
+
if (typeof document === "undefined") return false;
|
|
3157
|
+
return typeof document.startViewTransition === "function";
|
|
3158
|
+
}
|
|
3159
|
+
async function runWithTransition(callback) {
|
|
3160
|
+
if (!viewTransitionsSupported()) {
|
|
3161
|
+
callback();
|
|
3162
|
+
return;
|
|
3163
|
+
}
|
|
3164
|
+
const start = document.startViewTransition;
|
|
3165
|
+
const transition = start.call(document, callback);
|
|
3166
|
+
try {
|
|
3167
|
+
await transition.finished;
|
|
3168
|
+
} catch {
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3171
|
+
|
|
3172
|
+
// src/core/structural-applier.ts
|
|
3173
|
+
var KEY_ATTRIBUTE = "data-payload-key";
|
|
3174
|
+
var NESTED_KEY_ATTRIBUTE = "data-payload-nested-key";
|
|
3175
|
+
var NESTED_TEMPLATE_ATTRIBUTE = "data-payload-nested-template";
|
|
3176
|
+
var previousItemValues = /* @__PURE__ */ new WeakMap();
|
|
3177
|
+
function applyStructuralPatches(options) {
|
|
3178
|
+
const { template, container, patches } = options;
|
|
3179
|
+
const memory = getMemory(container);
|
|
3180
|
+
const removes = [];
|
|
3181
|
+
const inserts = [];
|
|
3182
|
+
const moves = [];
|
|
3183
|
+
const updates = [];
|
|
3184
|
+
const replaces = [];
|
|
3185
|
+
for (const patch of patches) {
|
|
3186
|
+
if (patch.kind === "remove") removes.push(patch);
|
|
3187
|
+
else if (patch.kind === "insert") inserts.push(patch);
|
|
3188
|
+
else if (patch.kind === "move") moves.push(patch);
|
|
3189
|
+
else if (patch.kind === "update") updates.push(patch);
|
|
3190
|
+
else replaces.push(patch);
|
|
3191
|
+
}
|
|
3192
|
+
removes.sort((a, b) => indexOf(b) - indexOf(a));
|
|
3193
|
+
for (const patch of removes) {
|
|
3194
|
+
if (patch.kind !== "remove") continue;
|
|
3195
|
+
const node = container.children[patch.index];
|
|
3196
|
+
node?.remove();
|
|
3197
|
+
forgetItem(memory, patch.value);
|
|
3198
|
+
}
|
|
3199
|
+
for (const patch of replaces) {
|
|
3200
|
+
if (patch.kind !== "replace") continue;
|
|
3201
|
+
const node = container.children[patch.index];
|
|
3202
|
+
const replacement = renderItem(template, patch.value, patch.index);
|
|
3203
|
+
if (!node || !replacement) continue;
|
|
3204
|
+
populateNestedSlots(replacement, patch.value);
|
|
3205
|
+
container.replaceChild(replacement, node);
|
|
3206
|
+
rememberItem(memory, patch.value);
|
|
3207
|
+
}
|
|
3208
|
+
for (const patch of updates) {
|
|
3209
|
+
if (patch.kind !== "update") continue;
|
|
3210
|
+
const oldNode = container.children[patch.index];
|
|
3211
|
+
const newNode = renderItem(template, patch.value, patch.index);
|
|
3212
|
+
if (!oldNode || !newNode) continue;
|
|
3213
|
+
reconcileNestedSlots(oldNode, newNode, patch.value, memory);
|
|
3214
|
+
container.replaceChild(newNode, oldNode);
|
|
3215
|
+
rememberItem(memory, patch.value);
|
|
3216
|
+
}
|
|
3217
|
+
inserts.sort((a, b) => indexOf(a) - indexOf(b));
|
|
3218
|
+
for (const patch of inserts) {
|
|
3219
|
+
if (patch.kind !== "insert") continue;
|
|
3220
|
+
const before = container.children[patch.index] ?? null;
|
|
3221
|
+
const node = renderItem(template, patch.value, patch.index);
|
|
3222
|
+
if (!node) continue;
|
|
3223
|
+
populateNestedSlots(node, patch.value);
|
|
3224
|
+
container.insertBefore(node, before);
|
|
3225
|
+
rememberItem(memory, patch.value);
|
|
3226
|
+
}
|
|
3227
|
+
for (const patch of moves) {
|
|
3228
|
+
if (patch.kind !== "move") continue;
|
|
3229
|
+
const key = readKey(patch.value);
|
|
3230
|
+
const node = key !== void 0 ? findByKey(container, key) : null;
|
|
3231
|
+
if (!node) continue;
|
|
3232
|
+
const before = container.children[patch.to] ?? null;
|
|
3233
|
+
if (node === before) continue;
|
|
3234
|
+
container.insertBefore(node, before);
|
|
3235
|
+
}
|
|
3236
|
+
}
|
|
3237
|
+
function populateNestedSlots(item, value) {
|
|
3238
|
+
const slots = item.querySelectorAll(`[${NESTED_KEY_ATTRIBUTE}]`);
|
|
3239
|
+
for (const slot of Array.from(slots)) {
|
|
3240
|
+
const key = slot.getAttribute(NESTED_KEY_ATTRIBUTE);
|
|
3241
|
+
if (key === null) continue;
|
|
3242
|
+
const nestedTemplate = slot.getAttribute(NESTED_TEMPLATE_ATTRIBUTE);
|
|
3243
|
+
if (!nestedTemplate) continue;
|
|
3244
|
+
const nestedItems = readNestedArray(value, key);
|
|
3245
|
+
if (nestedItems === void 0) continue;
|
|
3246
|
+
const patches = diffArray([], nestedItems);
|
|
3247
|
+
if (patches.length === 0) continue;
|
|
3248
|
+
applyStructuralPatches({
|
|
3249
|
+
template: nestedTemplate,
|
|
3250
|
+
container: slot,
|
|
3251
|
+
patches});
|
|
3252
|
+
}
|
|
3253
|
+
}
|
|
3254
|
+
function reconcileNestedSlots(oldItem, newItem, nextValue, memory) {
|
|
3255
|
+
const oldSlots = oldItem.querySelectorAll(`[${NESTED_KEY_ATTRIBUTE}]`);
|
|
3256
|
+
if (oldSlots.length === 0) return;
|
|
3257
|
+
const itemKey = readKey(nextValue);
|
|
3258
|
+
const prevValue = itemKey !== void 0 ? memory.get(itemKey) : void 0;
|
|
3259
|
+
for (const oldSlot of Array.from(oldSlots)) {
|
|
3260
|
+
const key = oldSlot.getAttribute(NESTED_KEY_ATTRIBUTE);
|
|
3261
|
+
if (key === null) continue;
|
|
3262
|
+
const newSlot = newItem.querySelector(`[${NESTED_KEY_ATTRIBUTE}="${cssEscape(key)}"]`);
|
|
3263
|
+
if (!newSlot) continue;
|
|
3264
|
+
newSlot.replaceWith(oldSlot);
|
|
3265
|
+
const nestedTemplate = oldSlot.getAttribute(NESTED_TEMPLATE_ATTRIBUTE) ?? newSlot.getAttribute(NESTED_TEMPLATE_ATTRIBUTE);
|
|
3266
|
+
if (!nestedTemplate) continue;
|
|
3267
|
+
const nextNested = readNestedArray(nextValue, key) ?? [];
|
|
3268
|
+
const prevNested = readNestedArray(prevValue, key) ?? [];
|
|
3269
|
+
const patches = diffArray(prevNested, nextNested);
|
|
3270
|
+
if (patches.length === 0) continue;
|
|
3271
|
+
applyStructuralPatches({
|
|
3272
|
+
template: nestedTemplate,
|
|
3273
|
+
container: oldSlot,
|
|
3274
|
+
patches});
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
function readNestedArray(value, key) {
|
|
3278
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
3279
|
+
const nested = value[key];
|
|
3280
|
+
if (!Array.isArray(nested)) return void 0;
|
|
3281
|
+
return nested;
|
|
3282
|
+
}
|
|
3283
|
+
function getMemory(container) {
|
|
3284
|
+
let map = previousItemValues.get(container);
|
|
3285
|
+
if (!map) {
|
|
3286
|
+
map = /* @__PURE__ */ new Map();
|
|
3287
|
+
previousItemValues.set(container, map);
|
|
3288
|
+
}
|
|
3289
|
+
return map;
|
|
3290
|
+
}
|
|
3291
|
+
function rememberItem(memory, value) {
|
|
3292
|
+
const key = readKey(value);
|
|
3293
|
+
if (key === void 0) return;
|
|
3294
|
+
memory.set(key, value);
|
|
3295
|
+
}
|
|
3296
|
+
function forgetItem(memory, value) {
|
|
3297
|
+
const key = readKey(value);
|
|
3298
|
+
if (key === void 0) return;
|
|
3299
|
+
memory.delete(key);
|
|
3300
|
+
}
|
|
3301
|
+
function indexOf(patch) {
|
|
3302
|
+
if (patch.kind === "move") return patch.to;
|
|
3303
|
+
return patch.index;
|
|
3304
|
+
}
|
|
3305
|
+
function readKey(value) {
|
|
3306
|
+
if (typeof value !== "object" || value === null) return void 0;
|
|
3307
|
+
const id = value["id"];
|
|
3308
|
+
if (typeof id === "string" || typeof id === "number") return String(id);
|
|
3309
|
+
return void 0;
|
|
3310
|
+
}
|
|
3311
|
+
function findByKey(container, key) {
|
|
3312
|
+
return container.querySelector(`[${KEY_ATTRIBUTE}="${cssEscape(key)}"]`);
|
|
3313
|
+
}
|
|
3314
|
+
function cssEscape(value) {
|
|
3315
|
+
const css = globalThis.CSS;
|
|
3316
|
+
if (css?.escape) return css.escape(value);
|
|
3317
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
3318
|
+
}
|
|
3319
|
+
function renderItem(template, value, index) {
|
|
3320
|
+
if (typeof document === "undefined") return null;
|
|
3321
|
+
const filled = fillTemplate(template, value, index);
|
|
3322
|
+
const safe = sanitizeHtml(filled);
|
|
3323
|
+
const host = document.createElement("template");
|
|
3324
|
+
host.innerHTML = safe;
|
|
3325
|
+
const first = host.content.firstElementChild;
|
|
3326
|
+
if (!first) return null;
|
|
3327
|
+
const key = readKey(value);
|
|
3328
|
+
if (key !== void 0) first.setAttribute(KEY_ATTRIBUTE, key);
|
|
3329
|
+
return first;
|
|
3330
|
+
}
|
|
3331
|
+
function fillTemplate(template, value, index) {
|
|
3332
|
+
let out = template;
|
|
3333
|
+
if (typeof value === "object" && value !== null) {
|
|
3334
|
+
for (const [key, raw] of Object.entries(value)) {
|
|
3335
|
+
const pattern = new RegExp(`\\{\\{${escapeRegex2(key)}\\}\\}`, "g");
|
|
3336
|
+
const replacement = escapeHtml(stringifyForTemplate(raw));
|
|
3337
|
+
out = out.replace(pattern, () => replacement);
|
|
3338
|
+
}
|
|
3339
|
+
} else {
|
|
3340
|
+
const replacement = escapeHtml(stringifyForTemplate(value));
|
|
3341
|
+
out = out.replace(/\{\{value\}\}/g, () => replacement);
|
|
3342
|
+
}
|
|
3343
|
+
return out.replace(/\{\{index\}\}/g, String(index));
|
|
3344
|
+
}
|
|
3345
|
+
function stringifyForTemplate(value) {
|
|
3346
|
+
if (value === null || value === void 0) return "";
|
|
3347
|
+
if (typeof value === "string") return value;
|
|
3348
|
+
if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
|
|
3349
|
+
return String(value);
|
|
3350
|
+
}
|
|
3351
|
+
return JSON.stringify(value);
|
|
3352
|
+
}
|
|
3353
|
+
function escapeRegex2(value) {
|
|
3354
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
3355
|
+
}
|
|
3356
|
+
|
|
3357
|
+
// src/field-types/structural-array.ts
|
|
3358
|
+
var previousValues = /* @__PURE__ */ new WeakMap();
|
|
3359
|
+
var warnedContainers = /* @__PURE__ */ new WeakSet();
|
|
3360
|
+
var structuralArrayRenderer = {
|
|
3361
|
+
name: "structural-array",
|
|
3362
|
+
render(target, value) {
|
|
3363
|
+
if (!Array.isArray(value)) return;
|
|
3364
|
+
const container = target.element;
|
|
3365
|
+
const template = target.arrayTemplate;
|
|
3366
|
+
if (template === void 0 || template.length === 0) {
|
|
3367
|
+
warnMissingTemplate(container, target.fieldName);
|
|
3368
|
+
return;
|
|
3369
|
+
}
|
|
3370
|
+
const previous = previousValues.get(container) ?? [];
|
|
3371
|
+
const patches = diffArray(previous, value);
|
|
3372
|
+
if (patches.length === 0) {
|
|
3373
|
+
previousValues.set(container, value.slice());
|
|
3374
|
+
return;
|
|
3375
|
+
}
|
|
3376
|
+
void runWithTransition(() => {
|
|
3377
|
+
applyStructuralPatches({
|
|
3378
|
+
template,
|
|
3379
|
+
container,
|
|
3380
|
+
patches});
|
|
3381
|
+
});
|
|
3382
|
+
previousValues.set(container, value.slice());
|
|
3383
|
+
}
|
|
3384
|
+
};
|
|
3385
|
+
function warnMissingTemplate(container, fieldName) {
|
|
3386
|
+
if (warnedContainers.has(container)) return;
|
|
3387
|
+
warnedContainers.add(container);
|
|
3388
|
+
console.warn(
|
|
3389
|
+
`[live-preview] Skipping structural update for "${fieldName}" \u2014 the <${container.tagName.toLowerCase()}> with data-payload-structural is missing data-payload-array-template. Add an inline template (e.g., data-payload-array-template="<li>{{label}}</li>") so the renderer can materialise new items.`
|
|
3390
|
+
);
|
|
3391
|
+
}
|
|
3392
|
+
registerBuiltinRenderer(structuralArrayRenderer);
|
|
3393
|
+
|
|
3394
|
+
// src/field-types/index.ts
|
|
3395
|
+
var BUILTIN = [
|
|
3396
|
+
textRenderer2,
|
|
3397
|
+
textareaRenderer,
|
|
3398
|
+
richTextRenderer,
|
|
3399
|
+
htmlRenderer,
|
|
3400
|
+
urlRenderer,
|
|
3401
|
+
{ ...urlRenderer, name: "email" },
|
|
3402
|
+
imageRenderer,
|
|
3403
|
+
uploadRenderer2,
|
|
3404
|
+
relationshipRenderer2,
|
|
3405
|
+
selectRenderer,
|
|
3406
|
+
{ ...selectRenderer, name: "radio" },
|
|
3407
|
+
checkboxRenderer,
|
|
3408
|
+
dateRenderer,
|
|
3409
|
+
numberRenderer,
|
|
3410
|
+
arrayRenderer,
|
|
3411
|
+
{ ...arrayRenderer, name: "blocks" },
|
|
3412
|
+
structuralArrayRenderer
|
|
3413
|
+
];
|
|
3414
|
+
function buildBuiltinRenderers2() {
|
|
3415
|
+
return buildBuiltinRenderers(BUILTIN);
|
|
3416
|
+
}
|
|
3417
|
+
|
|
3418
|
+
// src/client/index.ts
|
|
3419
|
+
var LivePreviewClient = class {
|
|
3420
|
+
#emitter = new EventEmitter();
|
|
3421
|
+
#detector;
|
|
3422
|
+
#renderers;
|
|
3423
|
+
#runtime;
|
|
3424
|
+
#plugins;
|
|
3425
|
+
#log;
|
|
3426
|
+
#started = false;
|
|
3427
|
+
#destroyed = false;
|
|
3428
|
+
constructor(config = {}) {
|
|
3429
|
+
const debug = config.debug ?? isDevMode();
|
|
3430
|
+
this.#log = debug ? (...args) => {
|
|
3431
|
+
console.debug("[live-preview]", ...args);
|
|
3432
|
+
} : () => {
|
|
3433
|
+
};
|
|
3434
|
+
this.#detector = new OriginDetector({
|
|
3435
|
+
...config.allowedOrigins !== void 0 ? { additionalOrigins: config.allowedOrigins } : {},
|
|
3436
|
+
...config.disableReferrerDetection !== void 0 ? { enableReferrerDetection: !config.disableReferrerDetection } : {},
|
|
3437
|
+
...config.disableLocalhostMatching !== void 0 ? { enableLocalhostMatching: !config.disableLocalhostMatching } : {}
|
|
3438
|
+
});
|
|
3439
|
+
if (this.#detector.isProductionUnconfigured) {
|
|
3440
|
+
this.#log("no trusted origin could be detected \u2014 set allowedOrigins or PAYLOAD_ADMIN_ORIGIN");
|
|
3441
|
+
}
|
|
3442
|
+
const builtin = buildBuiltinRenderers2();
|
|
3443
|
+
this.#renderers = { ...builtin };
|
|
3444
|
+
this.#plugins = new PluginManager({
|
|
3445
|
+
events: this.#emitter,
|
|
3446
|
+
config: Object.freeze({ ...config }),
|
|
3447
|
+
registerFieldRenderer: (renderer) => {
|
|
3448
|
+
this.#renderers[renderer.name] = renderer;
|
|
3449
|
+
},
|
|
3450
|
+
log: this.#log
|
|
3451
|
+
});
|
|
3452
|
+
this.#runtime = new LivePreviewRuntime({
|
|
3453
|
+
...config.root !== void 0 ? { root: config.root } : {},
|
|
3454
|
+
renderers: this.#renderers,
|
|
3455
|
+
originMatcher: (origin) => this.#detector.matches(origin),
|
|
3456
|
+
readyTargets: this.#detector.enumerate(),
|
|
3457
|
+
emitter: this.#emitter,
|
|
3458
|
+
...config.serverURL !== void 0 && config.serverURL !== "" ? {
|
|
3459
|
+
dataMerge: {
|
|
3460
|
+
serverURL: config.serverURL,
|
|
3461
|
+
...config.apiRoute !== void 0 ? { apiRoute: config.apiRoute } : {},
|
|
3462
|
+
...config.mergeDepth !== void 0 ? { depth: config.mergeDepth } : {},
|
|
3463
|
+
...config.mergeFetch !== void 0 ? { fetchFn: config.mergeFetch } : {}
|
|
3464
|
+
}
|
|
3465
|
+
} : {},
|
|
3466
|
+
...config.debounceMs !== void 0 ? { debounceMs: config.debounceMs } : {},
|
|
3467
|
+
...config.heartbeatMs !== void 0 ? { heartbeatMs: config.heartbeatMs } : {},
|
|
3468
|
+
...config.intersectionRootMargin !== void 0 ? { intersectionRootMargin: config.intersectionRootMargin } : {},
|
|
3469
|
+
...config.disableVisibilityGate !== void 0 ? { disableVisibilityGate: config.disableVisibilityGate } : {},
|
|
3470
|
+
...config.visibilityGateThreshold !== void 0 ? { visibilityGateThreshold: config.visibilityGateThreshold } : {},
|
|
3471
|
+
...config.enableA11y !== void 0 ? { enableA11y: config.enableA11y } : {},
|
|
3472
|
+
...config.a11yLocale !== void 0 ? { a11yLocale: config.a11yLocale } : {},
|
|
3473
|
+
onHeartbeatTimeout: () => {
|
|
3474
|
+
this.#detector.unlockOrigin();
|
|
3475
|
+
},
|
|
3476
|
+
...config.validateToken !== void 0 ? { validateToken: config.validateToken } : {},
|
|
3477
|
+
log: this.#log
|
|
3478
|
+
});
|
|
3479
|
+
this.#emitter.on("connect", (e) => {
|
|
3480
|
+
this.#detector.lockOrigin(e.origin);
|
|
3481
|
+
});
|
|
3482
|
+
if (config.autoStart !== false) {
|
|
3483
|
+
this.start();
|
|
3484
|
+
}
|
|
3485
|
+
}
|
|
3486
|
+
/**
|
|
3487
|
+
* Start the runtime. Returns `true` when the runtime actually
|
|
3488
|
+
* started (it may refuse to start outside a preview context).
|
|
3489
|
+
*/
|
|
3490
|
+
start() {
|
|
3491
|
+
if (this.#destroyed) return false;
|
|
3492
|
+
if (this.#started) return true;
|
|
3493
|
+
if (!isInPreviewContext()) return false;
|
|
3494
|
+
this.#started = true;
|
|
3495
|
+
return this.#runtime.start();
|
|
3496
|
+
}
|
|
3497
|
+
/**
|
|
3498
|
+
* Stop the runtime and tear down every plugin. Idempotent.
|
|
3499
|
+
*/
|
|
3500
|
+
async destroy() {
|
|
3501
|
+
if (this.#destroyed) return;
|
|
3502
|
+
this.#destroyed = true;
|
|
3503
|
+
this.#runtime.destroy();
|
|
3504
|
+
await this.#plugins.destroyAll();
|
|
3505
|
+
this.#emitter.removeAllListeners();
|
|
3506
|
+
}
|
|
3507
|
+
/**
|
|
3508
|
+
* Register a plugin.
|
|
3509
|
+
*/
|
|
3510
|
+
async use(plugin) {
|
|
3511
|
+
if (this.#destroyed) throw new Error("LivePreviewClient: already destroyed");
|
|
3512
|
+
await this.#plugins.register(plugin);
|
|
3513
|
+
}
|
|
3514
|
+
/**
|
|
3515
|
+
* Unregister a plugin by name.
|
|
3516
|
+
*/
|
|
3517
|
+
async unuse(name) {
|
|
3518
|
+
await this.#plugins.unregister(name);
|
|
3519
|
+
}
|
|
3520
|
+
/** Rebuild the element cache manually. */
|
|
3521
|
+
refreshCache() {
|
|
3522
|
+
this.#runtime.refreshCache();
|
|
3523
|
+
}
|
|
3524
|
+
/** Read-only access to the event emitter for `on`/`once`/`off`. */
|
|
3525
|
+
get events() {
|
|
3526
|
+
return this.#emitter;
|
|
3527
|
+
}
|
|
3528
|
+
/** Names of currently registered plugins. */
|
|
3529
|
+
get plugins() {
|
|
3530
|
+
return this.#plugins.list();
|
|
3531
|
+
}
|
|
3532
|
+
/** Current connection status. */
|
|
3533
|
+
get status() {
|
|
3534
|
+
return this.#runtime.status;
|
|
3535
|
+
}
|
|
3536
|
+
/** Number of valid updates received so far. */
|
|
3537
|
+
get updateCount() {
|
|
3538
|
+
return this.#runtime.updateCount;
|
|
3539
|
+
}
|
|
3540
|
+
/** `true` once `destroy()` has been called. */
|
|
3541
|
+
get destroyed() {
|
|
3542
|
+
return this.#destroyed;
|
|
3543
|
+
}
|
|
3544
|
+
};
|
|
3545
|
+
function initLivePreview(config = {}) {
|
|
3546
|
+
const client = new LivePreviewClient({ ...config, autoStart: false });
|
|
3547
|
+
return client.start() ? client : null;
|
|
3548
|
+
}
|
|
3549
|
+
|
|
3550
|
+
// src/inline/runtime.generated.ts
|
|
3551
|
+
var RUNTIME_SOURCE = '"use strict";(()=>{var Jr=Object.defineProperty;var Dn=t=>{throw TypeError(t)};var Zr=(t,e,n)=>e in t?Jr(t,e,{enumerable:!0,configurable:!0,writable:!0,value:n}):t[e]=n;var $n=(t,e,n)=>Zr(t,typeof e!="symbol"?e+"":e,n),Ot=(t,e,n)=>e.has(t)||Dn("Cannot "+n);var i=(t,e,n)=>(Ot(t,e,"read from private field"),n?n.call(t):e.get(t)),l=(t,e,n)=>e.has(t)?Dn("Cannot add the same private member more than once"):e instanceof WeakSet?e.add(t):e.set(t,n),d=(t,e,n,r)=>(Ot(t,e,"write to private field"),r?r.call(t,n):e.set(t,n),n),f=(t,e,n)=>(Ot(t,e,"access private method"),n);var F,_,te,Pt,Ft,bt=class{constructor(){l(this,te);l(this,F,new Map);l(this,_,new Map)}on(e,n){return f(this,te,Pt).call(this,i(this,F),e,n)}once(e,n){return f(this,te,Pt).call(this,i(this,_),e,n)}off(e,n){i(this,F).get(e)?.delete(n),i(this,_).get(e)?.delete(n)}async emit(e,n){let r=i(this,F).get(e);if(r)for(let s of[...r])await f(this,te,Ft).call(this,s,n,e);let o=i(this,_).get(e);if(o&&o.size>0){let s=[...o];i(this,_).delete(e);for(let a of s)await f(this,te,Ft).call(this,a,n,e)}}listenerCount(e){return(i(this,F).get(e)?.size??0)+(i(this,_).get(e)?.size??0)}removeAllListeners(e){if(e===void 0){i(this,F).clear(),i(this,_).clear();return}i(this,F).delete(e),i(this,_).delete(e)}eventNames(){let e=new Set;for(let n of i(this,F).keys())e.add(n);for(let n of i(this,_).keys())e.add(n);return[...e]}};F=new WeakMap,_=new WeakMap,te=new WeakSet,Pt=function(e,n,r){let o=e.get(n);return o||(o=new Set,e.set(n,o)),o.add(r),()=>{e.get(n)?.delete(r)}},Ft=async function(e,n,r){try{await e(n)}catch(o){let s=typeof r=="string"||typeof r=="number"?String(r):"<event>";console.error(`[live-preview] handler for "${s}" threw:`,o)}};var le="data-payload-field",Un="data-payload-type",Qr="data-payload-attribute",ei="data-payload-href",ti="data-payload-src",ni="data-payload-alt",ri="data-payload-array-template",ii="data-payload-array-separator",oi="data-payload-locale",si=`[${le}]`,Hn=new Set(["text","textarea","richText","email","number","checkbox","date","select","radio","array","blocks","group","tabs","row","collapsible","relationship","upload","point","json","code","ui","html","url","image","structural-array"]),k,W,Te,ne,Dt,$t,Rt=class{constructor(e={}){l(this,ne);l(this,k,new Map);l(this,W,new WeakMap);l(this,Te);d(this,Te,e.filter??di)}buildFromRoot(e){let n=performance.now();i(this,k).clear();let r=e.querySelectorAll(si),o=0;for(let s of r){if(!i(this,Te).call(this,s))continue;let a=f(this,ne,$t).call(this,s);a&&(f(this,ne,Dt).call(this,a),i(this,W).set(s,a),o+=1)}return{elementCount:o,fieldCount:i(this,k).size,durationMs:performance.now()-n}}add(e){if(!i(this,Te).call(this,e))return;let n=f(this,ne,$t).call(this,e);if(n)return f(this,ne,Dt).call(this,n),i(this,W).set(e,n),n}remove(e){let n=i(this,W).get(e);if(!n)return!1;i(this,W).delete(e);let r=i(this,k).get(n.fieldName);if(!r)return!1;let o=r.indexOf(n);return o<0?!1:(r.splice(o,1),r.length===0&&i(this,k).delete(n.fieldName),!0)}get(e){return i(this,k).get(e)}getByElement(e){return i(this,W).get(e)}get fieldCount(){return i(this,k).size}get elementCount(){let e=0;for(let n of i(this,k).values())e+=n.length;return e}entries(){return i(this,k).entries()}*values(){for(let e of i(this,k).values())yield*e}has(e){return i(this,W).has(e)}clear(){i(this,k).clear()}};k=new WeakMap,W=new WeakMap,Te=new WeakMap,ne=new WeakSet,Dt=function(e){let n=i(this,k).get(e.fieldName);if(n){n.push(e);return}i(this,k).set(e.fieldName,[e])},$t=function(e){let n=e.getAttribute(le);if(n===null||n.length===0)return;let r=e.getAttribute(Un),o=ai(e),s=e.getAttribute(Qr),a=e.getAttribute(ei),u=e.getAttribute(ti),m=e.getAttribute(ni),w=e.getAttribute(ri),c=e.getAttribute(ii),b=e.getAttribute(oi);return{element:e,fieldName:n,fieldType:o,explicitFieldType:r!==null&&Hn.has(r),...s!==null&&s.length>0?{targetAttribute:s}:{},...a!==null?{hrefField:a}:{},...u!==null?{srcField:u}:{},...m!==null?{altField:m}:{},...w!==null?{arrayTemplate:w}:{},...c!==null?{arraySeparator:c}:{},...b!==null?{locale:b}:{}}};function ai(t){let e=t.getAttribute(Un);if(e!==null&&Hn.has(e))return e;if(t.hasAttribute("data-payload-richtext"))return"richText";if(t.hasAttribute("data-payload-html"))return"html";if(t.hasAttribute("data-payload-structural"))return"structural-array";if(t.hasAttribute("data-payload-array"))return"array";if(t.tagName==="IMG")return"image";if(t.tagName==="A")return"url";if(t.tagName==="TIME")return"date";if(t.tagName==="INPUT"){let n=t.type;if(n==="checkbox")return"checkbox";if(n==="number")return"number";if(n==="date"||n==="datetime-local")return"date"}return"text"}function di(){return!0}var li=100,ui="200px",ue,De,$e,M,ce,re,D,we,jn,Vn,Tt=class{constructor(e,n={}){l(this,we);l(this,ue);l(this,De);l(this,$e);l(this,M,new Set);l(this,ce,null);l(this,re,null);l(this,D,null);d(this,ue,e),d(this,De,n.mutationDebounceMs??li),d(this,$e,n.intersectionRootMargin??ui)}start(e){d(this,ce,new MutationObserver(n=>{f(this,we,jn).call(this,n)})),i(this,ce).observe(e,{childList:!0,subtree:!0,attributes:!0,attributeFilter:[le]}),d(this,re,new IntersectionObserver(n=>{f(this,we,Vn).call(this,n)},{rootMargin:i(this,$e),threshold:0}))}observeElement(e){i(this,re)?.observe(e)}unobserveElement(e){i(this,re)?.unobserve(e),i(this,M).delete(e)}markVisible(e,n){n?i(this,M).add(e):i(this,M).delete(e)}isVisible(e){return i(this,M).has(e)}stop(){i(this,D)!==null&&(clearTimeout(i(this,D)),d(this,D,null)),i(this,ce)?.disconnect(),i(this,re)?.disconnect(),d(this,ce,null),d(this,re,null),i(this,M).clear()}};ue=new WeakMap,De=new WeakMap,$e=new WeakMap,M=new WeakMap,ce=new WeakMap,re=new WeakMap,D=new WeakMap,we=new WeakSet,jn=function(e){ci(e)&&(i(this,D)!==null&&clearTimeout(i(this,D)),d(this,D,setTimeout(()=>{d(this,D,null),i(this,ue).onStructuralChange()},i(this,De))))},Vn=function(e){for(let n of e){let r=n.target;n.isIntersecting?i(this,M).has(r)||(i(this,M).add(r),i(this,ue).onVisibilityChange(r,!0)):i(this,M).has(r)&&(i(this,M).delete(r),i(this,ue).onVisibilityChange(r,!1))}};function ci(t){for(let e of t){if(e.type==="attributes"&&e.attributeName===le)return!0;for(let n of e.addedNodes)if(Bn(n))return!0;for(let n of e.removedNodes)if(Bn(n))return!0}return!1}function Bn(t){if(t.nodeType!==Node.ELEMENT_NODE)return!1;let e=t;return e.hasAttribute(le)?!0:e.querySelector(`[${le}]`)!==null}var fi={basic:1,"schema-json":2,"preview-token":3,"nested-arrays":4,"recursive-diffs":4};function Ut(t){let e=typeof t=="number"&&Number.isFinite(t)&&t>=1?Math.floor(t):void 0,r=Math.min(4,e??1),o=new Set;for(let[s,a]of Object.entries(fi))r>=a&&o.add(s);return{ours:4,...e!==void 0?{theirs:e}:{theirs:void 0},negotiated:r,capabilities:o}}var He,v,xe,fe,ve,Gn,Wn,Ue=class{constructor(e,n){l(this,ve);l(this,He);l(this,v);l(this,xe);l(this,fe,!1);d(this,He,e),d(this,v,n),d(this,xe,r=>{f(this,ve,Gn).call(this,r)})}attach(e=window){i(this,fe)||(e.addEventListener("message",i(this,xe)),d(this,fe,!0))}detach(e=window){i(this,fe)&&(e.removeEventListener("message",i(this,xe)),d(this,fe,!1))}static sendReady(e,n){if(e.length===0||n.length===0)return;let r={type:"payload-live-preview",ready:!0,protocolVersion:4};for(let o of e)for(let s of n)try{o.postMessage(r,s)}catch{}}};He=new WeakMap,v=new WeakMap,xe=new WeakMap,fe=new WeakMap,ve=new WeakSet,Gn=function(e){let n=e.origin;if(!i(this,He).call(this,n)){i(this,v).onInvalid?.("origin",n);return}let r=e.data;if(!pi(r)){i(this,v).onInvalid?.("shape",n);return}switch(r.type){case"payload-live-preview":f(this,ve,Wn).call(this,r,n);return;case"payload-document-event":i(this,v).onDocumentEvent(r,n);return;default:i(this,v).onInvalid?.("type",n)}},Wn=function(e,n){let r=i(this,v).validateToken;if(r===void 0){i(this,v).onUpdate(e,n);return}if(e.ready===!0&&e.data===void 0){i(this,v).onUpdate(e,n);return}let o;try{o=r(e.previewToken,n)}catch{i(this,v).onInvalid?.("token",n);return}if(typeof o=="boolean"){o?i(this,v).onUpdate(e,n):i(this,v).onInvalid?.("token",n);return}o.then(s=>{s?i(this,v).onUpdate(e,n):i(this,v).onInvalid?.("token",n)},()=>{i(this,v).onInvalid?.("token",n)})};function pi(t){return typeof t!="object"||t===null||!("type"in t)?!1:typeof t.type=="string"}var Ee,Be,C,je,xt=class{constructor(e){l(this,Ee);l(this,Be);l(this,C,null);l(this,je,0);d(this,Ee,e.timeoutMs??0),d(this,Be,e.onTimeout)}kick(){d(this,je,Date.now()),!(i(this,Ee)<=0)&&(i(this,C)!==null&&clearTimeout(i(this,C)),d(this,C,setTimeout(()=>{d(this,C,null),i(this,Be).call(this)},i(this,Ee))))}stop(){i(this,C)!==null&&(clearTimeout(i(this,C)),d(this,C,null))}get lastKickAt(){return i(this,je)}get pending(){return i(this,C)!==null}};Ee=new WeakMap,Be=new WeakMap,C=new WeakMap,je=new WeakMap;var pe,Ve,ke,wt,vt=class{constructor(e){l(this,ke);l(this,pe,"disconnected");l(this,Ve);d(this,Ve,e)}get status(){return i(this,pe)}markConnected(){return f(this,ke,wt).call(this,"connected")}markConnecting(){return f(this,ke,wt).call(this,"connecting")}markDisconnected(){return f(this,ke,wt).call(this,"disconnected")}};pe=new WeakMap,Ve=new WeakMap,ke=new WeakSet,wt=function(e){if(i(this,pe)===e)return!1;let n=i(this,pe);return d(this,pe,e),i(this,Ve).call(this,e,n),!0};var mi=()=>{},ze,Ge,We,Ye,me,Y,Et=class{constructor(e){l(this,ze);l(this,Ge);l(this,We);l(this,Ye);l(this,me);l(this,Y,null);d(this,ze,e.serverURL.replace(/\\/+$/,""));let n=e.apiRoute??"/api";d(this,Ge,n.startsWith("/")?n:`/${n}`),d(this,We,e.depth??1),d(this,Ye,e.fetchFn),d(this,me,e.log??mi)}canMerge(e){if(e.globalSlug!==void 0&&e.globalSlug!=="")return!0;if(e.collectionSlug===void 0||e.collectionSlug==="")return!1;let n=e.data.id;return typeof n=="string"||typeof n=="number"}async merge(e){if(!this.canMerge(e))return{status:"unavailable"};let n=i(this,Ye)??(typeof fetch=="function"?fetch:void 0);if(n===void 0)return{status:"unavailable"};i(this,Y)?.abort();let r=new AbortController;d(this,Y,r);let o=e.globalSlug!==void 0&&e.globalSlug!==""?`globals/${e.globalSlug}`:`${e.collectionSlug}/${String(e.data.id)}`,s=`${i(this,ze)}${i(this,Ge)}/${encodeURI(o)}`;try{let a=await n(s,{method:"POST",credentials:"include",headers:{"Content-Type":"application/json","X-Payload-HTTP-Method-Override":"GET"},body:JSON.stringify({data:e.data,depth:i(this,We),flattenLocales:!1,...e.locale!==void 0?{locale:e.locale}:{}}),signal:r.signal});if(!a.ok)return i(this,me).call(this,"merge fetch failed",a.status,s),{status:"unavailable"};let u=await a.json();return u===null||typeof u!="object"||Array.isArray(u)?(i(this,me).call(this,"merge fetch returned non-object",s),{status:"unavailable"}):{status:"merged",doc:u}}catch(a){return r.signal.aborted?{status:"superseded"}:(i(this,me).call(this,"merge fetch error",a),{status:"unavailable"})}finally{i(this,Y)===r&&d(this,Y,null)}}destroy(){i(this,Y)?.abort(),d(this,Y,null)}};ze=new WeakMap,Ge=new WeakMap,We=new WeakMap,Ye=new WeakMap,me=new WeakMap,Y=new WeakMap;var hi=new Set(["http:","https:","mailto:","tel:"]),yi=/^\\s*(?:javascript|data|vbscript|file|blob|about)\\s*:/i,gi=/^(?:\\.{1,2}\\/|[a-zA-Z0-9_-]+\\/?)/;function R(t){if(typeof t!="string"||t.length===0)return!1;let e=t.trim();if(e.length===0||yi.test(e))return!1;if(e.startsWith("#")||e.startsWith("?")||e.startsWith("//")||e.startsWith("/"))return!0;try{let n=new URL(e);return hi.has(n.protocol)}catch{return gi.test(e)}}function kt(t){if(!R(t))return!1;let e=t.trim();return/^https?:\\/\\//i.test(e)||/^\\/\\/[^/]/.test(e)}var bi=new Set(["style","srcdoc","formaction","form","id","name","is","srcset","imagesrcset"]),Ri=new Set(["href","src","poster","cite","action","xlink:href","data"]);function Yn(t,e,n){let r=e.toLowerCase().trim();if(r.length===0||r.startsWith("on")||bi.has(r))return"blocked";if(n==null)return t.removeAttribute(r),"applied";if(typeof n!="string"&&typeof n!="number"&&typeof n!="boolean")return"blocked";let o=String(n);return Ri.has(r)&&!R(o)?"blocked":(t.setAttribute(r,o),"applied")}var Se,qe,Ke,Xe,Je,Ze,Qe,et,Ae,$,q,S,U,K,qn,Kn,Ht,St=class{constructor(e,n){l(this,K);l(this,Se);l(this,qe);l(this,Ke);l(this,Xe);l(this,Je);l(this,Ze);l(this,Qe);l(this,et);l(this,Ae);l(this,$,new Map);l(this,q,new Map);l(this,S,null);l(this,U,null);d(this,Se,e),d(this,qe,n.debounceMs??50),d(this,Ke,n.isVisible),d(this,Xe,n.visibilityGateThreshold??50),d(this,Je,n.disableVisibilityGate??!1),d(this,Ze,n.getCacheSize),d(this,Qe,n.onFlush),d(this,et,n.scheduleFrame??(typeof requestAnimationFrame=="function"?requestAnimationFrame.bind(globalThis):r=>setTimeout(()=>{r(performance.now())},0))),d(this,Ae,n.cancelFrame??(typeof cancelAnimationFrame=="function"?cancelAnimationFrame.bind(globalThis):r=>{clearTimeout(r)}))}schedule(e){let n=i(this,$).get(e.target.element);n?(n.value=e.value,n.allFields=e.allFields):i(this,$).set(e.target.element,{target:e.target,value:e.value,allFields:e.allFields}),f(this,K,qn).call(this)}flushNow(){return i(this,S)!==null&&(clearTimeout(i(this,S)),d(this,S,null)),f(this,K,Ht).call(this)}notifyVisible(e){let n=i(this,q).get(e);n&&(i(this,q).delete(e),i(this,Se).call(this,n))}forget(e){i(this,$).delete(e),i(this,q).delete(e)}destroy(){i(this,S)!==null&&(clearTimeout(i(this,S)),d(this,S,null)),i(this,U)!==null&&(i(this,Ae).call(this,i(this,U)),d(this,U,null)),i(this,$).clear(),i(this,q).clear()}get pendingCount(){return i(this,$).size}get replayCount(){return i(this,q).size}};Se=new WeakMap,qe=new WeakMap,Ke=new WeakMap,Xe=new WeakMap,Je=new WeakMap,Ze=new WeakMap,Qe=new WeakMap,et=new WeakMap,Ae=new WeakMap,$=new WeakMap,q=new WeakMap,S=new WeakMap,U=new WeakMap,K=new WeakSet,qn=function(){i(this,S)!==null&&clearTimeout(i(this,S)),d(this,S,setTimeout(()=>{d(this,S,null),f(this,K,Kn).call(this)},i(this,qe)))},Kn=function(){i(this,U)!==null&&i(this,Ae).call(this,i(this,U)),d(this,U,i(this,et).call(this,()=>{d(this,U,null),f(this,K,Ht).call(this)}))},Ht=function(){var u;let e=performance.now(),n=!i(this,Je)&&i(this,Ze).call(this)>i(this,Xe),r=i(this,$);d(this,$,new Map);let o=0,s=0;for(let m of r.values()){if(n&&!i(this,Ke).call(this,m.target.element)){i(this,q).set(m.target.element,m),s+=1;continue}i(this,Se).call(this,m),o+=1}let a={applied:o,deferred:s,durationMs:performance.now()-e};return(u=i(this,Qe))==null||u.call(this,a),a};function Le(){let t=Ti();if(t!==void 0)return t;let e=wi();return e!==void 0?e:"en"}function Ti(){if(typeof document>"u")return;let t=document.documentElement.getAttribute("lang");return t===null||t.length===0?void 0:t}function wi(){if(typeof navigator>"u")return;let t=navigator.language;return typeof t=="string"&&t.length>0?t:void 0}var Xn="payload-live-preview-a11y",xi=1500,vi=["position:absolute","width:1px","height:1px","padding:0","margin:-1px","overflow:hidden","clip:rect(0 0 0 0)","clip-path:inset(50%)","white-space:nowrap","border:0"].join(";"),Jn={connected:"Live preview connected",disconnected:"Live preview disconnected",updated:t=>`${String(t)} ${t===1?"change":"changes"} applied`},Ei={en:Jn,de:{connected:"Vorschau verbunden",disconnected:"Vorschau getrennt",updated:t=>`${String(t)} ${t===1?"\\xC4nderung":"\\xC4nderungen"} angewendet`}};function ki(t){let e=t.split(/[-_]/)[0]?.toLowerCase()??"en";return Ei[e]??Jn}var I,he,H,ie,Zn,At,tt=class{constructor(e=Le()){l(this,ie);l(this,I,null);l(this,he);l(this,H,null);d(this,he,ki(e))}announceConnected(){f(this,ie,At).call(this,i(this,he).connected)}announceDisconnected(){f(this,ie,At).call(this,i(this,he).disconnected)}announceUpdate(e){e<=0||f(this,ie,At).call(this,i(this,he).updated(e))}detach(){i(this,H)!==null&&(clearTimeout(i(this,H)),d(this,H,null)),i(this,I)?.parentNode&&i(this,I).parentNode.removeChild(i(this,I))}};I=new WeakMap,he=new WeakMap,H=new WeakMap,ie=new WeakSet,Zn=function(){if(i(this,I)!==null&&i(this,I).isConnected)return i(this,I);if(typeof document>"u"||document.body===null)return null;let e=document.getElementById(Xn);return e||(e=document.createElement("div"),e.id=Xn,e.setAttribute("role","status"),e.setAttribute("aria-live","polite"),e.setAttribute("aria-atomic","true"),e.setAttribute("style",vi),document.body.appendChild(e)),d(this,I,e),e},At=function(e){let n=f(this,ie,Zn).call(this);n&&(n.textContent=e,i(this,H)!==null&&clearTimeout(i(this,H)),d(this,H,setTimeout(()=>{d(this,H,null),i(this,I)&&(i(this,I).textContent="")},xi)))};var Si=new Set(["tabs","row","collapsible"]);function Bt(t,e=""){let n=new Map;return nt(t,e,n),n}function nt(t,e,n){for(let r of t){if(Si.has(r.type)){r.fields&&nt(r.fields,e,n);continue}if(r.type==="group"){let s=e===""?r.name:`${e}.${r.name}`;n.set(s,r),r.fields&&nt(r.fields,s,n);continue}let o=e===""?r.name:`${e}.${r.name}`;if(n.set(o,r),r.type==="array"&&r.fields)nt(r.fields,`${o}.*`,n);else if(r.type==="blocks"&&r.blocks)for(let s of r.blocks)nt(s.fields,`${o}.*.${s.slug}`,n)}}function jt(t,e){if(t.has(e))return t.get(e);let n=e.replace(/\\.\\d+(?=\\.|$)/g,".*");if(t.has(n))return t.get(n)}function Vt(t){if(typeof t!="object"||t===null)return;let e=t.id;if(typeof e=="string"||typeof e=="number")return e}function Qn(t){if(typeof t!="object"||t===null)return;let e=t.blockType;return typeof e=="string"?e:void 0}function rt(t,e){return e.every(r=>Vt(r)!==void 0)?Ai(t,e):Li(t,e)}function Ai(t,e){let n=[],r=new Map;for(let s=0;s<t.length;s+=1){let a=Vt(t[s]);a!==void 0&&r.set(a,s)}let o=new Set;for(let s=0;s<e.length;s+=1){let a=e[s],u=Vt(a);if(u===void 0){n.push({kind:"insert",index:s,value:a});continue}let m=r.get(u);if(m===void 0){n.push({kind:"insert",index:s,value:a});continue}o.add(m);let w=t[m];if(Qn(w)!==Qn(a)){n.push({kind:"replace",index:s,oldValue:w,value:a});continue}m!==s&&n.push({kind:"move",from:m,to:s,value:a}),er(w,a)||n.push({kind:"update",index:s,value:a})}for(let s=0;s<t.length;s+=1)o.has(s)||n.push({kind:"remove",index:s,value:t[s]});return n}function Li(t,e){let n=[],r=Math.min(t.length,e.length);for(let o=0;o<r;o+=1)er(t[o],e[o])||n.push({kind:"update",index:o,value:e[o]});if(e.length>t.length)for(let o=t.length;o<e.length;o+=1)n.push({kind:"insert",index:o,value:e[o]});if(t.length>e.length)for(let o=e.length;o<t.length;o+=1)n.push({kind:"remove",index:o,value:t[o]});return n}function er(t,e){if(Object.is(t,e))return!0;if(typeof t!="object"||typeof e!="object"||t===null||e===null)return!1;let n=t,r=e,o=Object.keys(n),s=Object.keys(r);if(o.length!==s.length)return!1;for(let a of o)if(!Object.is(n[a],r[a]))return!1;return!0}var tr={text:"text",textarea:"textarea",richText:"richText",email:"email",number:"number",checkbox:"checkbox",date:"date",select:"select",radio:"radio",array:"array",blocks:"blocks",group:"text",tabs:"text",row:"text",collapsible:"text",relationship:"relationship",upload:"upload",point:"text",json:"text",code:"text",ui:"text"};function zt(t){if(Object.prototype.hasOwnProperty.call(tr,t))return tr[t]}var Mi=[0,500,1e3,2e3],E,A,X,oe,Me,J,Ie,it,B,ye,ge,ot,O,Ne,_e,Z,be,N,Ce,Oe,Pe,Re,se,st,at,h,Gt,Wt,Yt,rr,ir,or,sr,ar,dr,lr,ur,cr,fr,pr,Lt=class{constructor(e){l(this,h);l(this,E);l(this,A);l(this,X);l(this,oe);l(this,Me);l(this,J);l(this,Ie);l(this,it);l(this,B);l(this,ye);l(this,ge);l(this,ot);l(this,O);l(this,Ne);l(this,_e,[]);l(this,Z);l(this,be);l(this,N);l(this,Ce);l(this,Oe);l(this,Pe,Ut(void 0));l(this,Re,!1);l(this,se,null);l(this,st,0);l(this,at,new Set);d(this,E,e.emitter),d(this,it,e.renderers),d(this,B,e.root??(typeof document<"u"?document:null)),d(this,ye,e.readyTargets),d(this,ge,e.sendReady??Oi),d(this,ot,e.onHeartbeatTimeout),d(this,O,e.log??Ni),d(this,Ne,e.warn??Ci),d(this,Z,Ii(e)),d(this,be,e.dataMerge!==void 0?new Et({serverURL:e.dataMerge.serverURL,...e.dataMerge.apiRoute!==void 0?{apiRoute:e.dataMerge.apiRoute}:{},...e.dataMerge.depth!==void 0?{depth:e.dataMerge.depth}:{},...e.dataMerge.fetchFn!==void 0?{fetchFn:e.dataMerge.fetchFn}:{},log:(...n)=>{i(this,O).call(this,...n)}}):null),d(this,A,new Rt),d(this,X,new Tt({onStructuralChange:()=>{f(this,h,Yt).call(this)},onVisibilityChange:(n,r)=>{r&&i(this,oe).notifyVisible(n)}},{...e.intersectionRootMargin!==void 0?{intersectionRootMargin:e.intersectionRootMargin}:{}})),d(this,oe,new St(n=>{f(this,h,ur).call(this,n)},{...e.debounceMs!==void 0?{debounceMs:e.debounceMs}:{},...e.disableVisibilityGate!==void 0?{disableVisibilityGate:e.disableVisibilityGate}:{},...e.visibilityGateThreshold!==void 0?{visibilityGateThreshold:e.visibilityGateThreshold}:{},isVisible:n=>i(this,X).isVisible(n),getCacheSize:()=>i(this,A).elementCount,onFlush:n=>{f(this,h,fr).call(this,n)}})),d(this,Me,new Ue(e.originMatcher,{onUpdate:(n,r)=>{f(this,h,or).call(this,n,r)},onDocumentEvent:(n,r)=>{f(this,h,ar).call(this,n,r)},onInvalid:(n,r)=>{if(n==="token"){let o=new Error(`Preview token rejected (origin: ${r})`);i(this,E).emit("error",{error:o,context:"token"})}i(this,O).call(this,"message rejected:",n,r)},...e.validateToken!==void 0?{validateToken:e.validateToken}:{}})),d(this,J,new vt((n,r)=>{i(this,O).call(this,"connection",r,"\\u2192",n)})),d(this,Ie,new xt({...e.heartbeatMs!==void 0?{timeoutMs:e.heartbeatMs}:{},onTimeout:()=>{f(this,h,pr).call(this)}}))}start(){if(i(this,Re))return!1;d(this,Re,!0);let e=i(this,B);if(e instanceof Document&&e.readyState==="loading"){let n=()=>{d(this,se,null),f(this,h,Gt).call(this)};return d(this,se,n),e.addEventListener("DOMContentLoaded",n,{once:!0}),!0}return f(this,h,Gt).call(this),!0}destroy(){if(!i(this,Re))return;if(d(this,Re,!1),i(this,se)!==null){let n=i(this,B);n instanceof Document&&n.removeEventListener("DOMContentLoaded",i(this,se)),d(this,se,null),i(this,E).emit("destroy",{timestamp:Date.now()});return}for(let n of i(this,_e))clearTimeout(n);i(this,_e).length=0,i(this,Ie).stop(),i(this,Me).detach(),i(this,X).stop(),i(this,oe).destroy(),i(this,be)?.destroy(),i(this,A).clear();let e=i(this,J).status==="connected";i(this,J).markDisconnected(),e&&(i(this,Z)?.announceDisconnected(),i(this,E).emit("disconnect",{reason:"destroy",timestamp:Date.now()})),i(this,Z)?.detach(),i(this,E).emit("destroy",{timestamp:Date.now()})}refreshCache(){f(this,h,Yt).call(this)}get status(){return i(this,J).status}get cache(){return i(this,A)}get updateCount(){return i(this,st)}get protocol(){return i(this,Pe)}};E=new WeakMap,A=new WeakMap,X=new WeakMap,oe=new WeakMap,Me=new WeakMap,J=new WeakMap,Ie=new WeakMap,it=new WeakMap,B=new WeakMap,ye=new WeakMap,ge=new WeakMap,ot=new WeakMap,O=new WeakMap,Ne=new WeakMap,_e=new WeakMap,Z=new WeakMap,be=new WeakMap,N=new WeakMap,Ce=new WeakMap,Oe=new WeakMap,Pe=new WeakMap,Re=new WeakMap,se=new WeakMap,st=new WeakMap,at=new WeakMap,h=new WeakSet,Gt=function(){f(this,h,Wt).call(this),i(this,X).start(i(this,B)instanceof Document?i(this,B).body:i(this,B)),i(this,Me).attach(),f(this,h,rr).call(this);for(let e of Mi)if(e===0)i(this,ge).call(this,i(this,ye));else{let n=setTimeout(()=>{i(this,ge).call(this,i(this,ye))},e);i(this,_e).push(n)}},Wt=function(){let e=i(this,A).buildFromRoot(i(this,B));i(this,O).call(this,"cache built",e),i(this,E).emit("cacheRefresh",e);for(let n of i(this,A).values())i(this,X).observeElement(n.element)},Yt=function(){for(let e of i(this,A).values())i(this,X).unobserveElement(e.element),i(this,oe).forget(e.element);f(this,h,Wt).call(this)},rr=function(){i(this,E).emit("init",{timestamp:Date.now()})},ir=function(e){let n=Ut(e);n.negotiated!==i(this,Pe).negotiated&&(d(this,Pe,n),i(this,O).call(this,"protocol negotiated",`ours=${4}`,`theirs=${e}`,`negotiated=${n.negotiated}`))},or=function(e,n){i(this,Ie).kick(),e.protocolVersion!==void 0&&f(this,h,ir).call(this,e.protocolVersion),e.data!==void 0&&(i(this,J).markConnected()&&(i(this,Z)?.announceConnected(),i(this,E).emit("connect",{origin:n,timestamp:Date.now()})),d(this,st,i(this,st)+1),e.locale!==void 0&&d(this,N,e.locale),e.fieldSchemaJSON!==void 0&&(d(this,Ce,e.fieldSchemaJSON),d(this,Oe,Bt(e.fieldSchemaJSON))),f(this,h,sr).call(this,e).then(r=>{if(r===null)return;let o={fields:r,...i(this,Ce)!==void 0?{schema:i(this,Ce)}:{},...e.globalSlug!==void 0?{globalSlug:e.globalSlug}:{},...e.collectionSlug!==void 0?{collectionSlug:e.collectionSlug}:{},...i(this,N)!==void 0?{locale:i(this,N)}:{}},s=!1;i(this,E).emit("beforeUpdate",{data:o,cancel:()=>{s=!0}}).then(()=>{s||f(this,h,dr).call(this,o)})}))},sr=async function(e){let n=e.data??{};if(i(this,be)===null)return n;let r=await i(this,be).merge({collectionSlug:e.collectionSlug,globalSlug:e.globalSlug,data:n,locale:i(this,N)});return r.status==="merged"?r.doc:r.status==="superseded"?null:n},ar=function(e,n){i(this,E).emit("documentSave",{timestamp:Date.now()})},dr=function(e){for(let[n,r]of i(this,A).entries()){let o=$i(e.fields,n,i(this,N));if(o!==void 0)for(let s of r){let a={target:s,value:o,allFields:e.fields};i(this,oe).schedule(a)}}f(this,h,lr).call(this,e.fields)},lr=function(e){if(i(this,A).elementCount===0)return;let n=new Set;for(let[r]of i(this,A).entries())n.add(r);for(let[r,o]of Object.entries(e)){if(i(this,at).has(r)||Pi.has(r)||!Fi(o))continue;let s=Di(r,i(this,N));n.has(r)||n.has(s)||(i(this,at).add(r),i(this,Ne).call(this,`[live-preview] update arrived for field "${r}" but no <\\u2026 data-payload-field="${s}"> element exists on this page. Render the binding anchor unconditionally in your template so live edits to an initially-empty field have somewhere to land.`))}},ur=function(e){let n=i(this,Oe)!==void 0?jt(i(this,Oe),e.target.fieldName):void 0,r=f(this,h,cr).call(this,e.target,n?.type);r==="text"&&e.target.explicitFieldType!==!0&&_i(e.value)&&(r="richText");let o=i(this,it)[r],s=Ui(e.target.element),a={allFields:e.allFields,locale:i(this,N),schema:n};try{e.target.targetAttribute!==void 0?Yn(e.target.element,e.target.targetAttribute,e.value)==="blocked"&&i(this,Ne).call(this,`[live-preview] refused to write field "${e.target.fieldName}" into attribute "${e.target.targetAttribute}" (unsafe attribute or value)`):o?o.render(e.target,e.value,a):i(this,O).call(this,"no renderer for",r)}catch(u){let m=u instanceof Error?u:new Error(String(u));i(this,E).emit("error",{error:m,context:"renderer"});return}i(this,E).emit("elementUpdate",{element:e.target.element,fieldName:e.target.fieldName,previousValue:s,nextValue:e.value})},cr=function(e,n){if(e.explicitFieldType)return e.fieldType;if(n!==void 0){let r=zt(n);if(r!==void 0)return r}return e.fieldType},fr=function(e){e.applied===0&&e.deferred===0||(e.applied>0&&i(this,Z)?.announceUpdate(e.applied),i(this,E).emit("afterUpdate",{data:{fields:{},...i(this,N)!==void 0?{locale:i(this,N)}:{}},updatedCount:e.applied,durationMs:e.durationMs}))},pr=function(){var e;i(this,J).markDisconnected()&&(i(this,Z)?.announceDisconnected(),i(this,E).emit("disconnect",{reason:"timeout",timestamp:Date.now()}));try{(e=i(this,ot))==null||e.call(this)}catch(n){i(this,O).call(this,"onHeartbeatTimeout hook threw:",n)}i(this,ge).call(this,i(this,ye))};function Ii(t){return t.enableA11y===!1?null:t.a11yLocale!==void 0?new tt(t.a11yLocale):new tt}function Ni(){}function _i(t){if(typeof t!="object"||t===null||!("root"in t))return!1;let e=t.root;return typeof e!="object"||e===null?!1:Array.isArray(e.children)}function Ci(...t){typeof console<"u"&&typeof console.warn=="function"&&console.warn(...t)}function Oi(t){if(typeof window>"u")return;let e=[];window.parent!==window&&e.push(window.parent),window.opener instanceof Window&&e.push(window.opener),Ue.sendReady(e,t)}var mr=new Set(["__proto__","prototype","constructor"]),Pi=new Set(["id","_id","createdAt","updatedAt","createdBy","updatedBy","_status","globalType","collection","locale","localized"]);function Fi(t){return t==null?!1:typeof t=="string"||typeof t=="number"||typeof t=="boolean"||typeof t=="bigint"}function Di(t,e){if(!e)return t;let n=`_${e}`;return t.endsWith(n)?t.slice(0,-n.length):t}function $i(t,e,n){let r=nr(t,e);if(r!==void 0)return r;if(e.includes(".")){let o=e.split("."),s=t;for(let a of o){if(mr.has(a)||s===null||typeof s!="object")return;s=s[a]}if(s!==void 0)return s}if(n!==void 0){let o=nr(t,`${e}_${n}`);if(o!==void 0)return o}}function nr(t,e){if(!mr.has(e)&&Object.prototype.hasOwnProperty.call(t,e))return t[e]}function Ui(t){return t.tagName==="INPUT"||t.tagName==="TEXTAREA"?t.value:t.tagName==="IMG"?t.src:t.textContent}function dt(){if(typeof window>"u")return!1;try{return window.self!==window.top}catch{return!0}}function qt(){return typeof window>"u"?!1:window.opener!=null}function Kt(){return dt()||qt()}function hr(){let t=Hi();if(t!==void 0)return t!=="production";let e=Bi();if(e!==void 0)return e;if(typeof window<"u"){let n=window.location.hostname;if(n==="localhost"||n==="127.0.0.1")return!0}return!1}function yr(t){if(typeof process<"u"){let n=process.env[t];if(typeof n=="string"&&n.length>0)return n}let e=void 0;if(e){let n=e[t];if(typeof n=="string"&&n.length>0)return n}}function Hi(){if(typeof process>"u")return;let t="production";return typeof t=="string"?t:void 0}function Bi(){let t=void 0;if(!t)return;let e=t.DEV;return typeof e=="boolean"?e:void 0}var ji=/^https?:\\/\\/(localhost|127\\.0\\.0\\.1)(?::\\d+)?$/i,Vi=/^https?:\\/\\/[^/?#\\s]+$/i,zi=["PAYLOAD_ADMIN_ORIGIN","PUBLIC_PAYLOAD_ADMIN_ORIGIN","NEXT_PUBLIC_PAYLOAD_ADMIN_ORIGIN"],j,V,ae,lt,P,Mt=class{constructor(e={}){l(this,j);l(this,V);l(this,ae);l(this,lt);l(this,P);let n=new Set;for(let a of e.additionalOrigins??[]){let u=gr(a);u!==void 0&&n.add(u)}for(let a of zi){let u=yr(a);if(u!==void 0)for(let m of u.split(",")){let w=gr(m);w!==void 0&&n.add(w)}}d(this,j,n);let r=e.enableReferrerDetection??!0,o;if(r){let a=e.referrer??(typeof document<"u"?document.referrer:"");if(a.length>0)try{let u=new URL(a).origin;u&&u!=="null"&&(o=u)}catch{}}d(this,V,o),d(this,lt,o!==void 0);let s=(e.enableLocalhostMatching??!0)&&(e.forceDevMode??hr());d(this,ae,s)}matches(e){return e.length===0||e==="null"?!1:i(this,P)!==void 0?e===i(this,P):!!(i(this,j).has(e)||i(this,j).size===0&&i(this,V)!==void 0&&e===i(this,V)||i(this,ae)&&ji.test(e))}lockOrigin(e){return this.matches(e)?(d(this,P,e),!0):!1}unlockOrigin(){let e=i(this,P);return d(this,P,void 0),e}get lockedOrigin(){return i(this,P)}enumerate(){if(i(this,P)!==void 0)return[i(this,P)];let e=new Set(i(this,j));if(i(this,j).size===0&&i(this,V)!==void 0&&e.add(i(this,V)),i(this,ae))for(let n of Gi)e.add(`http://localhost:${String(n)}`),e.add(`http://127.0.0.1:${String(n)}`);return[...e]}get referrerWasAvailable(){return i(this,lt)}get isProductionUnconfigured(){return!(!dt()||i(this,j).size>0||i(this,ae)||i(this,V)!==void 0)}get isReferrerOnlyTrust(){return i(this,j).size>0||i(this,ae)?!1:i(this,V)!==void 0}};j=new WeakMap,V=new WeakMap,ae=new WeakMap,lt=new WeakMap,P=new WeakMap;var Gi=[3e3,3001,3333,4e3,4321,5e3,5173,5174,8e3,8080,8888,9e3];function gr(t){let e=t.trim();if(e.length!==0)try{let n=new URL(e);return n.protocol!=="http:"&&n.protocol!=="https:"||!Vi.test(n.origin)?void 0:n.origin}catch{return}}var br={name:"payload-live-preview",version:"1.0.0",description:"Framework-agnostic live preview for Payload CMS \\u2014 Astro-first; works with Next.js, SvelteKit, Nuxt, and plain HTML.",author:"relative23",license:"MIT",type:"module",main:"./dist/index.cjs",module:"./dist/index.js",types:"./dist/index.d.ts",exports:{".":{import:{types:"./dist/index.d.ts",default:"./dist/index.js"},require:{types:"./dist/index.d.cts",default:"./dist/index.cjs"}},"./core":{import:{types:"./dist/core.d.ts",default:"./dist/core.js"},require:{types:"./dist/core.d.cts",default:"./dist/core.cjs"}},"./astro":{import:{types:"./dist/adapters/astro/index.d.ts",default:"./dist/adapters/astro/index.js"}},"./nextjs":{import:{types:"./dist/adapters/nextjs/index.d.ts",default:"./dist/adapters/nextjs/index.js"}},"./sveltekit":{import:{types:"./dist/adapters/sveltekit/index.d.ts",default:"./dist/adapters/sveltekit/index.js"}},"./nuxt":{import:{types:"./dist/adapters/nuxt/index.d.ts",default:"./dist/adapters/nuxt/index.js"}},"./codegen":{import:{types:"./dist/codegen.d.ts",default:"./dist/codegen.js"},require:{types:"./dist/codegen.d.cts",default:"./dist/codegen.cjs"}},"./codegen/astro":{import:{types:"./dist/codegen-astro.d.ts",default:"./dist/codegen-astro.js"}},"./payload":{import:{types:"./dist/payload.d.ts",default:"./dist/payload.js"},require:{types:"./dist/payload.d.cts",default:"./dist/payload.cjs"}},"./astro/RichText.astro":"./dist/adapters/astro/RichText.astro","./astro/middleware-entry":{import:{types:"./dist/adapters/astro/middleware-entry.d.ts",default:"./dist/adapters/astro/middleware-entry.js"}}},bin:{"pll-codegen":"./dist/codegen-cli.js"},files:["dist","README.md","LICENSE"],sideEffects:!1,scripts:{prepare:"npm run build:runtime","build:runtime":"tsx scripts/build-runtime.ts",build:"npm run build:runtime && tsup && npm run build:assets","build:assets":`node -e "require(\'node:fs\').copyFileSync(\'src/adapters/astro/RichText.astro\',\'dist/adapters/astro/RichText.astro\')"`,dev:"tsup --watch",test:"vitest run","test:watch":"vitest","test:unit":"vitest run tests/unit","test:integration":"vitest run tests/integration","test:e2e":"playwright test","test:bench":"vitest bench tests/benchmarks","test:coverage":"vitest run --coverage",typecheck:"tsc --noEmit",lint:"eslint .","lint:fix":"eslint . --fix",format:\'prettier --write "src/**/*.ts" "tests/**/*.ts"\',"format:check":\'prettier --check "src/**/*.ts" "tests/**/*.ts"\',check:"npm run typecheck && npm run lint && npm run test",prepublishOnly:"npm run check && npm run build",changeset:"changeset",version:"changeset version",release:"npm run build && changeset publish"},devDependencies:{"@changesets/cli":"^2.31.0","@eslint/js":"^10.0.1","@playwright/test":"^1.61.1","@types/node":"^22.20.1","@vitest/coverage-v8":"^4.1.10",esbuild:"^0.28.1",eslint:"^10.7.0",globals:"^17.7.0",jsdom:"^29.1.1",prettier:"^3.9.5","ts-morph":"^28.0.0",tsup:"^8.5.1",tsx:"^4.23.0",typescript:"^5.9.3","typescript-eslint":"^8.63.0",vitest:"^4.1.10"},keywords:["payload","payload-cms","payload-live-preview","live-preview","cms","headless","astro","nextjs","sveltekit","nuxt","vue","svelte","schema-driven","lexical","csp-safe","framework-agnostic"],peerDependencies:{astro:">=4.0.0 <8.0.0","ts-morph":"^28.0.0"},peerDependenciesMeta:{astro:{optional:!0},"ts-morph":{optional:!0}},engines:{node:">=20.19.0"},publishConfig:{access:"public",provenance:!0},repository:{type:"git",url:"https://github.com/relative23/payload-live-preview.git"},bugs:{url:"https://github.com/relative23/payload-live-preview/issues"},homepage:"https://github.com/relative23/payload-live-preview#readme"};var Rr=br.version;var Xt=new Set(["p","br","strong","b","em","i","u","s","strike","mark","small","h1","h2","h3","h4","h5","h6","ul","ol","li","dl","dt","dd","blockquote","code","pre","kbd","samp","var","a","span","div","section","article","aside","header","footer","nav","figure","figcaption","img","picture","source","audio","video","sub","sup","hr","time","abbr","cite","q","table","thead","tbody","tfoot","tr","th","td","caption","colgroup","col"]),wr=new Set(["script","style","iframe","object","embed","link","meta","base","form","input","button","select","textarea","svg","math","template","frame","frameset","noframes","noscript"]),xr=new Set(["id","class","lang","dir","title","role","tabindex"]),Yi="aria-",qi="data-",Jt={a:new Set(["href","target","rel","download","hreflang","type"]),img:new Set(["src","srcset","sizes","alt","width","height","loading","decoding"]),picture:new Set([]),source:new Set(["src","srcset","sizes","type","media"]),audio:new Set(["src","controls","autoplay","loop","muted","preload"]),video:new Set(["src","poster","controls","autoplay","loop","muted","preload","width","height","playsinline"]),time:new Set(["datetime"]),abbr:new Set(["title"]),q:new Set(["cite"]),blockquote:new Set(["cite"]),table:new Set(["summary"]),th:new Set(["colspan","rowspan","scope","headers","abbr"]),td:new Set(["colspan","rowspan","headers"]),col:new Set(["span"]),colgroup:new Set(["span"]),ol:new Set(["start","reversed","type"]),li:new Set(["value"]),code:new Set(["class"]),pre:new Set(["class"])},vr=new Set(["href","src","cite","poster"]),Zt=class extends Error{constructor(){super(...arguments);$n(this,"name","SanitizerEnvironmentError")}};function Ki(t){if(!t){let r=new Map;for(let[o,s]of Object.entries(Jt))r.set(o,s);return{allowedTags:Xt,attrByTag:r}}let e=new Set(Xt);for(let r of t.additionalAllowedTags??[])e.add(r);let n=new Map;for(let[r,o]of Object.entries(Jt))n.set(r,o);for(let[r,o]of Object.entries(t.additionalAllowedAttributes??{})){let s=n.get(r),a=new Set(s??[]);for(let u of o)a.add(u);n.set(r,a)}return{allowedTags:e,attrByTag:n}}var Tr;function z(t,e){let n=Er();if(!n)throw new Zt("sanitizeHtml requires a DOM. Inject one via setSanitizerDocument() for SSR (linkedom, jsdom, happy-dom, \\u2026) or use the plain-text path on the server.");if(t==="")return"";let r=Ki(e),o=n.createElement("template");return o.innerHTML=t,Qt(o.content,r),o.innerHTML}function Er(){if(Tr)return Tr;if(!(typeof document>"u"))return document}function kr(){return Er()!==void 0}function Qt(t,e){for(let n of Array.from(t.childNodes))n.nodeType===Node.ELEMENT_NODE?Xi(n,e):n.nodeType===Node.COMMENT_NODE&&n.remove()}function Xi(t,e){let n=t.tagName.toLowerCase();if(wr.has(n)){t.remove();return}if(!e.allowedTags.has(n)){Qt(t,e);let r=t.parentNode;if(r){for(;t.firstChild;)r.insertBefore(t.firstChild,t);t.remove()}return}Ji(t,n,e),n==="a"&&Qi(t),Qt(t,e)}function Ji(t,e,n){let r=n.attrByTag.get(e);for(let o of Array.from(t.attributes)){let s=o.name.toLowerCase();if(s.startsWith("on")){t.removeAttribute(o.name);continue}if(s==="style"){t.removeAttribute(o.name);continue}if(!(xr.has(s)||s.startsWith(Yi)||s.startsWith(qi))){if(r?.has(s)){(vr.has(s)&&!R(o.value)||s==="srcset"&&!Zi(o.value))&&t.removeAttribute(o.name);continue}t.removeAttribute(o.name)}}}function Zi(t){let e=t.split(",");for(let n of e){let r=n.trim();if(r.length===0)continue;let o=r.split(/\\s+/,1)[0];if(o===void 0||o.length===0||!R(o))return!1}return!0}function Qi(t){let e=t.getAttribute("href");e&&kt(e)&&(t.setAttribute("rel","noopener noreferrer"),t.hasAttribute("target")||t.setAttribute("target","_blank"))}var Ws=Object.freeze({allowedTags:Xt,removeCompletely:wr,globalAttributes:xr,attributesByTag:Jt,urlAttributes:vr});var Sr=new Map;function p(t,e){Sr.set(t,e)}function Ar(t){return Sr.get(t)}var eo={1:"left",2:"center",3:"right",4:"justify"},to=new Set(["left","center","right","justify","start","end"]);function en(t){let e=t.format;if(typeof e=="string"&&to.has(e))return e;if(typeof e=="number"){let n=eo[e];if(n!==void 0)return n}}function tn(t){let e=t.indent;return typeof e=="number"&&e>0?e:0}function Q(t){return t.direction==="rtl"?\' dir="rtl"\':t.direction==="ltr"?\' dir="ltr"\':""}function G(t){let e=en(t),n=tn(t),r=[];return e!==void 0&&r.push(`text-align:${e}`),n>0&&r.push(`padding-inline-start:${String(n*40)}px`),r.length===0?"":` style="${r.join(";")}"`}var no=Object.freeze({"&":"&","<":"<",">":">",\'"\':""","\'":"'","/":"/","`":"`","=":"="}),ro=/[&<>"\'/`=]/g;function g(t){return t===""?"":t.replace(ro,e=>no[e])}function Lr(t){return t.replace(/[\'"()\\\\]/g,"\\\\$&")}var io=Object.freeze({"&":"&","<":"<",">":">",\'"\':""","\'":"'"}),oo=/[&<>"\']/g;function L(t){return t===""?"":t.replace(oo,e=>io[e])}function It(t){return g(t).replace(/\\r\\n|\\r|\\n/g,"<br>")}var ee={BOLD:1,ITALIC:2,STRIKETHROUGH:4,UNDERLINE:8,CODE:16,SUBSCRIPT:32,SUPERSCRIPT:64,HIGHLIGHT:128};var nn=t=>{let e=typeof t.text=="string"?t.text:"",n=g(e),r=typeof t.format=="number"?t.format:0;return(r&ee.CODE)!==0&&(n=`<code>${n}</code>`),(r&ee.BOLD)!==0&&(n=`<strong>${n}</strong>`),(r&ee.ITALIC)!==0&&(n=`<em>${n}</em>`),(r&ee.UNDERLINE)!==0&&(n=`<u>${n}</u>`),(r&ee.STRIKETHROUGH)!==0&&(n=`<s>${n}</s>`),(r&ee.SUBSCRIPT)!==0&&(n=`<sub>${n}</sub>`),(r&ee.SUPERSCRIPT)!==0&&(n=`<sup>${n}</sup>`),(r&ee.HIGHLIGHT)!==0&&(n=`<mark>${n}</mark>`),n};p("text",nn);var rn=(t,e)=>{let n=e.renderChildren(t.children??[]);return`<p${Q(t)}${G(t)}>${n}</p>`};p("paragraph",rn);var so=new Set(["h1","h2","h3","h4","h5","h6"]),on=(t,e)=>{let n=typeof t.tag=="string"?t.tag.toLowerCase():"h2",r=so.has(n)?n:"h2",o=e.renderChildren(t.children??[]);return`<${r}${Q(t)}${G(t)}>${o}</${r}>`};p("heading",on);var sn=(t,e)=>{let n=typeof t.listType=="string"?t.listType:"bullet",r=n==="number"?"ol":"ul",o=n==="number"&&typeof t.start=="number"&&t.start>1?` start="${String(t.start)}"`:"",s=n==="check"?\' role="list"\':"",a=e.renderChildren(t.children??[]);return`<${r}${Q(t)}${G(t)}${o}${s}>${a}</${r}>`},an=(t,e)=>{let n=e.renderChildren(t.children??[]);if(typeof t.checked=="boolean")return`<li role="checkbox" aria-checked="${t.checked?"true":"false"}"${G(t)}>${n}</li>`;let r=typeof t.value=="number"?` value="${g(String(t.value))}"`:"";return`<li${Q(t)}${G(t)}${r}>${n}</li>`};p("list",sn);p("listitem",an);var ut=(t,e)=>{let n=typeof t.url=="string"?t.url:"",r=e.renderChildren(t.children??[]);if(n.length===0||!R(n))return r;let o=kt(n)?\' target="_blank" rel="noopener noreferrer"\':ao(t),s=typeof t.title=="string"?` title="${g(t.title)}"`:"";return`<a href="${L(n)}"${o}${s}>${r}</a>`};function ao(t){let e=typeof t.target=="string"?t.target:"";if(e.length===0)return"";let n=g(e);return e==="_blank"?\' target="_blank" rel="noopener noreferrer"\':` target="${n}"`}p("link",ut);p("autolink",ut);var dn=(t,e)=>{let n=e.renderChildren(t.children??[]);return`<blockquote${Q(t)}${G(t)}>${n}</blockquote>`};p("quote",dn);var ln=t=>{let e=Ir(typeof t.language=="string"?t.language:""),n=Mr(t);return`<pre><code${e===""?"":` class="language-${e}"`}>${g(n)}</code></pre>`},un=t=>{let e=typeof t.text=="string"?t.text:"";return`<span${typeof t.highlightType=="string"?` class="token-${Ir(t.highlightType)}"`:""}>${g(e)}</span>`};p("code",ln);p("code-highlight",un);function Mr(t){if(typeof t.text=="string")return t.text;if(!t.children)return"";let e="";for(let n of t.children)e+=Mr(n);return e}function Ir(t){return t.replace(/[^a-zA-Z0-9_-]/g,"")}var cn=()=>"<br>",fn=()=>"<hr>",pn=()=>" ";p("linebreak",cn);p("horizontalrule",fn);p("tab",pn);var mn=t=>{let e=lo(t);if(e===void 0)return"";let n=e.url;if(typeof n!="string"||!R(n))return"";let r=typeof e.mimeType=="string"?e.mimeType:"";return r.startsWith("video/")?co(e,n):r.startsWith("audio/")?fo(e,n):r===""||r.startsWith("image/")?uo(e,n):po(e,n)};p("upload",mn);function lo(t){let e=t.value;if(!(typeof e!="object"||e===null))return e}function uo(t,e){let n=typeof t.alt=="string"?g(t.alt):"",r=typeof t.width=="number"?` width="${String(t.width)}"`:"",o=typeof t.height=="number"?` height="${String(t.height)}"`:"";return`<img src="${L(e)}" alt="${n}"${r}${o} loading="lazy" decoding="async">`}function co(t,e){let n=typeof t.width=="number"?` width="${String(t.width)}"`:"",r=typeof t.height=="number"?` height="${String(t.height)}"`:"",o=typeof t.mimeType=="string"?` type="${L(t.mimeType)}"`:"";return`<video controls${n}${r}><source src="${L(e)}"${o}></video>`}function fo(t,e){let n=typeof t.mimeType=="string"?` type="${L(t.mimeType)}"`:"";return`<audio controls><source src="${L(e)}"${n}></audio>`}function po(t,e){let n=typeof t.filename=="string"?g(t.filename):g(e);return`<a href="${L(e)}" target="_blank" rel="noopener noreferrer">${n}</a>`}var hn=t=>{let e=typeof t.relationTo=="string"?t.relationTo:"",n=mo(t),r=ho(n,e),o=g(r),s=e===""?"":` data-relation-to="${g(e)}"`,a=n?.url;return typeof a=="string"&&R(a)?`<a href="${L(a)}"${s}>${o}</a>`:`<span${s}>${o}</span>`};p("relationship",hn);function mo(t){let e=t.value;if(!(typeof e!="object"||e===null))return e}function ho(t,e){return t===void 0?e===""?"#":`#${e}`:typeof t.title=="string"&&t.title.length>0?t.title:typeof t.name=="string"&&t.name.length>0?t.name:typeof t.slug=="string"&&t.slug.length>0?t.slug:t.id!==void 0?String(t.id):e===""?"#":`#${e}`}var yo=new Map;function yn(t){return yo.get(t)}var gn=(t,e)=>{let n=go(t),r=typeof n.blockType=="string"?n.blockType:"",o=Nr(r);if(r!==""){let u=yn(r)??yn(o);if(u)return u(n,{renderChildren:e.renderChildren})}let s=[];o!==""&&s.push(`data-block-type="${g(o)}"`);for(let[u,m]of Object.entries(n)){if(u==="blockType"||u==="id"||m==null)continue;let w=typeof m=="string"||typeof m=="number"||typeof m=="boolean"?String(m):JSON.stringify(m);s.push(`data-block-${Nr(u)}="${g(w)}"`)}return`<div${s.length===0?"":` ${s.join(" ")}`}></div>`};p("block",gn);function go(t){let e=t.fields;return typeof e!="object"||e===null?{}:e}function Nr(t){return t.replace(/[^a-zA-Z0-9_-]/g,"-").toLowerCase()}p("text",nn);p("paragraph",rn);p("heading",on);p("list",sn);p("listitem",an);p("link",ut);p("autolink",ut);p("quote",dn);p("code",ln);p("code-highlight",un);p("linebreak",cn);p("horizontalrule",fn);p("tab",pn);p("upload",mn);p("relationship",hn);p("block",gn);var bo={renderChildren:bn,resolveAlignment:en,resolveIndent:tn};function de(t){if(typeof t!="object"||t===null||!("root"in t))return!1;let e=t.root;return typeof e!="object"||e===null||!("children"in e)?!1:Array.isArray(e.children)}function _r(t,e={}){if(!de(t))return"";let n=bn(t.root.children);return e.sanitize===!1||!kr()?n:z(n)}function Nt(t){return de(t)?t.root.children.map(Cr).join(`\n`).trim():""}function bn(t){let e="";for(let n of t)e+=Ro(n);return e}function Ro(t){let e=Ar(t.type);return e?e(t,bo):t.children!==void 0?bn(t.children):""}function Cr(t){if(typeof t.text=="string")return t.text;if(t.type==="linebreak")return`\n`;if(t.children===void 0)return"";let e="";for(let n of t.children)e+=Cr(n);return e}var Rn=new Map;function Or(t=[]){for(let n of t)Rn.set(n.name,n);let e={};for(let[n,r]of Rn)e[n]=r;return Object.freeze(e)}function y(t){Rn.set(t.name,t)}var To="data-payload-text",Pr=new WeakSet,Tn={name:"text",render(t,e){let n=t.element,r=vo(e);if(n.tagName==="INPUT"||n.tagName==="TEXTAREA"){n.value=r;return}if(wo(n)&&!n.hasAttribute(To)){xo(n,t.fieldName);return}if(r.includes(`\n`)||r.includes("\\r")){n.innerHTML=It(r);return}n.textContent=r}};function wo(t){return t.firstElementChild!==null}function xo(t,e){Pr.has(t)||(Pr.add(t),console.warn(`[live-preview] Skipping text update for "${e}" \\u2014 the bound <${t.tagName.toLowerCase()}> has structured child elements. Either move data-payload-field to the element whose textContent is the field value, or opt in with data-payload-text to replace the structure.`))}function vo(t){return t==null?"":de(t)?Nt(t):typeof t=="string"?t:typeof t=="number"||typeof t=="boolean"?String(t):JSON.stringify(t)}y(Tn);function T(t){if(t==null)return"";if(typeof t=="string")return t;if(typeof t=="number"||typeof t=="boolean"||typeof t=="bigint")return String(t);try{return JSON.stringify(t)}catch{return""}}var wn={name:"textarea",render(t,e){let n=t.element,r=de(e)?Nt(e):T(e);if(n.tagName==="INPUT"||n.tagName==="TEXTAREA"){n.value=r;return}n.innerHTML=It(r)}};y(wn);var xn={name:"richText",render(t,e){if(de(e)){t.element.innerHTML=_r(e);return}typeof e=="string"&&(t.element.innerHTML=z(e))}};y(xn);var vn={name:"html",render(t,e){if(e==null){t.element.textContent="";return}let n=typeof e=="string"?e:T(e);t.element.innerHTML=z(n)}};y(vn);var ct={name:"url",render(t,e,n){let r=t.element,o=T(e);if(r.tagName==="A"){let s=r,a=t.hrefField,u=a?n.allFields[a]:e;typeof u=="string"&&R(u)&&(s.href=u),s.textContent=o;return}if(r.tagName==="INPUT"||r.tagName==="TEXTAREA"){r.value=o;return}r.textContent=o}};y(ct);y({...ct,name:"email"});var En={name:"image",render(t,e,n){let r=t.element,o=Eo(e),s=ko(o,e);if(s!==void 0){if(r.tagName==="IMG"){let a=r;a.src=s;let u=So(o,t.altField,n.allFields);u!==void 0&&(a.alt=u);return}r.style.backgroundImage=`url(\'${Lr(s)}\')`}}};function Eo(t){if(!(typeof t!="object"||t===null))return t}function ko(t,e){if(t?.url!==void 0&&R(t.url))return t.url;if(typeof e=="string"&&R(e))return e}function So(t,e,n){if(t?.alt!==void 0)return t.alt;if(e!==void 0){let r=n[e];if(typeof r=="string")return r}}y(En);var kn={name:"upload",render(t,e){let n=t.element,r=Ao(e);if(r===void 0)return;let o=r.url;if(typeof o!="string"||!R(o))return;if(n.tagName==="IMG"){let a=n;a.src=o,r.alt!==void 0&&(a.alt=r.alt);return}if(n.tagName==="A"){let a=n;a.href=o,a.textContent=r.filename??o;return}let s=r.filename!==void 0?g(r.filename):g(o);n.innerHTML=`<a href="${L(o)}">${s}</a>`}};function Ao(t){if(!(typeof t!="object"||t===null))return t}y(kn);var Sn={name:"relationship",render(t,e){let n=t.element,o=Lo(e).join(", ");if(n.tagName==="A"&&!Array.isArray(e)){let s=n,a=e?.url;typeof a=="string"&&R(a)&&(s.href=a),s.textContent=o;return}n.textContent=o}};function Lo(t){return t==null?[""]:Array.isArray(t)?t.map(Fr):[Fr(t)]}function Fr(t){if(typeof t=="object"&&t!==null){let e=t;return typeof e.title=="string"&&e.title.length>0?e.title:typeof e.name=="string"&&e.name.length>0?e.name:typeof e.slug=="string"&&e.slug.length>0?e.slug:e.id!==void 0?String(e.id):""}return T(t)}y(Sn);var ft={name:"select",render(t,e){let n=t.element,r=Dr(e);if(n.tagName==="SELECT"){n.value=r;return}if(n.tagName==="INPUT"&&n.type==="radio"){let o=n;o.checked=o.value===r;return}n.textContent=r}};function Dr(t){return Array.isArray(t)?t.map(e=>Dr(e)).join(", "):T(t)}y(ft);y({...ft,name:"radio"});var An={name:"checkbox",render(t,e){let n=t.element,r=!!e;if(n.tagName==="INPUT"&&n.type==="checkbox"){n.checked=r;return}if(n.hasAttribute("aria-checked")){n.setAttribute("aria-checked",r?"true":"false");return}n.textContent=r?"true":"false"}};y(An);var pt=new Map,mt=new Map,Mo=64,Io=Mo;function $r(t,e){let n=Hr(t,e),r=pt.get(n);if(r!==void 0)return pt.delete(n),pt.set(n,r),r;let o=new Intl.NumberFormat(t,e);return pt.set(n,o),Br(pt),o}function Ur(t,e){let n=Hr(t,e),r=mt.get(n);if(r!==void 0)return mt.delete(n),mt.set(n,r),r;let o=new Intl.DateTimeFormat(t,e);return mt.set(n,o),Br(mt),o}function Hr(t,e){let n=t??"";if(e==null)return n;let r=Ln(e);return r==="{}"?n:`${n}|${r}`}function Ln(t){if(t===null||typeof t!="object")return JSON.stringify(t);if(Array.isArray(t))return`[${t.map(o=>Ln(o)).join(",")}]`;let e=t,n=Object.keys(e).sort(),r=[];for(let o of n){let s=e[o];s!==void 0&&r.push(`${JSON.stringify(o)}:${Ln(s)}`)}return`{${r.join(",")}}`}function Br(t){for(;t.size>Io;){let e=t.keys().next().value;if(e===void 0)break;t.delete(e)}}var Mn={name:"date",render(t,e,n){let r=t.element;if(e==null||e===""){r.tagName==="INPUT"?r.value="":(r.tagName==="TIME"&&r.removeAttribute("datetime"),r.textContent="");return}let o=typeof e=="string"?e:T(e),s=new Date(o),a=!Number.isNaN(s.getTime());if(r.tagName==="INPUT"){r.value=a?No(r.type,s):o;return}if(r.tagName==="TIME"){r.setAttribute("datetime",a?s.toISOString():o),r.textContent=a?jr(s,n):o;return}r.textContent=a?jr(s,n):o}};function jr(t,e){let n=e.locale??Le();try{return Ur(n,{dateStyle:"medium",timeStyle:"short"}).format(t)}catch{return t.toISOString()}}function No(t,e){return t==="date"?e.toISOString().slice(0,10):t==="datetime-local"?e.toISOString().slice(0,16):e.toISOString()}y(Mn);var In={name:"number",render(t,e,n){let r=t.element;if(e==null||e===""){r.tagName==="INPUT"?r.value="":r.textContent="";return}let o=typeof e=="number"?e:Number(T(e));if(Number.isNaN(o)){r.textContent=T(e);return}if(r.tagName==="INPUT"){r.value=String(o);return}r.textContent=_o(o,n)}};function _o(t,e){let n=e.locale??Le();try{return $r(n).format(t)}catch{return String(t)}}y(In);var ht={name:"array",render(t,e){let n=t.element;if(!Array.isArray(e))return;let r=t.arrayTemplate;if(r!==void 0&&r.length>0){let s=Co(r,e);n.innerHTML=z(s);return}let o=t.arraySeparator??", ";n.textContent=e.map(Fo).join(o)}};function Co(t,e){let n="";for(let r=0;r<e.length;r+=1)n+=Oo(t,e[r],r);return n}function Oo(t,e,n){let r=t;if(typeof e=="object"&&e!==null)for(let[o,s]of Object.entries(e)){let a=new RegExp(`\\\\{\\\\{${Po(o)}\\\\}\\\\}`,"g");r=r.replace(a,g(T(s)))}else r=r.replace(/\\{\\{value\\}\\}/g,g(T(e)));return r=r.replace(/\\{\\{index\\}\\}/g,String(n)),r}function Po(t){return t.replace(/[.*+?^${}()|[\\]\\\\]/g,"\\\\$&")}function Fo(t){return T(t)}y(ht);y({...ht,name:"blocks"});function Do(){return typeof document>"u"?!1:typeof document.startViewTransition=="function"}async function Vr(t){if(!Do()){t();return}let n=document.startViewTransition.call(document,t);try{await n.finished}catch{}}var Yr="data-payload-key",yt="data-payload-nested-key",Cn="data-payload-nested-template",zr=new WeakMap;function Ct(t){let{template:e,container:n,patches:r}=t,o=Uo(n),s=[],a=[],u=[],m=[],w=[];for(let c of r)c.kind==="remove"?s.push(c):c.kind==="insert"?a.push(c):c.kind==="move"?u.push(c):c.kind==="update"?m.push(c):w.push(c);s.sort((c,b)=>_t(b)-_t(c));for(let c of s){if(c.kind!=="remove")continue;n.children[c.index]?.remove(),Ho(o,c.value)}for(let c of w){if(c.kind!=="replace")continue;let b=n.children[c.index],x=_n(e,c.value,c.index);!b||!x||(Gr(x,c.value),n.replaceChild(x,b),Nn(o,c.value))}for(let c of m){if(c.kind!=="update")continue;let b=n.children[c.index],x=_n(e,c.value,c.index);!b||!x||($o(b,x,c.value,o),n.replaceChild(x,b),Nn(o,c.value))}a.sort((c,b)=>_t(c)-_t(b));for(let c of a){if(c.kind!=="insert")continue;let b=n.children[c.index]??null,x=_n(e,c.value,c.index);x&&(Gr(x,c.value),n.insertBefore(x,b),Nn(o,c.value))}for(let c of u){if(c.kind!=="move")continue;let b=gt(c.value),x=b!==void 0?Bo(n,b):null;if(!x)continue;let Fe=n.children[c.to]??null;x!==Fe&&n.insertBefore(x,Fe)}}function Gr(t,e){let n=t.querySelectorAll(`[${yt}]`);for(let r of Array.from(n)){let o=r.getAttribute(yt);if(o===null)continue;let s=r.getAttribute(Cn);if(!s)continue;let a=On(e,o);if(a===void 0)continue;let u=rt([],a);u.length!==0&&Ct({template:s,container:r,patches:u,nextItems:a})}}function $o(t,e,n,r){let o=t.querySelectorAll(`[${yt}]`);if(o.length===0)return;let s=gt(n),a=s!==void 0?r.get(s):void 0;for(let u of Array.from(o)){let m=u.getAttribute(yt);if(m===null)continue;let w=e.querySelector(`[${yt}="${qr(m)}"]`);if(!w)continue;w.replaceWith(u);let c=u.getAttribute(Cn)??w.getAttribute(Cn);if(!c)continue;let b=On(n,m)??[],x=On(a,m)??[],Fe=rt(x,b);Fe.length!==0&&Ct({template:c,container:u,patches:Fe,nextItems:b})}}function On(t,e){if(typeof t!="object"||t===null)return;let n=t[e];if(Array.isArray(n))return n}function Uo(t){let e=zr.get(t);return e||(e=new Map,zr.set(t,e)),e}function Nn(t,e){let n=gt(e);n!==void 0&&t.set(n,e)}function Ho(t,e){let n=gt(e);n!==void 0&&t.delete(n)}function _t(t){return t.kind==="move"?t.to:t.index}function gt(t){if(typeof t!="object"||t===null)return;let e=t.id;if(typeof e=="string"||typeof e=="number")return String(e)}function Bo(t,e){return t.querySelector(`[${Yr}="${qr(e)}"]`)}function qr(t){let e=globalThis.CSS;return e?.escape?e.escape(t):t.replace(/[^a-zA-Z0-9_-]/g,"\\\\$&")}function _n(t,e,n){if(typeof document>"u")return null;let r=jo(t,e,n),o=z(r),s=document.createElement("template");s.innerHTML=o;let a=s.content.firstElementChild;if(!a)return null;let u=gt(e);return u!==void 0&&a.setAttribute(Yr,u),a}function jo(t,e,n){let r=t;if(typeof e=="object"&&e!==null)for(let[o,s]of Object.entries(e)){let a=new RegExp(`\\\\{\\\\{${Vo(o)}\\\\}\\\\}`,"g"),u=g(Wr(s));r=r.replace(a,()=>u)}else{let o=g(Wr(e));r=r.replace(/\\{\\{value\\}\\}/g,()=>o)}return r.replace(/\\{\\{index\\}\\}/g,String(n))}function Wr(t){return t==null?"":typeof t=="string"?t:typeof t=="number"||typeof t=="boolean"||typeof t=="bigint"?String(t):JSON.stringify(t)}function Vo(t){return t.replace(/[.*+?^${}()|[\\]\\\\]/g,"\\\\$&")}var Pn=new WeakMap,Kr=new WeakSet,Fn={name:"structural-array",render(t,e){if(!Array.isArray(e))return;let n=t.element,r=t.arrayTemplate;if(r===void 0||r.length===0){zo(n,t.fieldName);return}let o=Pn.get(n)??[],s=rt(o,e);if(s.length===0){Pn.set(n,e.slice());return}Vr(()=>{Ct({template:r,container:n,patches:s,nextItems:e})}),Pn.set(n,e.slice())}};function zo(t,e){Kr.has(t)||(Kr.add(t),console.warn(`[live-preview] Skipping structural update for "${e}" \\u2014 the <${t.tagName.toLowerCase()}> with data-payload-structural is missing data-payload-array-template. Add an inline template (e.g., data-payload-array-template="<li>{{label}}</li>") so the renderer can materialise new items.`))}y(Fn);var Go=[Tn,wn,xn,vn,ct,{...ct,name:"email"},En,kn,Sn,ft,{...ft,name:"radio"},An,Mn,In,ht,{...ht,name:"blocks"},Fn];function Xr(){return Or(Go)}function Wo(){if(typeof window>"u"||!Kt())return;let t=window.__livePreview;if(t!==void 0)return t;let e=Yo(),n=new Mt({additionalOrigins:e.additionalOrigins,enableReferrerDetection:!e.disableReferrerDetection,enableLocalhostMatching:!e.disableLocalhostMatching});n.isProductionUnconfigured?console.warn("[live-preview] No trusted origin could be detected. Set the PAYLOAD_ADMIN_ORIGIN env var or pass `allowedOrigins` to generateInlineScript()."):n.isReferrerOnlyTrust&&console.warn("[live-preview] Trusting the embedding page via document.referrer only \\u2014 any site that frames this page could post preview updates. Pass explicit `allowedOrigins` and serve a `frame-ancestors` CSP for production.");let r=new bt,o=Xr(),s=new Lt({renderers:o,originMatcher:u=>n.matches(u),readyTargets:n.enumerate(),emitter:r,...typeof e.serverURL=="string"&&e.serverURL!==""?{dataMerge:{serverURL:e.serverURL,...typeof e.apiRoute=="string"?{apiRoute:e.apiRoute}:{},...typeof e.mergeDepth=="number"?{depth:e.mergeDepth}:{}}}:{},debounceMs:e.debounceMs,heartbeatMs:e.heartbeatMs,intersectionRootMargin:e.intersectionRootMargin,disableVisibilityGate:e.disableVisibilityGate,visibilityGateThreshold:e.visibilityGateThreshold,enableA11y:e.enableA11y,onHeartbeatTimeout:()=>{n.unlockOrigin()},...e.debug?{log:(...u)=>{console.debug("[live-preview]",...u)}}:{}});r.on("connect",u=>{n.lockOrigin(u.origin)}),s.start();let a=Object.freeze({version:Rr,destroy:()=>{s.destroy()},refresh:()=>{s.refreshCache()},enumerateOrigins:()=>n.enumerate()});return Object.defineProperty(window,"__livePreview",{value:a,writable:!1,configurable:!0}),a}function Yo(){let t=typeof __LIVE_PREVIEW_CONFIG__>"u"?void 0:__LIVE_PREVIEW_CONFIG__;return t!==void 0?t:{additionalOrigins:[],serverURL:"",apiRoute:"/api",mergeDepth:1,debug:!1,debounceMs:50,enableA11y:!0,heartbeatMs:0,disableVisibilityGate:!1,visibilityGateThreshold:50,intersectionRootMargin:"200px",disableReferrerDetection:!1,disableLocalhostMatching:!1}}var qo=typeof __INLINE_BUILD__>"u"?!1:__INLINE_BUILD__;qo&&Wo();var Ol={isInIframe:dt,isInPopup:qt,isInPreviewContext:Kt};})();\n';
|
|
3552
|
+
var RUNTIME_BUILD_INFO = { generatedAt: "2026-07-11T16:51:06.280Z", size: 57982 };
|
|
3553
|
+
|
|
3554
|
+
// src/inline/generator.ts
|
|
3555
|
+
var DEFAULT_DEBOUNCE_MS3 = 50;
|
|
3556
|
+
var DEFAULT_HEARTBEAT_MS = 0;
|
|
3557
|
+
var DEFAULT_VISIBILITY_GATE_THRESHOLD = 50;
|
|
3558
|
+
var DEFAULT_INTERSECTION_ROOT_MARGIN = "200px";
|
|
3559
|
+
function generateInlineScript(config = {}) {
|
|
3560
|
+
if (RUNTIME_SOURCE.length === 0) {
|
|
3561
|
+
throw new Error(
|
|
3562
|
+
"[live-preview] runtime.generated.ts is empty. Run `npm run build:runtime` before bundling."
|
|
3563
|
+
);
|
|
3564
|
+
}
|
|
3565
|
+
const configLiteral = JSON.stringify({
|
|
3566
|
+
additionalOrigins: config.allowedOrigins ?? [],
|
|
3567
|
+
serverURL: config.serverURL ?? "",
|
|
3568
|
+
apiRoute: config.apiRoute ?? "/api",
|
|
3569
|
+
mergeDepth: config.mergeDepth ?? 1,
|
|
3570
|
+
debug: config.debug ?? false,
|
|
3571
|
+
debounceMs: config.debounceMs ?? DEFAULT_DEBOUNCE_MS3,
|
|
3572
|
+
enableA11y: config.enableA11y ?? true,
|
|
3573
|
+
heartbeatMs: config.heartbeatMs ?? DEFAULT_HEARTBEAT_MS,
|
|
3574
|
+
disableVisibilityGate: config.disableVisibilityGate ?? false,
|
|
3575
|
+
visibilityGateThreshold: config.visibilityGateThreshold ?? DEFAULT_VISIBILITY_GATE_THRESHOLD,
|
|
3576
|
+
intersectionRootMargin: config.intersectionRootMargin ?? DEFAULT_INTERSECTION_ROOT_MARGIN,
|
|
3577
|
+
disableReferrerDetection: config.disableReferrerDetection ?? false,
|
|
3578
|
+
disableLocalhostMatching: config.disableLocalhostMatching ?? false
|
|
3579
|
+
// `<` must never appear literally inside an inline <script> body —
|
|
3580
|
+
// a consumer-supplied string containing `</script>` would otherwise
|
|
3581
|
+
// terminate the tag early.
|
|
3582
|
+
}).replace(/</g, "\\u003C");
|
|
3583
|
+
const generatedAt = RUNTIME_BUILD_INFO.generatedAt === "" ? "dev" : RUNTIME_BUILD_INFO.generatedAt;
|
|
3584
|
+
return [
|
|
3585
|
+
`/* payload-live-preview runtime ${generatedAt} */`,
|
|
3586
|
+
`var __LIVE_PREVIEW_CONFIG__=${configLiteral};`,
|
|
3587
|
+
`var __INLINE_BUILD__=true;`,
|
|
3588
|
+
RUNTIME_SOURCE
|
|
3589
|
+
].join("\n");
|
|
3590
|
+
}
|
|
3591
|
+
function wrapWithScriptTag(body, options = {}) {
|
|
3592
|
+
const nonceAttr = options.nonce !== void 0 ? ` nonce="${escapeNonce(options.nonce)}"` : "";
|
|
3593
|
+
return `<script${nonceAttr}>${body}</script>`;
|
|
3594
|
+
}
|
|
3595
|
+
function runtimeBuildInfo() {
|
|
3596
|
+
return RUNTIME_BUILD_INFO;
|
|
3597
|
+
}
|
|
3598
|
+
function escapeNonce(nonce) {
|
|
3599
|
+
if (!/^[A-Za-z0-9+/=_-]+$/.test(nonce)) {
|
|
3600
|
+
throw new RangeError("wrapWithScriptTag: nonce contains invalid characters");
|
|
3601
|
+
}
|
|
3602
|
+
return nonce;
|
|
3603
|
+
}
|
|
3604
|
+
|
|
3605
|
+
// src/security/csp.ts
|
|
3606
|
+
var DEFAULT_NONCE_BYTES = 16;
|
|
3607
|
+
var cryptoOverride;
|
|
3608
|
+
function setCspCrypto(crypto) {
|
|
3609
|
+
cryptoOverride = crypto ?? void 0;
|
|
3610
|
+
}
|
|
3611
|
+
function generateCspNonce(bytes = DEFAULT_NONCE_BYTES) {
|
|
3612
|
+
if (!Number.isInteger(bytes) || bytes < 8) {
|
|
3613
|
+
throw new RangeError(`generateCspNonce: bytes must be an integer >= 8, got ${String(bytes)}`);
|
|
3614
|
+
}
|
|
3615
|
+
const crypto = resolveCrypto();
|
|
3616
|
+
if (!crypto) {
|
|
3617
|
+
throw new Error(
|
|
3618
|
+
"generateCspNonce: Web Crypto is unavailable. On Node 18 call setCspCrypto(webcrypto) once at startup, or run with --experimental-global-webcrypto. We won't fabricate a predictable nonce \u2014 that would silently defeat the entire CSP."
|
|
3619
|
+
);
|
|
3620
|
+
}
|
|
3621
|
+
const buffer = new Uint8Array(bytes);
|
|
3622
|
+
crypto.getRandomValues(buffer);
|
|
3623
|
+
return base64UrlEncode(buffer);
|
|
3624
|
+
}
|
|
3625
|
+
function resolveCrypto() {
|
|
3626
|
+
if (cryptoOverride?.getRandomValues) return cryptoOverride;
|
|
3627
|
+
const fromGlobal = globalThis.crypto;
|
|
3628
|
+
if (fromGlobal?.getRandomValues) return fromGlobal;
|
|
3629
|
+
return void 0;
|
|
3630
|
+
}
|
|
3631
|
+
function base64UrlEncode(bytes) {
|
|
3632
|
+
let binary = "";
|
|
3633
|
+
for (const byte of bytes) binary += String.fromCharCode(byte);
|
|
3634
|
+
const base64 = typeof btoa === "function" ? btoa(binary) : Buffer.from(bytes).toString("base64");
|
|
3635
|
+
return base64.replaceAll("+", "-").replaceAll("/", "_").replaceAll("=", "");
|
|
3636
|
+
}
|
|
3637
|
+
function buildFrameAncestors(options = {}) {
|
|
3638
|
+
const { self = true, origins = [], allowNone = false } = options;
|
|
3639
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3640
|
+
const out = [];
|
|
3641
|
+
if (allowNone && !self && origins.length === 0) return "'none'";
|
|
3642
|
+
if (self) {
|
|
3643
|
+
out.push("'self'");
|
|
3644
|
+
seen.add("'self'");
|
|
3645
|
+
}
|
|
3646
|
+
for (const origin of origins) {
|
|
3647
|
+
const trimmed = origin.trim();
|
|
3648
|
+
if (trimmed.length === 0 || seen.has(trimmed)) continue;
|
|
3649
|
+
seen.add(trimmed);
|
|
3650
|
+
out.push(trimmed);
|
|
3651
|
+
}
|
|
3652
|
+
return out.length > 0 ? out.join(" ") : "'none'";
|
|
3653
|
+
}
|
|
3654
|
+
function buildScriptSrcWithNonce(nonce, options = {}) {
|
|
3655
|
+
if (nonce.length === 0) throw new RangeError("buildScriptSrcWithNonce: nonce is empty");
|
|
3656
|
+
const { self = true, extra = [], strictDynamic = false } = options;
|
|
3657
|
+
const parts = [];
|
|
3658
|
+
if (self) parts.push("'self'");
|
|
3659
|
+
parts.push(`'nonce-${nonce}'`);
|
|
3660
|
+
if (strictDynamic) parts.push("'strict-dynamic'");
|
|
3661
|
+
for (const e of extra) {
|
|
3662
|
+
const trimmed = e.trim();
|
|
3663
|
+
if (trimmed.length > 0) parts.push(trimmed);
|
|
3664
|
+
}
|
|
3665
|
+
return parts.join(" ");
|
|
3666
|
+
}
|
|
3667
|
+
function mergeCspHeader(existing, additions) {
|
|
3668
|
+
const directives = /* @__PURE__ */ new Map();
|
|
3669
|
+
for (const part of existing.split(";")) {
|
|
3670
|
+
const trimmed = part.trim();
|
|
3671
|
+
if (trimmed.length === 0) continue;
|
|
3672
|
+
const spaceIndex = trimmed.indexOf(" ");
|
|
3673
|
+
if (spaceIndex < 0) {
|
|
3674
|
+
directives.set(trimmed.toLowerCase(), "");
|
|
3675
|
+
continue;
|
|
3676
|
+
}
|
|
3677
|
+
directives.set(
|
|
3678
|
+
trimmed.slice(0, spaceIndex).toLowerCase(),
|
|
3679
|
+
trimmed.slice(spaceIndex + 1).trim()
|
|
3680
|
+
);
|
|
3681
|
+
}
|
|
3682
|
+
for (const [rawName, rawAddition] of Object.entries(additions)) {
|
|
3683
|
+
const name = rawName.toLowerCase();
|
|
3684
|
+
const addition = typeof rawAddition === "string" ? { value: rawAddition } : rawAddition;
|
|
3685
|
+
const mode = addition.mode ?? "union";
|
|
3686
|
+
const current = directives.get(name);
|
|
3687
|
+
if (mode === "replace" || current === void 0 || current.length === 0) {
|
|
3688
|
+
directives.set(name, addition.value);
|
|
3689
|
+
continue;
|
|
3690
|
+
}
|
|
3691
|
+
const seen = /* @__PURE__ */ new Set();
|
|
3692
|
+
const merged = [];
|
|
3693
|
+
for (const token of [...current.split(/\s+/), ...addition.value.split(/\s+/)]) {
|
|
3694
|
+
if (token.length === 0 || seen.has(token)) continue;
|
|
3695
|
+
seen.add(token);
|
|
3696
|
+
merged.push(token);
|
|
3697
|
+
}
|
|
3698
|
+
const withoutNone = merged.filter((token) => token !== "'none'");
|
|
3699
|
+
directives.set(name, (withoutNone.length > 0 ? withoutNone : merged).join(" "));
|
|
3700
|
+
}
|
|
3701
|
+
const out = [];
|
|
3702
|
+
for (const [name, value] of directives) {
|
|
3703
|
+
out.push(value.length === 0 ? name : `${name} ${value}`);
|
|
3704
|
+
}
|
|
3705
|
+
return out.join("; ");
|
|
3706
|
+
}
|
|
3707
|
+
|
|
3708
|
+
// src/adapters/shared/preview-request.ts
|
|
3709
|
+
var DEFAULT_QUERY_PARAMS = ["preview", "draft", "livePreview"];
|
|
3710
|
+
function isPreviewRequest(request, options = {}) {
|
|
3711
|
+
const signals = new Set(options.signals ?? ["query", "fetch-dest", "referer"]);
|
|
3712
|
+
const queryParams = options.queryParams ?? DEFAULT_QUERY_PARAMS;
|
|
3713
|
+
const checkFetchDest = (options.checkFetchDest ?? true) && signals.has("fetch-dest");
|
|
3714
|
+
if (signals.has("query")) {
|
|
3715
|
+
let url;
|
|
3716
|
+
try {
|
|
3717
|
+
url = new URL(request.url);
|
|
3718
|
+
} catch {
|
|
3719
|
+
url = void 0;
|
|
3720
|
+
}
|
|
3721
|
+
if (url !== void 0) {
|
|
3722
|
+
for (const param of queryParams) {
|
|
3723
|
+
const value = url.searchParams.get(param);
|
|
3724
|
+
if (value === "true" || value === "1") return true;
|
|
3725
|
+
}
|
|
3726
|
+
}
|
|
3727
|
+
}
|
|
3728
|
+
if (checkFetchDest && request.headers.get("sec-fetch-dest") === "iframe") {
|
|
3729
|
+
return true;
|
|
3730
|
+
}
|
|
3731
|
+
const adminOrigins = signals.has("referer") ? options.adminOrigins ?? [] : [];
|
|
3732
|
+
if (adminOrigins.length > 0) {
|
|
3733
|
+
const referer = request.headers.get("referer");
|
|
3734
|
+
if (referer !== null) {
|
|
3735
|
+
try {
|
|
3736
|
+
const refererOrigin = new URL(referer).origin;
|
|
3737
|
+
for (const adminOrigin of adminOrigins) {
|
|
3738
|
+
try {
|
|
3739
|
+
if (new URL(adminOrigin).origin === refererOrigin) return true;
|
|
3740
|
+
} catch {
|
|
3741
|
+
}
|
|
3742
|
+
}
|
|
3743
|
+
} catch {
|
|
3744
|
+
}
|
|
3745
|
+
}
|
|
3746
|
+
}
|
|
3747
|
+
return false;
|
|
3748
|
+
}
|
|
3749
|
+
|
|
3750
|
+
// src/preview-fetch.ts
|
|
3751
|
+
async function fetchPreviewDocument(options) {
|
|
3752
|
+
const params = baseParams(options);
|
|
3753
|
+
if (options.id !== void 0) {
|
|
3754
|
+
const url2 = `${apiBase(options)}/${encodeURIComponent(options.collection)}/${encodeURIComponent(
|
|
3755
|
+
String(options.id)
|
|
3756
|
+
)}?${params.toString()}`;
|
|
3757
|
+
return requestJson(url2, options);
|
|
3758
|
+
}
|
|
3759
|
+
if (options.where !== void 0) {
|
|
3760
|
+
appendWhere(params, options.where, ["where"]);
|
|
3761
|
+
}
|
|
3762
|
+
params.set("limit", "1");
|
|
3763
|
+
const url = `${apiBase(options)}/${encodeURIComponent(options.collection)}?${params.toString()}`;
|
|
3764
|
+
const page = await requestJson(url, options);
|
|
3765
|
+
const first = page?.docs?.[0];
|
|
3766
|
+
return first ?? null;
|
|
3767
|
+
}
|
|
3768
|
+
async function fetchPreviewGlobal(options) {
|
|
3769
|
+
const params = baseParams(options);
|
|
3770
|
+
const url = `${apiBase(options)}/globals/${encodeURIComponent(options.global)}?${params.toString()}`;
|
|
3771
|
+
return requestJson(url, options);
|
|
3772
|
+
}
|
|
3773
|
+
function apiBase(options) {
|
|
3774
|
+
const origin = options.serverURL.replace(/\/+$/, "");
|
|
3775
|
+
const route = options.apiRoute ?? "/api";
|
|
3776
|
+
return `${origin}${route.startsWith("/") ? route : `/${route}`}`;
|
|
3777
|
+
}
|
|
3778
|
+
function baseParams(options) {
|
|
3779
|
+
const params = new URLSearchParams();
|
|
3780
|
+
params.set("depth", String(options.depth ?? 1));
|
|
3781
|
+
if (options.draft ?? true) params.set("draft", "true");
|
|
3782
|
+
if (options.locale !== void 0) params.set("locale", options.locale);
|
|
3783
|
+
return params;
|
|
3784
|
+
}
|
|
3785
|
+
function appendWhere(params, node, path) {
|
|
3786
|
+
for (const [key, value] of Object.entries(node)) {
|
|
3787
|
+
const nextPath = [...path, key];
|
|
3788
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
3789
|
+
appendWhere(params, value, nextPath);
|
|
3790
|
+
continue;
|
|
3791
|
+
}
|
|
3792
|
+
const name = nextPath.map((seg, i) => i === 0 ? seg : `[${seg}]`).join("");
|
|
3793
|
+
if (Array.isArray(value)) {
|
|
3794
|
+
params.set(name, value.map(String).join(","));
|
|
3795
|
+
} else {
|
|
3796
|
+
params.set(name, String(value));
|
|
3797
|
+
}
|
|
3798
|
+
}
|
|
3799
|
+
}
|
|
3800
|
+
async function requestJson(url, options) {
|
|
3801
|
+
const fetchFn = options.fetchFn ?? (typeof fetch === "function" ? fetch : void 0);
|
|
3802
|
+
if (fetchFn === void 0) return null;
|
|
3803
|
+
try {
|
|
3804
|
+
const response = await fetchFn(url, {
|
|
3805
|
+
credentials: "include",
|
|
3806
|
+
headers: {
|
|
3807
|
+
Accept: "application/json",
|
|
3808
|
+
...options.headers ?? {}
|
|
3809
|
+
}
|
|
3810
|
+
});
|
|
3811
|
+
if (!response.ok) return null;
|
|
3812
|
+
const body = await response.json();
|
|
3813
|
+
if (body === null || typeof body !== "object") return null;
|
|
3814
|
+
return body;
|
|
3815
|
+
} catch {
|
|
3816
|
+
return null;
|
|
3817
|
+
}
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
// src/plugins/built-in/highlight.ts
|
|
3821
|
+
var STYLE_ID = "payload-live-preview-highlight";
|
|
3822
|
+
var REDUCED_MOTION_CSS = ".lp-highlight{outline:2px solid rgba(0,102,204,0.85);outline-offset:2px;}";
|
|
3823
|
+
var ANIMATED_CSS = "@keyframes lp-highlight{0%{outline:2px solid rgba(0,102,204,0.85);outline-offset:2px}100%{outline:2px solid transparent;outline-offset:2px}}.lp-highlight{animation:lp-highlight 0.6s ease-out;}";
|
|
3824
|
+
var highlightPlugin = {
|
|
3825
|
+
name: "highlight",
|
|
3826
|
+
version: "1.0.0",
|
|
3827
|
+
init: (ctx) => {
|
|
3828
|
+
if (typeof document === "undefined") return;
|
|
3829
|
+
const prefersReducedMotion = typeof window !== "undefined" && typeof window.matchMedia === "function" && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
|
|
3830
|
+
if (!document.getElementById(STYLE_ID)) {
|
|
3831
|
+
const style = document.createElement("style");
|
|
3832
|
+
style.id = STYLE_ID;
|
|
3833
|
+
style.textContent = prefersReducedMotion ? REDUCED_MOTION_CSS : ANIMATED_CSS;
|
|
3834
|
+
document.head.appendChild(style);
|
|
3835
|
+
}
|
|
3836
|
+
const duration = prefersReducedMotion ? 1e3 : 600;
|
|
3837
|
+
ctx.events.on("elementUpdate", (e) => {
|
|
3838
|
+
const element = e.element;
|
|
3839
|
+
element.classList.add("lp-highlight");
|
|
3840
|
+
window.setTimeout(() => {
|
|
3841
|
+
element.classList.remove("lp-highlight");
|
|
3842
|
+
}, duration);
|
|
3843
|
+
});
|
|
3844
|
+
},
|
|
3845
|
+
destroy: () => {
|
|
3846
|
+
if (typeof document === "undefined") return;
|
|
3847
|
+
document.getElementById(STYLE_ID)?.remove();
|
|
3848
|
+
}
|
|
3849
|
+
};
|
|
3850
|
+
|
|
3851
|
+
// src/plugins/built-in/debug.ts
|
|
3852
|
+
var debugPlugin = {
|
|
3853
|
+
name: "debug",
|
|
3854
|
+
version: "1.0.0",
|
|
3855
|
+
init: (ctx) => {
|
|
3856
|
+
ctx.events.on("init", () => {
|
|
3857
|
+
ctx.log("init");
|
|
3858
|
+
});
|
|
3859
|
+
ctx.events.on("connect", (e) => {
|
|
3860
|
+
ctx.log("connect \u2190", e.origin);
|
|
3861
|
+
});
|
|
3862
|
+
ctx.events.on("disconnect", (e) => {
|
|
3863
|
+
ctx.log("disconnect (", e.reason, ")");
|
|
3864
|
+
});
|
|
3865
|
+
ctx.events.on("cacheRefresh", (e) => {
|
|
3866
|
+
ctx.log("cacheRefresh", e.elementCount, "elements,", e.fieldCount, "fields");
|
|
3867
|
+
});
|
|
3868
|
+
ctx.events.on("beforeUpdate", (e) => {
|
|
3869
|
+
ctx.log("beforeUpdate", Object.keys(e.data.fields).length, "fields");
|
|
3870
|
+
});
|
|
3871
|
+
ctx.events.on("afterUpdate", (e) => {
|
|
3872
|
+
ctx.log("afterUpdate", e.updatedCount, "in", e.durationMs.toFixed(2), "ms");
|
|
3873
|
+
});
|
|
3874
|
+
ctx.events.on("error", (e) => {
|
|
3875
|
+
ctx.log("error in", e.context, ":", e.error.message);
|
|
3876
|
+
});
|
|
3877
|
+
}
|
|
3878
|
+
};
|
|
3879
|
+
|
|
3880
|
+
// src/plugins/built-in/analytics.ts
|
|
3881
|
+
function createAnalyticsPlugin() {
|
|
3882
|
+
let updateCount = 0;
|
|
3883
|
+
let totalElements = 0;
|
|
3884
|
+
let totalDurationMs = 0;
|
|
3885
|
+
return {
|
|
3886
|
+
name: "analytics",
|
|
3887
|
+
version: "1.0.0",
|
|
3888
|
+
init: (ctx) => {
|
|
3889
|
+
ctx.events.on("afterUpdate", (e) => {
|
|
3890
|
+
updateCount += 1;
|
|
3891
|
+
totalElements += e.updatedCount;
|
|
3892
|
+
totalDurationMs += e.durationMs;
|
|
3893
|
+
});
|
|
3894
|
+
},
|
|
3895
|
+
getStats: () => ({
|
|
3896
|
+
updateCount,
|
|
3897
|
+
totalElements,
|
|
3898
|
+
totalDurationMs,
|
|
3899
|
+
averageDurationMs: updateCount === 0 ? 0 : totalDurationMs / updateCount
|
|
3900
|
+
})
|
|
3901
|
+
};
|
|
3902
|
+
}
|
|
3903
|
+
|
|
3904
|
+
// src/plugins/built-in/document-save.ts
|
|
3905
|
+
var SCROLL_KEY = "payload-live-preview:scroll";
|
|
3906
|
+
function saveScrollPosition() {
|
|
3907
|
+
if (typeof window === "undefined") return;
|
|
3908
|
+
try {
|
|
3909
|
+
sessionStorage.setItem(
|
|
3910
|
+
SCROLL_KEY,
|
|
3911
|
+
JSON.stringify({ href: window.location.href, x: window.scrollX, y: window.scrollY })
|
|
3912
|
+
);
|
|
3913
|
+
} catch {
|
|
3914
|
+
}
|
|
3915
|
+
}
|
|
3916
|
+
function restoreScrollPosition() {
|
|
3917
|
+
if (typeof window === "undefined") return;
|
|
3918
|
+
try {
|
|
3919
|
+
const raw = sessionStorage.getItem(SCROLL_KEY);
|
|
3920
|
+
if (raw === null) return;
|
|
3921
|
+
sessionStorage.removeItem(SCROLL_KEY);
|
|
3922
|
+
const saved = JSON.parse(raw);
|
|
3923
|
+
if (saved.href !== window.location.href) return;
|
|
3924
|
+
window.scrollTo(saved.x ?? 0, saved.y ?? 0);
|
|
3925
|
+
} catch {
|
|
3926
|
+
}
|
|
3927
|
+
}
|
|
3928
|
+
function reloadPreservingScroll() {
|
|
3929
|
+
if (typeof window === "undefined") return;
|
|
3930
|
+
saveScrollPosition();
|
|
3931
|
+
window.location.reload();
|
|
3932
|
+
}
|
|
3933
|
+
var DEFAULT_REVALIDATE_URL = "/api/revalidate";
|
|
3934
|
+
function documentSavePlugin(options = {}) {
|
|
3935
|
+
const strategy = options.strategy ?? "silent";
|
|
3936
|
+
return {
|
|
3937
|
+
name: "document-save",
|
|
3938
|
+
version: "1.1.0",
|
|
3939
|
+
init: (ctx) => {
|
|
3940
|
+
restoreScrollPosition();
|
|
3941
|
+
ctx.events.on("documentSave", () => {
|
|
3942
|
+
run(strategy, options, ctx.log);
|
|
3943
|
+
});
|
|
3944
|
+
}
|
|
3945
|
+
};
|
|
3946
|
+
}
|
|
3947
|
+
function run(strategy, options, log) {
|
|
3948
|
+
if (strategy === "silent") return;
|
|
3949
|
+
if (strategy === "reload") {
|
|
3950
|
+
reloadPreservingScroll();
|
|
3951
|
+
return;
|
|
3952
|
+
}
|
|
3953
|
+
if (strategy === "fetch") {
|
|
3954
|
+
void runFetch(options, log);
|
|
3955
|
+
return;
|
|
3956
|
+
}
|
|
3957
|
+
void runRevalidate(options, log);
|
|
3958
|
+
}
|
|
3959
|
+
async function runFetch(options, log) {
|
|
3960
|
+
if (!options.handler) {
|
|
3961
|
+
log("document-save: fetch strategy selected but no handler supplied");
|
|
3962
|
+
return;
|
|
3963
|
+
}
|
|
3964
|
+
try {
|
|
3965
|
+
await options.handler();
|
|
3966
|
+
} catch (err) {
|
|
3967
|
+
log("document-save handler threw:", err);
|
|
3968
|
+
}
|
|
3969
|
+
}
|
|
3970
|
+
async function runRevalidate(options, log) {
|
|
3971
|
+
if (typeof fetch === "undefined") {
|
|
3972
|
+
log("document-save: revalidate strategy needs fetch \u2014 not available");
|
|
3973
|
+
return;
|
|
3974
|
+
}
|
|
3975
|
+
const url = options.revalidateUrl ?? DEFAULT_REVALIDATE_URL;
|
|
3976
|
+
let ok = false;
|
|
3977
|
+
try {
|
|
3978
|
+
const response = await fetch(url, {
|
|
3979
|
+
method: "POST",
|
|
3980
|
+
headers: {
|
|
3981
|
+
"Content-Type": "application/json",
|
|
3982
|
+
...options.revalidateHeaders ?? {}
|
|
3983
|
+
},
|
|
3984
|
+
body: JSON.stringify({ source: "payload-live-preview" })
|
|
3985
|
+
});
|
|
3986
|
+
ok = response.ok;
|
|
3987
|
+
if (!ok) log("document-save revalidate non-2xx:", response.status);
|
|
3988
|
+
} catch (err) {
|
|
3989
|
+
log("document-save revalidate failed:", err);
|
|
3990
|
+
}
|
|
3991
|
+
if (!ok && options.onRevalidateFailure === "reload") {
|
|
3992
|
+
reloadPreservingScroll();
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3996
|
+
// src/dsl/bind.ts
|
|
3997
|
+
function bind(field, options) {
|
|
3998
|
+
return buildAttributes(field, options);
|
|
3999
|
+
}
|
|
4000
|
+
function bindByPath(picker, options) {
|
|
4001
|
+
const path = recordPath(picker);
|
|
4002
|
+
if (path.length === 0) {
|
|
4003
|
+
throw new Error(
|
|
4004
|
+
"bindByPath: the picker did not read any field \u2014 return data.<field> instead of a constant"
|
|
4005
|
+
);
|
|
4006
|
+
}
|
|
4007
|
+
return buildAttributes(path.join("."), options);
|
|
4008
|
+
}
|
|
4009
|
+
function buildAttributes(field, options) {
|
|
4010
|
+
if (field.length === 0) {
|
|
4011
|
+
throw new Error("bind: field name must be a non-empty string");
|
|
4012
|
+
}
|
|
4013
|
+
const attrs = { "data-payload-field": field };
|
|
4014
|
+
if (options?.attribute !== void 0) attrs["data-payload-attribute"] = options.attribute;
|
|
4015
|
+
if (options?.type !== void 0) attrs["data-payload-type"] = options.type;
|
|
4016
|
+
return attrs;
|
|
4017
|
+
}
|
|
4018
|
+
function recordPath(picker) {
|
|
4019
|
+
const path = [];
|
|
4020
|
+
const proxy = new Proxy(
|
|
4021
|
+
{},
|
|
4022
|
+
{
|
|
4023
|
+
get(_target, prop) {
|
|
4024
|
+
if (typeof prop === "symbol") return void 0;
|
|
4025
|
+
if (/^\d+$/.test(prop)) return proxy;
|
|
4026
|
+
path.push(prop);
|
|
4027
|
+
return proxy;
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
);
|
|
4031
|
+
try {
|
|
4032
|
+
picker(proxy);
|
|
4033
|
+
} catch {
|
|
4034
|
+
}
|
|
4035
|
+
return path;
|
|
4036
|
+
}
|
|
4037
|
+
|
|
4038
|
+
export { EventEmitter, LIBRARY_PROTOCOL_VERSION, LivePreviewClient, OriginDetector, VERSION, bind, bindByPath, buildBuiltinRenderers2 as 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 };
|
|
4039
|
+
//# sourceMappingURL=index.js.map
|
|
4040
|
+
//# sourceMappingURL=index.js.map
|