@preprio/toolkit 0.5.0-rc.1 → 0.5.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/README.md +416 -23
  2. package/dist/astro/index.cjs +1290 -1
  3. package/dist/astro/index.d.cts +47 -1
  4. package/dist/astro/index.d.ts +47 -1
  5. package/dist/astro/index.js +1297 -1
  6. package/dist/chunk-F46GJS6Y.js +26 -0
  7. package/dist/{chunk-4JNKYIEV.js → chunk-IDEC77LZ.js} +1423 -642
  8. package/dist/chunk-IPMB32AZ.js +246 -0
  9. package/dist/chunk-KD3GK6TO.js +1221 -0
  10. package/dist/chunk-S3VT4UWM.js +75 -0
  11. package/dist/chunk-TBBU3LDD.js +184 -0
  12. package/dist/chunk-URXSDJOU.js +356 -0
  13. package/dist/codegen/index.cjs +82 -0
  14. package/dist/codegen/index.d.cts +70 -0
  15. package/dist/codegen/index.d.ts +70 -0
  16. package/dist/codegen/index.js +64 -0
  17. package/dist/index.cjs +3342 -417
  18. package/dist/index.d.cts +276 -5
  19. package/dist/index.d.ts +276 -5
  20. package/dist/index.js +307 -144
  21. package/dist/live-preview-DLXGXRE5.js +18 -0
  22. package/dist/nextjs/components.cjs +1582 -386
  23. package/dist/nextjs/components.d.cts +1 -1
  24. package/dist/nextjs/components.d.ts +1 -1
  25. package/dist/nextjs/components.js +4 -1
  26. package/dist/nextjs/index.cjs +1181 -6
  27. package/dist/nextjs/index.d.cts +83 -3
  28. package/dist/nextjs/index.d.ts +83 -3
  29. package/dist/nextjs/index.js +1179 -7
  30. package/dist/nextjs/live-action.cjs +285 -0
  31. package/dist/nextjs/live-action.d.cts +12 -0
  32. package/dist/nextjs/live-action.d.ts +12 -0
  33. package/dist/nextjs/live-action.js +68 -0
  34. package/dist/nextjs/live.cjs +1899 -0
  35. package/dist/nextjs/live.d.cts +54 -0
  36. package/dist/nextjs/live.d.ts +54 -0
  37. package/dist/nextjs/live.js +268 -0
  38. package/dist/nuxt/chunk-BWPGO25E.js +340 -0
  39. package/dist/nuxt/index.cjs +1 -1
  40. package/dist/nuxt/index.js +1 -1
  41. package/dist/nuxt/live-preview-2RCU35W5.js +1189 -0
  42. package/dist/nuxt/live.cjs +1816 -0
  43. package/dist/nuxt/live.d.cts +43 -0
  44. package/dist/nuxt/live.d.ts +43 -0
  45. package/dist/nuxt/live.js +198 -0
  46. package/dist/{pixel-e1fUWl2Y.d.cts → pixel-CX422dCO.d.cts} +18 -3
  47. package/dist/{pixel-e1fUWl2Y.d.ts → pixel-CX422dCO.d.ts} +18 -3
  48. package/dist/react/components.cjs +1582 -386
  49. package/dist/react/components.d.cts +17 -2
  50. package/dist/react/components.d.ts +17 -2
  51. package/dist/react/components.js +1582 -386
  52. package/dist/sveltekit/chunk-BWPGO25E.js +340 -0
  53. package/dist/sveltekit/index.cjs +1 -1
  54. package/dist/sveltekit/index.js +1 -1
  55. package/dist/sveltekit/live-preview-2RCU35W5.js +1189 -0
  56. package/dist/sveltekit/live.cjs +1830 -0
  57. package/dist/sveltekit/live.d.cts +59 -0
  58. package/dist/sveltekit/live.d.ts +59 -0
  59. package/dist/sveltekit/live.js +204 -0
  60. package/package.json +37 -11
  61. package/src/astro/PreprToolbar.astro +7 -1
package/dist/index.cjs CHANGED
@@ -23,6 +23,7 @@ __export(src_exports, {
23
23
  PreprError: () => PreprError,
24
24
  PreprToolbarElement: () => PreprToolbarElement,
25
25
  VERSION: () => VERSION,
26
+ annotateEditLinks: () => annotateEditLinks,
26
27
  createPreprPreview: () => createPreprPreview,
27
28
  createToolbarStore: () => createToolbarStore,
28
29
  definePreprToolbar: () => definePreprToolbar,
@@ -34,17 +35,23 @@ __export(src_exports, {
34
35
  getPreprUUIDFromHeaders: () => getPreprUUIDFromHeaders,
35
36
  getToolbarPropsFromHeaders: () => getToolbarPropsFromHeaders,
36
37
  loadTrackingPixel: () => loadTrackingPixel,
38
+ normalizeQuery: () => normalizeQuery,
39
+ parsePatchLog: () => parsePatchLog,
40
+ previewAttrs: () => previewAttrs,
37
41
  processPreprRequest: () => processPreprRequest,
42
+ readLiveLogFromRequest: () => readLiveLogFromRequest,
38
43
  resolveFeatures: () => resolveFeatures,
39
44
  setTrackingParam: () => setTrackingParam,
45
+ startHtmlLivePreview: () => startHtmlLivePreview,
40
46
  stegaClean: () => stegaClean,
47
+ stegaTarget: () => stegaTarget,
41
48
  trackEvent: () => trackEvent,
42
49
  validatePreprToken: () => validatePreprToken
43
50
  });
44
51
  module.exports = __toCommonJS(src_exports);
45
52
 
46
53
  // src/version.ts
47
- var VERSION = "0.5.0-rc.1";
54
+ var VERSION = "0.5.0-rc.10";
48
55
 
49
56
  // src/core/features.ts
50
57
  function isEnabled(config) {
@@ -75,6 +82,7 @@ var PARAM_SEGMENT = "prepr_preview_segment";
75
82
  var PARAM_VARIANT = "prepr_preview_ab";
76
83
  var PARAM_HIDE_BAR = "prepr_hide_bar";
77
84
  var ONE_YEAR_SECONDS = 60 * 60 * 24 * 365;
85
+ var HEADER_LIVE_RENDER = "x-prepr-live";
78
86
 
79
87
  // src/core/cookies.ts
80
88
  function getCookie(name) {
@@ -116,6 +124,40 @@ function crossSiteCookieOptions() {
116
124
  return { sameSite: "None", secure: true };
117
125
  }
118
126
 
127
+ // src/core/locales/de.json
128
+ var de_default = {
129
+ common: {
130
+ viewingAs: "Ansicht als:",
131
+ user: "Benutzer",
132
+ off: "Aus",
133
+ on: "An",
134
+ reset: "Zur\xFCcksetzen",
135
+ toolbar: "Symbolleiste"
136
+ },
137
+ adaptiveContent: {
138
+ adaptiveContent: "Adaptive Inhalte",
139
+ enablePreview: "Vorschaumodus",
140
+ segment: "Segment",
141
+ ABVariant: "A/B-Variante",
142
+ chooseSegment: "Segment w\xE4hlen",
143
+ searchSegments: "Segmente suchen",
144
+ none: "Keine Segmente",
145
+ allOtherUsers: "Alle anderen Benutzer",
146
+ offDescription: "Die Website als echter Benutzer ansehen (Cookies verwenden)",
147
+ onDescription: "Mit der Symbolleiste Segmente und A/B-Varianten simulieren"
148
+ },
149
+ editingTools: {
150
+ editingTools: "Bearbeitungswerkzeuge",
151
+ editMode: "Bearbeitungsmodus",
152
+ ariaCloseEditMode: "Bearbeitungsmodus schlie\xDFen"
153
+ },
154
+ visualEditor: {
155
+ edit: "Bearbeiten",
156
+ otherItem: "anderes Element",
157
+ linkedItem: "verkn\xFCpftes Element"
158
+ }
159
+ };
160
+
119
161
  // src/core/locales/en.json
120
162
  var en_default = {
121
163
  common: {
@@ -142,6 +184,45 @@ var en_default = {
142
184
  editingTools: "Editing tools",
143
185
  editMode: "Edit mode",
144
186
  ariaCloseEditMode: "Close edit mode"
187
+ },
188
+ visualEditor: {
189
+ edit: "Edit",
190
+ otherItem: "other item",
191
+ linkedItem: "linked item"
192
+ }
193
+ };
194
+
195
+ // src/core/locales/fr.json
196
+ var fr_default = {
197
+ common: {
198
+ viewingAs: "Affichage en tant que :",
199
+ user: "Utilisateur",
200
+ off: "D\xE9sactiv\xE9",
201
+ on: "Activ\xE9",
202
+ reset: "R\xE9initialiser",
203
+ toolbar: "Barre d'outils"
204
+ },
205
+ adaptiveContent: {
206
+ adaptiveContent: "Contenu adaptatif",
207
+ enablePreview: "Mode aper\xE7u",
208
+ segment: "Segment",
209
+ ABVariant: "Variante A/B",
210
+ chooseSegment: "Choisir un segment",
211
+ searchSegments: "Rechercher des segments",
212
+ none: "Aucun segment",
213
+ allOtherUsers: "Tous les autres utilisateurs",
214
+ offDescription: "Voir le site comme un vrai utilisateur (utiliser les cookies)",
215
+ onDescription: "Utilisez la barre d'outils pour simuler des segments et des variantes A/B"
216
+ },
217
+ editingTools: {
218
+ editingTools: "Outils d'\xE9dition",
219
+ editMode: "Mode \xE9dition",
220
+ ariaCloseEditMode: "Fermer le mode \xE9dition"
221
+ },
222
+ visualEditor: {
223
+ edit: "Modifier",
224
+ otherItem: "autre \xE9l\xE9ment",
225
+ linkedItem: "\xE9l\xE9ment li\xE9"
145
226
  }
146
227
  };
147
228
 
@@ -171,13 +252,20 @@ var nl_default = {
171
252
  editingTools: "Editing Tools",
172
253
  editMode: "Edit mode",
173
254
  ariaCloseEditMode: "Edit mode sluiten"
255
+ },
256
+ visualEditor: {
257
+ edit: "Bewerken",
258
+ otherItem: "ander item",
259
+ linkedItem: "gekoppeld item"
174
260
  }
175
261
  };
176
262
 
177
263
  // src/core/i18n.ts
178
264
  var dictionaries = {
179
265
  en: en_default,
180
- nl: nl_default
266
+ nl: nl_default,
267
+ fr: fr_default,
268
+ de: de_default
181
269
  };
182
270
  var LOCALES = Object.keys(dictionaries);
183
271
  function isLocale(value) {
@@ -211,6 +299,9 @@ var trustedParentOrigin = null;
211
299
  function setTrustedParentOrigin(origin) {
212
300
  trustedParentOrigin = origin;
213
301
  }
302
+ function getTrustedParentOrigin() {
303
+ return trustedParentOrigin;
304
+ }
214
305
  function sendPreprEvent(event, data, options) {
215
306
  if (typeof window !== "undefined") {
216
307
  const message = {
@@ -270,52 +361,53 @@ function createScopedLogger(scopeName) {
270
361
  prefix: `[Prepr][${scopeName}]`
271
362
  });
272
363
  }
273
- function throttle(func, delay) {
274
- let timeoutId = null;
275
- let lastExecTime = 0;
276
- const throttledFunc = ((...args) => {
277
- const currentTime = Date.now();
278
- const timeSinceLastExec = currentTime - lastExecTime;
279
- if (timeSinceLastExec >= delay) {
280
- func(...args);
281
- lastExecTime = currentTime;
282
- } else {
283
- if (timeoutId) {
284
- clearTimeout(timeoutId);
285
- }
286
- timeoutId = setTimeout(() => {
287
- func(...args);
288
- lastExecTime = Date.now();
289
- timeoutId = null;
290
- }, delay - timeSinceLastExec);
291
- }
292
- });
293
- throttledFunc.cancel = () => {
294
- if (timeoutId) {
295
- clearTimeout(timeoutId);
296
- timeoutId = null;
297
- }
298
- };
299
- return throttledFunc;
300
- }
301
- function createElementCache(query, ttl = 1e3) {
302
- let cache = null;
303
- let lastCacheTime = 0;
304
- return () => {
305
- const now = Date.now();
306
- if (!cache || now - lastCacheTime > ttl) {
307
- cache = document.querySelectorAll(query);
308
- lastCacheTime = now;
309
- }
310
- return cache;
311
- };
312
- }
313
364
 
314
365
  // src/core/iframe-bridge.ts
315
366
  var debug = createScopedLogger("iframe-bridge");
367
+ var MAX_REFERENCES = 500;
368
+ var MAX_HREF_LENGTH = 8192;
369
+ var MAX_ASSETS = 500;
370
+ var MAX_ASSET_KEY_LENGTH = 2048;
371
+ var MAX_ASSET_FIELD_LENGTH = 512;
372
+ function parseIds(value) {
373
+ if (!Array.isArray(value)) return void 0;
374
+ return value.slice(0, MAX_REFERENCES).filter((id) => typeof id === "string" && id.length > 0);
375
+ }
376
+ function parseReferences(value) {
377
+ if (!Array.isArray(value)) return void 0;
378
+ const list = [];
379
+ for (const entry of value.slice(0, MAX_REFERENCES)) {
380
+ const id = entry?.id;
381
+ const field = entry?.field;
382
+ if (typeof id === "string" && id && typeof field === "string" && field) {
383
+ list.push({ id, field });
384
+ }
385
+ }
386
+ return list;
387
+ }
388
+ function boundedString(value, max) {
389
+ return typeof value === "string" && value && value.length <= max ? value : void 0;
390
+ }
391
+ function parseAssets(value) {
392
+ if (!Array.isArray(value)) return void 0;
393
+ const list = [];
394
+ for (const entry of value.slice(0, MAX_ASSETS)) {
395
+ if (!entry || typeof entry !== "object") continue;
396
+ const raw = entry;
397
+ const key = boundedString(raw.key, MAX_ASSET_KEY_LENGTH);
398
+ const field = boundedString(raw.field, MAX_ASSET_FIELD_LENGTH);
399
+ if (!key || !field) continue;
400
+ const entryId = boundedString(raw.entryId, MAX_ASSET_FIELD_LENGTH);
401
+ list.push(entryId ? { key, field, entryId } : { key, field });
402
+ }
403
+ return list;
404
+ }
316
405
  var EDITOR_BASE_DOMAIN = "prepr.io";
317
406
  function isAllowedEditorOrigin(origin, allowed) {
318
- if (allowed) return allowed.includes(origin);
407
+ if (allowed) {
408
+ if (allowed.includes("*")) return origin !== "null";
409
+ return allowed.includes(origin);
410
+ }
319
411
  let url;
320
412
  try {
321
413
  url = new URL(origin);
@@ -331,6 +423,10 @@ function isAllowedEditorOrigin(origin, allowed) {
331
423
  function createIframeBridge(store, options = {}) {
332
424
  const allowedOrigins = options.allowedEditorOrigins;
333
425
  const onScrollToField = options.onScrollToField;
426
+ const onHandshake = options.onHandshake;
427
+ const onReferences = options.onReferences;
428
+ const onAssets = options.onAssets;
429
+ const onAllowNavigation = options.onAllowNavigation;
334
430
  let parentOrigin = null;
335
431
  const onKeyDown = (event) => {
336
432
  const key = event.key.toLowerCase();
@@ -340,7 +436,10 @@ function createIframeBridge(store, options = {}) {
340
436
  const onMessage = (evt) => {
341
437
  const data = evt?.data;
342
438
  if (data?.event === "prepr:initVE" && !parentOrigin) {
343
- if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) return;
439
+ if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) {
440
+ debug.warn("ignored editor handshake from origin", evt.origin);
441
+ return;
442
+ }
344
443
  parentOrigin = evt.origin;
345
444
  setTrustedParentOrigin(parentOrigin);
346
445
  if (data.scrollPosition != null) {
@@ -348,8 +447,43 @@ function createIframeBridge(store, options = {}) {
348
447
  setTimeout(() => window.scrollTo(0, top), 1);
349
448
  }
350
449
  store?.set({ previewMode: true, editMode: data.editMode ?? true });
450
+ const itemId = typeof data.itemId === "string" && data.itemId ? data.itemId : void 0;
451
+ const referencedIds = parseReferences(data.referencedIds);
452
+ const componentIds = parseIds(data.componentIds);
453
+ const uiLocale = isLocale(data.uiLocale) ? data.uiLocale : void 0;
454
+ const navigationGuard = data.navigationGuard === true;
455
+ const assets = parseAssets(data.assets);
456
+ try {
457
+ onHandshake?.({
458
+ ...itemId && { itemId },
459
+ ...referencedIds && { referencedIds },
460
+ ...componentIds && { componentIds },
461
+ ...uiLocale && { uiLocale },
462
+ ...navigationGuard && { navigationGuard },
463
+ ...assets && { assets }
464
+ });
465
+ } catch (error) {
466
+ debug.warn("handshake handler failed", error);
467
+ }
351
468
  }
352
469
  if (!parentOrigin || evt.origin !== parentOrigin) return;
470
+ if (data?.event === "prepr:references") {
471
+ const assets = parseAssets(data.assets);
472
+ if (assets) {
473
+ try {
474
+ onAssets?.(assets);
475
+ } catch (error) {
476
+ debug.warn("assets handler failed", error);
477
+ }
478
+ }
479
+ const referencedIds = parseReferences(data.referencedIds);
480
+ if (!referencedIds) return;
481
+ try {
482
+ onReferences?.(referencedIds, parseIds(data.componentIds));
483
+ } catch (error) {
484
+ debug.warn("references handler failed", error);
485
+ }
486
+ }
353
487
  if (data?.event === "prepr:getScrollPosition") {
354
488
  const currentScrollY = window.scrollY || document.documentElement.scrollTop;
355
489
  sendPreprEvent("getScrollPosition", { value: currentScrollY });
@@ -357,12 +491,32 @@ function createIframeBridge(store, options = {}) {
357
491
  if (data?.event === "prepr:scrollToField") {
358
492
  if (typeof data.field !== "string" || !data.field) return;
359
493
  if (data.id !== void 0 && typeof data.id !== "string") return;
494
+ const block = typeof data.block === "number" && Number.isInteger(data.block) ? data.block : void 0;
495
+ const entryId = typeof data.entryId === "string" && data.entryId ? data.entryId : void 0;
496
+ const fieldById = typeof data.fieldById === "string" && data.fieldById ? data.fieldById : void 0;
360
497
  try {
361
- onScrollToField?.({ field: data.field, id: data.id });
498
+ onScrollToField?.({
499
+ field: data.field,
500
+ id: data.id,
501
+ block,
502
+ entryId,
503
+ fieldById
504
+ });
362
505
  } catch (error) {
363
506
  debug.warn("scrollToField handler failed", error);
364
507
  }
365
508
  }
509
+ if (data?.event === "prepr:allowNavigation") {
510
+ const href = data.href;
511
+ if (typeof href !== "string" || !href || href.length > MAX_HREF_LENGTH) {
512
+ return;
513
+ }
514
+ try {
515
+ onAllowNavigation?.(href);
516
+ } catch (error) {
517
+ debug.warn("allowNavigation handler failed", error);
518
+ }
519
+ }
366
520
  };
367
521
  return {
368
522
  start() {
@@ -418,6 +572,7 @@ function decodeStega(str) {
418
572
 
419
573
  // src/core/stega/dom.ts
420
574
  var IGNORED_ANCESTORS = "script, style, noscript";
575
+ var AUTO_ENTRY_ATTR = "data-prepr-entry-auto";
421
576
  function isIgnoredTextNode(node) {
422
577
  if (node.parentElement?.closest(IGNORED_ANCESTORS)) return true;
423
578
  return !node.textContent?.trim();
@@ -451,7 +606,9 @@ function parseEditHref(href) {
451
606
  return {
452
607
  id: url.pathname.split("/").pop() || void 0,
453
608
  field: url.searchParams.get("field") || void 0,
454
- locale: url.searchParams.get("locale") || void 0
609
+ locale: url.searchParams.get("locale") || void 0,
610
+ // Added by the toolkit's `annotateEditLinks`, not by Prepr.
611
+ entry: url.searchParams.get("entry") || void 0
455
612
  };
456
613
  } catch {
457
614
  return {};
@@ -465,45 +622,68 @@ function setOrRemove(element, name, value) {
465
622
  }
466
623
  }
467
624
  function tagEncodedElement(element, decoded) {
625
+ if (isSiteTagged(element)) return false;
468
626
  element.setAttribute("data-prepr-encoded", "");
469
627
  element.setAttribute("data-prepr-href", decoded.href);
470
628
  element.setAttribute("data-prepr-origin", decoded.origin);
471
- const { id, field, locale } = parseEditHref(decoded.href);
629
+ const { id, field, locale, entry } = parseEditHref(decoded.href);
472
630
  setOrRemove(element, "data-prepr-id", id);
473
- setOrRemove(element, "data-prepr-field", field);
631
+ const fieldHint = element.getAttribute("data-prepr-edit-target")?.trim() || void 0;
632
+ setOrRemove(element, "data-prepr-field", field ?? fieldHint);
474
633
  setOrRemove(element, "data-prepr-locale", locale);
634
+ const siteEntry = element.hasAttribute("data-prepr-entry-id") && !element.hasAttribute(AUTO_ENTRY_ATTR);
635
+ if (!siteEntry) {
636
+ setOrRemove(element, "data-prepr-entry-id", entry);
637
+ setOrRemove(element, AUTO_ENTRY_ATTR, entry && "true");
638
+ }
639
+ return true;
640
+ }
641
+ function isSiteTagged(element) {
642
+ return element.hasAttribute("data-prepr-encoded") && !element.hasAttribute("data-prepr-href");
643
+ }
644
+ function readEditPayload(element) {
645
+ return {
646
+ href: element.getAttribute("data-prepr-href") ?? "",
647
+ origin: element.getAttribute("data-prepr-origin") ?? "",
648
+ id: element.getAttribute("data-prepr-id") ?? void 0,
649
+ field: element.getAttribute("data-prepr-field") ?? void 0,
650
+ locale: element.getAttribute("data-prepr-locale") ?? void 0,
651
+ entryId: element.getAttribute("data-prepr-entry-id") || void 0
652
+ };
475
653
  }
476
654
 
477
655
  // src/core/stega/auto-clean.ts
478
656
  var debug3 = createScopedLogger("stega:auto-clean");
657
+ var cleanedNodes = /* @__PURE__ */ new WeakMap();
479
658
  function createStegaAutoClean(options) {
480
659
  const stripText = options?.enabled ?? true;
481
660
  let observer = null;
482
661
  let processing = false;
483
- let cleaned = /* @__PURE__ */ new WeakMap();
484
662
  let pendingHidden = /* @__PURE__ */ new Set();
485
663
  let pendingDirty = /* @__PURE__ */ new Set();
486
664
  let debounceTimeout = null;
487
665
  function cleanTextNode(textNode) {
488
666
  if (isIgnoredTextNode(textNode)) return false;
489
667
  const textContent = textNode.textContent;
490
- const info = cleaned.get(textNode);
668
+ const info = cleanedNodes.get(textNode);
491
669
  const settled = stripText ? info?.cleaned : info?.original;
492
670
  if (info && textContent === settled) {
493
671
  if (!info.decoded) return false;
494
672
  const shown = resolveEditTarget(textNode);
495
- if (!shown) return false;
673
+ if (!shown) {
674
+ pendingHidden.add(textNode);
675
+ return false;
676
+ }
496
677
  pendingHidden.delete(textNode);
497
678
  if (shown.hasAttribute("data-prepr-encoded")) return false;
498
- tagEncodedElement(shown, info.decoded);
499
- return true;
679
+ return tagEncodedElement(shown, info.decoded);
500
680
  }
501
681
  try {
502
682
  const { cleaned: stripped, encoded } = (0, import_stega2.vercelStegaSplit)(textContent);
503
683
  if (!encoded || stripped === textContent) return false;
504
684
  const decoded = decodeStega(textContent);
505
685
  if (!decoded?.href && !stripText) return false;
506
- cleaned.set(textNode, {
686
+ cleanedNodes.set(textNode, {
507
687
  original: textContent,
508
688
  cleaned: stripped,
509
689
  decoded: decoded?.href ? decoded : null
@@ -513,8 +693,7 @@ function createStegaAutoClean(options) {
513
693
  const target = resolveEditTarget(textNode);
514
694
  if (target) {
515
695
  pendingHidden.delete(textNode);
516
- tagEncodedElement(target, decoded);
517
- return true;
696
+ return tagEncodedElement(target, decoded);
518
697
  }
519
698
  pendingHidden.add(textNode);
520
699
  } catch (error) {
@@ -545,7 +724,7 @@ function createStegaAutoClean(options) {
545
724
  }
546
725
  if (mutation.type === "characterData" && mutation.target.nodeType === Node.TEXT_NODE) {
547
726
  const textNode = mutation.target;
548
- const info = cleaned.get(textNode);
727
+ const info = cleanedNodes.get(textNode);
549
728
  const text = textNode.textContent ?? "";
550
729
  const settled = stripText ? info?.cleaned : info?.original;
551
730
  if (text !== settled && (0, import_stega2.vercelStegaSplit)(text).encoded) {
@@ -588,7 +767,16 @@ function createStegaAutoClean(options) {
588
767
  retryPendingHidden();
589
768
  }, 50);
590
769
  }
770
+ let resizeTimeout = null;
771
+ function handleResize() {
772
+ if (pendingHidden.size === 0 || resizeTimeout) return;
773
+ resizeTimeout = setTimeout(() => {
774
+ resizeTimeout = null;
775
+ retryPendingHidden();
776
+ }, 100);
777
+ }
591
778
  function setupObserver() {
779
+ window.addEventListener("resize", handleResize, { passive: true });
592
780
  observer = new MutationObserver(handleMutations);
593
781
  observer.observe(document.body, {
594
782
  childList: true,
@@ -611,11 +799,15 @@ function createStegaAutoClean(options) {
611
799
  clearTimeout(debounceTimeout);
612
800
  debounceTimeout = null;
613
801
  }
802
+ if (resizeTimeout) {
803
+ clearTimeout(resizeTimeout);
804
+ resizeTimeout = null;
805
+ }
614
806
  if (observer) {
615
807
  observer.disconnect();
616
808
  observer = null;
809
+ window.removeEventListener("resize", handleResize);
617
810
  }
618
- cleaned = /* @__PURE__ */ new WeakMap();
619
811
  pendingHidden = /* @__PURE__ */ new Set();
620
812
  pendingDirty = /* @__PURE__ */ new Set();
621
813
  debug3.log("stega auto-clean stopped");
@@ -623,6 +815,439 @@ function createStegaAutoClean(options) {
623
815
  return { start, stop };
624
816
  }
625
817
 
818
+ // src/core/visual-editor/overlay.ts
819
+ var HOST_TAG = "prepr-visual-editor";
820
+ var SCROLL_SETTLE_MS = 120;
821
+ var LABEL_SPACE_PX = 22;
822
+ var BOX_PADDING_PX = 3;
823
+ var LABEL_MARGIN_PX = 4;
824
+ var PROBE_POINTS = [
825
+ [0.5, 0.5],
826
+ [0.15, 0.2],
827
+ [0.85, 0.2],
828
+ [0.15, 0.8],
829
+ [0.85, 0.8]
830
+ ];
831
+ function overlapsBox(area, rect) {
832
+ return area.left < rect.right + BOX_PADDING_PX && area.right > rect.left - BOX_PADDING_PX && area.top < rect.bottom + BOX_PADDING_PX && area.bottom > rect.top - BOX_PADDING_PX;
833
+ }
834
+ function overlaps(a2, b2) {
835
+ return a2.left < b2.right && b2.left < a2.right && a2.top < b2.bottom && b2.top < a2.bottom;
836
+ }
837
+ function coverOf(hit, target, x2, y2) {
838
+ let cover = hit.getBoundingClientRect();
839
+ for (let el = hit.parentElement; el && !el.contains(target) && el !== document.body; el = el.parentElement) {
840
+ const r2 = el.getBoundingClientRect();
841
+ if (x2 < r2.left || x2 >= r2.right || y2 < r2.top || y2 >= r2.bottom) break;
842
+ cover = r2;
843
+ }
844
+ return cover;
845
+ }
846
+ function mergeAreas(areas) {
847
+ const merged = areas.slice();
848
+ for (let i2 = 0; i2 < merged.length; i2++) {
849
+ for (let j2 = i2 + 1; j2 < merged.length; j2++) {
850
+ if (!overlaps(merged[i2], merged[j2])) continue;
851
+ merged[i2] = {
852
+ left: Math.min(merged[i2].left, merged[j2].left),
853
+ top: Math.min(merged[i2].top, merged[j2].top),
854
+ right: Math.max(merged[i2].right, merged[j2].right),
855
+ bottom: Math.max(merged[i2].bottom, merged[j2].bottom)
856
+ };
857
+ merged.splice(j2, 1);
858
+ j2 = i2;
859
+ }
860
+ }
861
+ return merged;
862
+ }
863
+ function findCovers(target, rect) {
864
+ const width = window.innerWidth;
865
+ const height = window.innerHeight;
866
+ const covers = [];
867
+ const seen = /* @__PURE__ */ new Set();
868
+ let probed = false;
869
+ let reached = false;
870
+ for (const [fx, fy] of PROBE_POINTS) {
871
+ const x2 = rect.left + rect.width * fx;
872
+ const y2 = rect.top + rect.height * fy;
873
+ if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height) continue;
874
+ probed = true;
875
+ const hit = document.elementFromPoint(x2, y2);
876
+ if (!hit || hit === target || target.contains(hit)) {
877
+ reached = true;
878
+ continue;
879
+ }
880
+ if (seen.has(hit)) continue;
881
+ seen.add(hit);
882
+ if (covers.some(
883
+ (c2) => x2 >= c2.left && x2 < c2.right && y2 >= c2.top && y2 < c2.bottom
884
+ )) {
885
+ continue;
886
+ }
887
+ covers.push(coverOf(hit, target, x2, y2));
888
+ }
889
+ if (!probed) return [];
890
+ return reached ? mergeAreas(covers) : null;
891
+ }
892
+ function clipPath(x2, y2, covers) {
893
+ const far = 1e5;
894
+ const points = [
895
+ `${-far}px ${-far}px`,
896
+ `${far}px ${-far}px`,
897
+ `${far}px ${far}px`,
898
+ `${-far}px ${far}px`,
899
+ `${-far}px ${-far}px`
900
+ ];
901
+ for (const c2 of covers) {
902
+ const l2 = c2.left - x2;
903
+ const t3 = c2.top - y2;
904
+ const r2 = c2.right - x2;
905
+ const b2 = c2.bottom - y2;
906
+ points.push(
907
+ `${l2}px ${t3}px`,
908
+ `${r2}px ${t3}px`,
909
+ `${r2}px ${b2}px`,
910
+ `${l2}px ${b2}px`,
911
+ `${l2}px ${t3}px`,
912
+ `${-far}px ${-far}px`
913
+ );
914
+ }
915
+ return `polygon(evenodd, ${points.join(", ")})`;
916
+ }
917
+ var STYLES = `
918
+ :host {
919
+ all: initial;
920
+ position: fixed;
921
+ inset: 0;
922
+ pointer-events: none;
923
+ z-index: 2147483646;
924
+ contain: strict;
925
+ }
926
+ .box {
927
+ position: absolute;
928
+ top: 0;
929
+ left: 0;
930
+ box-sizing: border-box;
931
+ border-radius: 6px;
932
+ opacity: 0;
933
+ will-change: transform, width, height;
934
+ transition:
935
+ transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
936
+ width 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
937
+ height 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
938
+ opacity 140ms ease;
939
+ }
940
+ .box[data-visible] { opacity: 1; }
941
+ .box[data-instant], :host([data-scrolling]) .box {
942
+ transition: opacity 140ms ease;
943
+ }
944
+ .hover {
945
+ border: 1.5px solid rgba(79, 70, 229, 0.9);
946
+ background: rgba(79, 70, 229, 0.06);
947
+ }
948
+ .selected {
949
+ border: 2px solid #4f46e5;
950
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.18);
951
+ }
952
+ .label {
953
+ position: absolute;
954
+ left: -2px;
955
+ bottom: 100%;
956
+ margin-bottom: 4px;
957
+ padding: 3px 7px;
958
+ border-radius: 5px;
959
+ background: #4f46e5;
960
+ color: #fff;
961
+ font: 500 11px/1.3 ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
962
+ letter-spacing: 0.01em;
963
+ white-space: nowrap;
964
+ display: flex;
965
+ max-width: 240px;
966
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25);
967
+ }
968
+ .text {
969
+ min-width: 0;
970
+ overflow: hidden;
971
+ text-overflow: ellipsis;
972
+ }
973
+ .hover .label { background: rgba(79, 70, 229, 0.92); }
974
+ /* Content of another item than the one open in the editor: still clickable,
975
+ but visibly not part of the item being edited. */
976
+ .hover[data-item-kind='other'] {
977
+ border: 1.5px dashed #a5b4fc;
978
+ background: rgba(100, 116, 139, 0.05);
979
+ }
980
+ .hover[data-item-kind='other'] .label { background: #64748b; }
981
+ .hover[data-item-kind='referenced'] {
982
+ border: 1.5px solid #a5b4fc;
983
+ background: rgba(79, 70, 229, 0.03);
984
+ }
985
+ .hover[data-item-kind='referenced'] .label { background: #818cf8; }
986
+ .box[data-label-covered] .label { visibility: hidden; }
987
+ .box[data-label-inside] .label {
988
+ bottom: auto;
989
+ top: 2px;
990
+ left: 2px;
991
+ margin: 0;
992
+ }
993
+ /* A clickable hover label (edit mode on the live site). Only while shown, so
994
+ a fading box never leaves an invisible click target behind. */
995
+ :host([data-label-action]) .hover[data-visible] .label {
996
+ pointer-events: auto;
997
+ cursor: pointer;
998
+ }
999
+ :host([data-label-action]) .hover .label::after {
1000
+ content: '\\2197';
1001
+ margin-left: 5px;
1002
+ }
1003
+ :host([data-label-action]) .hover .label:hover { background: #4338ca; }
1004
+ /* Bridges the gap between the label and its element, so the pointer can
1005
+ travel from one to the other without the hover ending on the way. */
1006
+ :host([data-label-action]) .hover .label::before {
1007
+ content: '';
1008
+ position: absolute;
1009
+ left: 0;
1010
+ right: 0;
1011
+ top: 100%;
1012
+ height: ${LABEL_MARGIN_PX + BOX_PADDING_PX + 2}px;
1013
+ }
1014
+ @media (prefers-reduced-motion: reduce) {
1015
+ .box { transition: opacity 80ms linear; }
1016
+ }
1017
+ `;
1018
+ var VisualEditorOverlay = class {
1019
+ constructor(options = {}) {
1020
+ this.host = null;
1021
+ this.hoverBox = null;
1022
+ this.selectedBox = null;
1023
+ this.frame = null;
1024
+ this.scrollTimeout = null;
1025
+ this.resizeObserver = null;
1026
+ this.onLabelClick = options.onLabelClick;
1027
+ }
1028
+ mount() {
1029
+ if (this.host) return;
1030
+ const host = document.createElement(HOST_TAG);
1031
+ const shadow = host.attachShadow({ mode: "open" });
1032
+ const style = document.createElement("style");
1033
+ style.textContent = STYLES;
1034
+ shadow.appendChild(style);
1035
+ this.hoverBox = this.createBox(shadow, "hover");
1036
+ this.selectedBox = this.createBox(shadow, "selected");
1037
+ const onLabelClick = this.onLabelClick;
1038
+ if (onLabelClick) {
1039
+ host.setAttribute("data-label-action", "");
1040
+ const hoverBox = this.hoverBox;
1041
+ hoverBox.label.addEventListener("click", () => {
1042
+ if (hoverBox.target) onLabelClick(hoverBox.target);
1043
+ });
1044
+ }
1045
+ document.documentElement.appendChild(host);
1046
+ this.host = host;
1047
+ if (typeof ResizeObserver !== "undefined") {
1048
+ this.resizeObserver = new ResizeObserver(() => this.schedule());
1049
+ }
1050
+ }
1051
+ unmount() {
1052
+ if (this.frame !== null) cancelAnimationFrame(this.frame);
1053
+ if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
1054
+ this.frame = null;
1055
+ this.scrollTimeout = null;
1056
+ this.resizeObserver?.disconnect();
1057
+ this.resizeObserver = null;
1058
+ this.host?.remove();
1059
+ this.host = null;
1060
+ this.hoverBox = null;
1061
+ this.selectedBox = null;
1062
+ }
1063
+ /**
1064
+ * Whether a pointer event target is this chrome: events from inside the
1065
+ * shadow root reach the page retargeted to its host.
1066
+ */
1067
+ owns(node) {
1068
+ return node !== null && node === this.host;
1069
+ }
1070
+ getHoverTarget() {
1071
+ return this.hoverBox?.target ?? null;
1072
+ }
1073
+ getSelectedTarget() {
1074
+ return this.selectedBox?.target ?? null;
1075
+ }
1076
+ setHover(target, label, options = {}) {
1077
+ if (target) {
1078
+ this.hoverBox?.root.setAttribute(
1079
+ "data-item-kind",
1080
+ options.kind ?? "current"
1081
+ );
1082
+ }
1083
+ this.retarget(this.hoverBox, target, label);
1084
+ }
1085
+ setSelected(target, label) {
1086
+ this.retarget(this.selectedBox, target, label);
1087
+ }
1088
+ /** Call on every scroll; positions follow in the next frame, untransitioned. */
1089
+ onScroll() {
1090
+ if (!this.host) return;
1091
+ this.host.setAttribute("data-scrolling", "");
1092
+ if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
1093
+ this.scrollTimeout = setTimeout(() => {
1094
+ this.host?.removeAttribute("data-scrolling");
1095
+ this.scrollTimeout = null;
1096
+ }, SCROLL_SETTLE_MS);
1097
+ this.schedule();
1098
+ }
1099
+ /** Re-measure both boxes in the next animation frame. */
1100
+ schedule() {
1101
+ if (this.frame !== null || !this.host) return;
1102
+ this.frame = requestAnimationFrame(() => {
1103
+ this.frame = null;
1104
+ this.render();
1105
+ });
1106
+ }
1107
+ createBox(shadow, kind) {
1108
+ const root = document.createElement("div");
1109
+ root.className = `box ${kind}`;
1110
+ const label = document.createElement("div");
1111
+ label.className = "label";
1112
+ const text = document.createElement("span");
1113
+ text.className = "text";
1114
+ label.appendChild(text);
1115
+ root.appendChild(label);
1116
+ shadow.appendChild(root);
1117
+ return { root, label, text, target: null };
1118
+ }
1119
+ retarget(box, target, label) {
1120
+ if (!box || box.target === target) return;
1121
+ const previous = box.target;
1122
+ if (previous) this.resizeObserver?.unobserve(previous);
1123
+ box.target = target;
1124
+ if (target) {
1125
+ this.resizeObserver?.observe(target);
1126
+ box.text.textContent = label;
1127
+ if (!box.root.hasAttribute("data-visible")) {
1128
+ box.root.setAttribute("data-instant", "");
1129
+ }
1130
+ }
1131
+ this.schedule();
1132
+ }
1133
+ render() {
1134
+ const hover = this.hoverBox ? this.measure(this.hoverBox) : null;
1135
+ const selected = this.selectedBox ? this.measure(this.selectedBox) : null;
1136
+ if (this.hoverBox) this.paint(this.hoverBox, hover, selected);
1137
+ if (this.selectedBox) this.paint(this.selectedBox, selected, hover);
1138
+ }
1139
+ measure(box) {
1140
+ const target = box.target;
1141
+ if (!target) return null;
1142
+ if (!target.isConnected) {
1143
+ this.resizeObserver?.unobserve(target);
1144
+ box.target = null;
1145
+ return null;
1146
+ }
1147
+ const rect = target.getBoundingClientRect();
1148
+ if (rect.width === 0 && rect.height === 0) return null;
1149
+ const covers = findCovers(target, rect);
1150
+ if (!covers) return null;
1151
+ return {
1152
+ rect,
1153
+ covers,
1154
+ labelWidth: covers.length ? box.label.offsetWidth : 0,
1155
+ labelHeight: covers.length ? box.label.offsetHeight : 0
1156
+ };
1157
+ }
1158
+ paint(box, measurement, other = null) {
1159
+ const root = box.root;
1160
+ if (!measurement) {
1161
+ root.removeAttribute("data-visible");
1162
+ return;
1163
+ }
1164
+ const { rect, covers } = measurement;
1165
+ const x2 = rect.left - BOX_PADDING_PX;
1166
+ const y2 = rect.top - BOX_PADDING_PX;
1167
+ const labelAbove = {
1168
+ left: x2 - 2,
1169
+ top: y2 - LABEL_SPACE_PX,
1170
+ right: x2 - 2 + (measurement.labelWidth || Math.min(240, rect.width)),
1171
+ bottom: y2
1172
+ };
1173
+ const labelInside = y2 < LABEL_SPACE_PX || other !== null && other.rect !== rect && overlapsBox(labelAbove, other.rect);
1174
+ root.style.transform = `translate3d(${x2}px, ${y2}px, 0)`;
1175
+ root.style.width = `${rect.width + BOX_PADDING_PX * 2}px`;
1176
+ root.style.height = `${rect.height + BOX_PADDING_PX * 2}px`;
1177
+ root.toggleAttribute("data-label-inside", labelInside);
1178
+ root.style.clipPath = covers.length ? clipPath(x2, y2, covers) : "";
1179
+ const labelLeft = labelInside ? x2 + 2 : x2 - 2;
1180
+ const labelTop = labelInside ? y2 + 2 : y2 - LABEL_MARGIN_PX - measurement.labelHeight;
1181
+ const label = {
1182
+ left: labelLeft,
1183
+ top: labelTop,
1184
+ right: labelLeft + measurement.labelWidth,
1185
+ bottom: labelTop + measurement.labelHeight
1186
+ };
1187
+ root.toggleAttribute(
1188
+ "data-label-covered",
1189
+ covers.some((c2) => overlaps(c2, label))
1190
+ );
1191
+ if (root.hasAttribute("data-instant")) {
1192
+ root.setAttribute("data-visible", "");
1193
+ requestAnimationFrame(() => root.removeAttribute("data-instant"));
1194
+ return;
1195
+ }
1196
+ root.setAttribute("data-visible", "");
1197
+ }
1198
+ };
1199
+
1200
+ // src/core/visual-editor/field-path.ts
1201
+ var INDEX_SEGMENT = /^\d+$/;
1202
+ var WORD_SEPARATORS = /[_-]+/g;
1203
+ function normalizeFieldPath(path) {
1204
+ return path.split(".").map(
1205
+ (segment) => INDEX_SEGMENT.test(segment) ? String(Number.parseInt(segment, 10)) : segment
1206
+ ).join(".");
1207
+ }
1208
+ var GENERIC_LEAVES = /* @__PURE__ */ new Set(["value", "text", "body", "content", "label"]);
1209
+ function humanize(name) {
1210
+ const words = name.replace(WORD_SEPARATORS, " ").trim();
1211
+ return words.charAt(0).toUpperCase() + words.slice(1);
1212
+ }
1213
+ function fieldLabel(path, locale = "en") {
1214
+ if (!path) return t("visualEditor.edit", locale);
1215
+ const segments = path.split(".").filter((s2) => s2 && s2 !== "items" && !INDEX_SEGMENT.test(s2));
1216
+ const name = segments[segments.length - 1];
1217
+ if (!name) return t("visualEditor.edit", locale);
1218
+ const parent = segments[segments.length - 2];
1219
+ if (parent && GENERIC_LEAVES.has(name)) {
1220
+ return `${humanize(parent)} \u203A ${humanize(name)}`;
1221
+ }
1222
+ return humanize(name);
1223
+ }
1224
+ function findFieldElement(lookup) {
1225
+ if (!lookup.field) return null;
1226
+ const candidates = document.querySelectorAll(
1227
+ "[data-prepr-encoded]"
1228
+ );
1229
+ if (lookup.entryId) {
1230
+ for (const candidate of candidates) {
1231
+ if (candidate.getAttribute("data-prepr-entry-id") === lookup.entryId) {
1232
+ return candidate;
1233
+ }
1234
+ }
1235
+ }
1236
+ for (const path of [lookup.field, lookup.fieldById]) {
1237
+ if (!path) continue;
1238
+ const wanted = normalizeFieldPath(path);
1239
+ for (const candidate of candidates) {
1240
+ const field = candidate.getAttribute("data-prepr-field");
1241
+ if (!field || normalizeFieldPath(field) !== wanted) continue;
1242
+ if (lookup.id && candidate.getAttribute("data-prepr-id") !== lookup.id) {
1243
+ continue;
1244
+ }
1245
+ return candidate;
1246
+ }
1247
+ }
1248
+ return null;
1249
+ }
1250
+
626
1251
  // src/core/stega/elements.ts
627
1252
  var debug4 = createScopedLogger("stega:elements");
628
1253
  var StegaElements = class {
@@ -712,235 +1337,22 @@ var StegaElements = class {
712
1337
  attributeFilter: ["class", "style", "hidden", "open", "aria-expanded"]
713
1338
  });
714
1339
  }
715
- /** Drops the active class but keeps the data attributes, unlike cleanup(). */
716
- cleanupVisuals() {
717
- document.querySelectorAll(".prepr-overlay-active").forEach((element) => element.classList.remove("prepr-overlay-active"));
718
- }
719
- cleanup() {
1340
+ /**
1341
+ * Stops watching for new content. Tags stay on their elements: the page's
1342
+ * stega characters are stripped once they are read, so a tag removed here
1343
+ * could never be rebuilt when edit mode is turned back on.
1344
+ */
1345
+ disconnect() {
720
1346
  if (this.observer) {
721
1347
  this.observer.disconnect();
722
1348
  this.observer = null;
723
1349
  }
724
- document.querySelectorAll("[data-prepr-encoded]").forEach((element) => {
725
- element.removeAttribute("data-prepr-encoded");
726
- element.removeAttribute("data-prepr-href");
727
- element.removeAttribute("data-prepr-origin");
728
- element.removeAttribute("data-prepr-id");
729
- element.removeAttribute("data-prepr-field");
730
- element.removeAttribute("data-prepr-locale");
731
- element.classList.remove("prepr-overlay-active");
732
- });
733
1350
  this.elements = void 0;
734
1351
  }
735
1352
  };
736
1353
 
737
- // src/core/stega/overlay.ts
738
- var debug5 = createScopedLogger("stega:overlay");
739
- var StegaOverlay = class {
740
- constructor(onEdit) {
741
- this.overlay = null;
742
- this.tooltip = null;
743
- this.hideTimeout = null;
744
- this.currentElement = null;
745
- this.onEdit = onEdit;
746
- }
747
- create() {
748
- const overlay = document.createElement("div");
749
- overlay.className = "prepr-overlay";
750
- overlay.style.display = "none";
751
- const tooltip = document.createElement("div");
752
- tooltip.className = "prepr-tooltip";
753
- tooltip.style.display = "none";
754
- document.body.appendChild(overlay);
755
- document.body.appendChild(tooltip);
756
- this.overlay = overlay;
757
- this.tooltip = tooltip;
758
- return { overlay, tooltip };
759
- }
760
- getTooltip() {
761
- return this.tooltip;
762
- }
763
- hasCurrentElement() {
764
- return this.currentElement !== null;
765
- }
766
- clearHideTimeout() {
767
- if (this.hideTimeout) {
768
- clearTimeout(this.hideTimeout);
769
- this.hideTimeout = null;
770
- }
771
- }
772
- show(element) {
773
- if (!this.overlay || !this.tooltip) return;
774
- this.clearHideTimeout();
775
- if (this.currentElement && this.currentElement !== element) {
776
- this.currentElement.classList.remove("prepr-overlay-active");
777
- }
778
- const rect = element.getBoundingClientRect();
779
- const href = element.getAttribute("data-prepr-href");
780
- const origin = element.getAttribute("data-prepr-origin");
781
- const id = element.getAttribute("data-prepr-id") ?? void 0;
782
- const field = element.getAttribute("data-prepr-field") ?? void 0;
783
- const locale = element.getAttribute("data-prepr-locale") ?? void 0;
784
- debug5.log("showing overlay", { href, origin });
785
- const overlay = this.overlay;
786
- overlay.style.display = "block";
787
- overlay.style.top = `${rect.top + window.scrollY - 2}px`;
788
- overlay.style.left = `${rect.left + window.scrollX - 4}px`;
789
- overlay.style.width = `${rect.width + 8}px`;
790
- overlay.style.height = `${rect.height + 4}px`;
791
- const tooltip = this.tooltip;
792
- if (href && origin) {
793
- const MIN_WIDTH_FOR_TEXT = 80;
794
- const isCompact = rect.width < MIN_WIDTH_FOR_TEXT;
795
- tooltip.textContent = isCompact ? "\u2197" : `${origin} \u2197`;
796
- tooltip.style.display = "block";
797
- tooltip.style.minWidth = isCompact ? "auto" : "80px";
798
- requestAnimationFrame(() => {
799
- let top = rect.top + window.scrollY - tooltip.clientHeight - 2;
800
- let left = rect.right + 4 - tooltip.clientWidth;
801
- const minTop = window.scrollY + 4;
802
- const maxTop = window.scrollY + window.innerHeight - tooltip.clientHeight - 4;
803
- const minLeft = window.scrollX + 4;
804
- const maxLeft = window.scrollX + window.innerWidth - tooltip.clientWidth - 4;
805
- const below = top < minTop;
806
- if (below) {
807
- top = rect.bottom + window.scrollY + 2;
808
- }
809
- top = Math.max(minTop, Math.min(top, maxTop));
810
- left = Math.max(minLeft, Math.min(left, maxLeft));
811
- tooltip.dataset.preprPlacement = below ? "below" : "above";
812
- tooltip.style.top = `${top}px`;
813
- tooltip.style.left = `${left}px`;
814
- });
815
- tooltip.onclick = () => {
816
- this.onEdit({ href, origin, id, field, locale });
817
- };
818
- }
819
- this.currentElement = element;
820
- element.classList.add("prepr-overlay-active");
821
- }
822
- hideImmediate() {
823
- this.clearHideTimeout();
824
- if (this.overlay) this.overlay.style.display = "none";
825
- if (this.tooltip) this.tooltip.style.display = "none";
826
- if (this.currentElement) {
827
- this.currentElement.classList.remove("prepr-overlay-active");
828
- this.currentElement = null;
829
- }
830
- }
831
- hide() {
832
- if (!this.overlay || !this.tooltip) return;
833
- this.clearHideTimeout();
834
- this.hideTimeout = setTimeout(() => this.hideImmediate(), 100);
835
- }
836
- cleanup() {
837
- this.clearHideTimeout();
838
- if (this.overlay) {
839
- this.overlay.style.display = "none";
840
- this.overlay.parentNode?.removeChild(this.overlay);
841
- this.overlay = null;
842
- }
843
- if (this.tooltip) {
844
- this.tooltip.style.display = "none";
845
- this.tooltip.parentNode?.removeChild(this.tooltip);
846
- this.tooltip = null;
847
- }
848
- if (this.currentElement) {
849
- this.currentElement.classList.remove("prepr-overlay-active");
850
- this.currentElement = null;
851
- }
852
- }
853
- };
854
-
855
- // src/core/stega/proximity.ts
856
- var debug6 = createScopedLogger("stega:proximity");
857
- var StegaProximity = class {
858
- constructor() {
859
- this.highlighted = /* @__PURE__ */ new Set();
860
- this.visible = /* @__PURE__ */ new Set();
861
- this.observer = null;
862
- this.getEncodedElements = createElementCache(
863
- "[data-prepr-encoded]",
864
- 200
865
- );
866
- }
867
- /** Call after the encoded element set changes — rebuilds the observer. */
868
- refreshObserving() {
869
- try {
870
- if (typeof IntersectionObserver === "undefined") return;
871
- if (this.observer) {
872
- this.observer.disconnect();
873
- this.observer = null;
874
- }
875
- this.visible = /* @__PURE__ */ new Set();
876
- this.observer = new IntersectionObserver(
877
- (entries) => {
878
- entries.forEach((entry) => {
879
- const el = entry.target;
880
- if (entry.isIntersecting) {
881
- this.visible.add(el);
882
- } else {
883
- this.visible.delete(el);
884
- }
885
- });
886
- },
887
- { root: null, rootMargin: "0px", threshold: 0 }
888
- );
889
- const nodes = this.getEncodedElements();
890
- nodes.forEach((el) => this.observer.observe(el));
891
- debug6.log("observing", nodes.length, "encoded elements");
892
- } catch (error) {
893
- debug6.log("error setting up IntersectionObserver", error);
894
- }
895
- }
896
- stopObserving() {
897
- if (this.observer) {
898
- this.observer.disconnect();
899
- this.observer = null;
900
- this.visible.clear();
901
- }
902
- }
903
- updateElementGradients(cursorX, cursorY) {
904
- const candidates = this.visible.size > 0 ? Array.from(this.visible) : Array.from(this.getEncodedElements());
905
- const newHighlighted = /* @__PURE__ */ new Set();
906
- candidates.forEach((element) => {
907
- const rect = element.getBoundingClientRect();
908
- const distance = Math.min(
909
- Math.abs(cursorX - rect.left),
910
- Math.abs(cursorX - rect.right),
911
- Math.abs(cursorY - rect.top),
912
- Math.abs(cursorY - rect.bottom)
913
- );
914
- if (distance < 150) {
915
- const relativeX = cursorX - rect.left;
916
- const relativeY = cursorY - rect.top;
917
- element.style.setProperty("--cursor-x", `${relativeX}px`);
918
- element.style.setProperty("--cursor-y", `${relativeY}px`);
919
- const baseGradientSize = Math.max(
920
- 150,
921
- Math.max(rect.width, rect.height) * 1.1
922
- );
923
- const distanceScale = Math.max(0, (400 - distance) / 400);
924
- const gradientSize = baseGradientSize * distanceScale;
925
- element.style.setProperty("--gradient-size", `${gradientSize}px`);
926
- element.classList.add("prepr-proximity-highlight");
927
- newHighlighted.add(element);
928
- } else {
929
- element.classList.remove("prepr-proximity-highlight");
930
- }
931
- });
932
- this.highlighted = newHighlighted;
933
- }
934
- clearAllHighlights() {
935
- this.highlighted.forEach(
936
- (element) => element.classList.remove("prepr-proximity-highlight")
937
- );
938
- this.highlighted.clear();
939
- }
940
- };
941
-
942
1354
  // src/core/stega/stega.generated.ts
943
- var STEGA_STYLES = '[data-prepr-encoded]{position:relative}.prepr-overlay{pointer-events:none;z-index:10000;border:2px solid #4f46e5;border-radius:4px 0 4px 4px;transition:all .2s ease-in-out;position:absolute}.prepr-tooltip{color:#fff;white-space:nowrap;z-index:10001;text-align:center;pointer-events:auto;cursor:pointer;background-color:#4f46e5;border-radius:4px 4px 0 0;min-width:80px;padding:4px 8px;font-size:12px;line-height:1;transition:opacity .2s ease-in-out;position:absolute}.prepr-tooltip[data-prepr-placement=below]{border-radius:0 0 4px 4px}.prepr-tooltip:hover{background-color:#4338ca}.prepr-proximity-highlight{z-index:1;position:relative}.prepr-proximity-highlight,.prepr-proximity-highlight *{overflow:visible!important}.prepr-proximity-highlight:before{content:"";pointer-events:none;background:radial-gradient(circle var(--gradient-size) at var(--cursor-x) var(--cursor-y), #4f46e526 0%, #2563eb00 70%);z-index:9999;border:2px solid #4f46e526;border-radius:4px;transition:all .2s;position:absolute;top:-2px;bottom:-2px;left:-4px;right:-4px;overflow:visible}';
1355
+ var STEGA_STYLES = '@property --prepr-presence{syntax:"<number>";inherits:true;initial-value:1}[data-prepr-encoded]{outline:1px dashed rgb(79 70 229/calc(.3 * var(--prepr-presence,1)));outline-offset:2px}';
944
1356
 
945
1357
  // src/core/stega/styles.ts
946
1358
  var STEGA_STYLE_MARKER = "data-prepr-stega";
@@ -960,46 +1372,80 @@ function removeStegaStyles() {
960
1372
  }
961
1373
 
962
1374
  // src/core/stega/scan.ts
963
- var debug7 = createScopedLogger("stega:scan");
1375
+ var debug5 = createScopedLogger("stega:scan");
1376
+ var ENCODED_SELECTOR = "[data-prepr-encoded]";
1377
+ var PRESENCE_FADE_MS = 400;
964
1378
  var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
1379
+ function fadeInPresence() {
1380
+ const root = document.documentElement;
1381
+ if (typeof root.animate !== "function") return;
1382
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
1383
+ root.animate(
1384
+ { "--prepr-presence": ["0", "1"] },
1385
+ { duration: PRESENCE_FADE_MS, easing: "ease-out" }
1386
+ );
1387
+ }
965
1388
  function createStegaController(opts) {
966
1389
  const tooltipEnabled = opts.tooltip ?? true;
967
- const overlay = new StegaOverlay(opts.onEdit);
968
- const proximity = new StegaProximity();
1390
+ const locale = opts.locale ?? "en";
969
1391
  const elements = new StegaElements();
1392
+ const overlay = new VisualEditorOverlay({
1393
+ onLabelClick: (target) => opts.onEdit(readEditPayload(target))
1394
+ });
970
1395
  let initialized = false;
971
- let throttledMouseMove = null;
972
- const handleTooltipMouseEnter = () => {
973
- overlay.clearHideTimeout();
1396
+ let pointerFrame = null;
1397
+ let pointerTarget = null;
1398
+ let pointerX = 0;
1399
+ let pointerY = 0;
1400
+ let hitTestPending = false;
1401
+ const updateHover = () => {
1402
+ pointerFrame = null;
1403
+ if (hitTestPending) {
1404
+ hitTestPending = false;
1405
+ pointerTarget = document.elementFromPoint(pointerX, pointerY);
1406
+ }
1407
+ if (overlay.owns(pointerTarget)) return;
1408
+ const encoded = pointerTarget instanceof Element ? pointerTarget.closest(ENCODED_SELECTOR) : null;
1409
+ overlay.setHover(
1410
+ encoded,
1411
+ encoded ? fieldLabel(encoded.getAttribute("data-prepr-field"), locale) : ""
1412
+ );
974
1413
  };
975
- const handleTooltipMouseLeave = () => {
976
- if (!overlay.hasCurrentElement()) {
977
- overlay.hide();
1414
+ const scheduleHover = () => {
1415
+ if (pointerFrame === null) {
1416
+ pointerFrame = requestAnimationFrame(updateHover);
978
1417
  }
979
1418
  };
1419
+ const handlePointer = (e2) => {
1420
+ pointerTarget = e2.target;
1421
+ pointerX = e2.clientX;
1422
+ pointerY = e2.clientY;
1423
+ hitTestPending = false;
1424
+ scheduleHover();
1425
+ };
1426
+ const handlePointerLeave = () => {
1427
+ pointerTarget = null;
1428
+ hitTestPending = false;
1429
+ overlay.setHover(null, "");
1430
+ };
980
1431
  const handleScroll = () => {
981
- overlay.hideImmediate();
982
- proximity.clearAllHighlights();
1432
+ overlay.onScroll();
1433
+ if (pointerTarget === null) return;
1434
+ hitTestPending = true;
1435
+ scheduleHover();
983
1436
  };
1437
+ const handleResize = () => overlay.schedule();
984
1438
  const handleClick = (e2) => {
985
1439
  const target = e2.target;
986
- const encoded = target?.closest(
987
- "[data-prepr-encoded]"
988
- );
1440
+ const encoded = target?.closest(ENCODED_SELECTOR);
989
1441
  if (!encoded) return;
990
1442
  e2.preventDefault();
991
1443
  e2.stopPropagation();
992
- opts.onEdit({
993
- href: encoded.getAttribute("data-prepr-href") ?? "",
994
- origin: encoded.getAttribute("data-prepr-origin") ?? "",
995
- id: encoded.getAttribute("data-prepr-id") ?? void 0,
996
- field: encoded.getAttribute("data-prepr-field") ?? void 0,
997
- locale: encoded.getAttribute("data-prepr-locale") ?? void 0
998
- });
1444
+ opts.onEdit(readEditPayload(encoded));
999
1445
  };
1000
1446
  const start = () => {
1001
1447
  if (initialized) {
1002
- debug7.log("already initialized, skipping setup");
1448
+ debug5.log("already initialized, skipping setup");
1003
1449
  return;
1004
1450
  }
1005
1451
  if (!tooltipEnabled) {
@@ -1009,73 +1455,64 @@ function createStegaController(opts) {
1009
1455
  });
1010
1456
  document.addEventListener("click", handleClick, { capture: true });
1011
1457
  initialized = true;
1012
- debug7.log("stega controller started (editor mode)");
1458
+ debug5.log("stega controller started (headless)");
1013
1459
  return;
1014
1460
  }
1015
1461
  injectStegaStyles();
1016
- const { tooltip } = overlay.create();
1017
- tooltip.addEventListener("mouseenter", handleTooltipMouseEnter);
1018
- tooltip.addEventListener("mouseleave", handleTooltipMouseLeave);
1462
+ fadeInPresence();
1463
+ overlay.mount();
1019
1464
  elements.scanDocument(true);
1020
- proximity.refreshObserving();
1021
- elements.setupMutationObserver(() => proximity.refreshObserving());
1022
- throttledMouseMove = throttle((e2) => {
1023
- const mouseEvent = e2;
1024
- const target = mouseEvent.target;
1025
- if (!target) return;
1026
- if (target.closest(".prepr-tooltip")) return;
1027
- proximity.updateElementGradients(mouseEvent.clientX, mouseEvent.clientY);
1028
- const encoded = target.closest("[data-prepr-encoded]");
1029
- if (encoded) {
1030
- overlay.show(encoded);
1031
- } else {
1032
- overlay.hide();
1033
- }
1034
- }, 16);
1035
- document.addEventListener("mousemove", throttledMouseMove, {
1036
- passive: true
1465
+ elements.setupMutationObserver(() => {
1466
+ if (pointerTarget === null) return;
1467
+ hitTestPending = true;
1468
+ scheduleHover();
1037
1469
  });
1470
+ document.addEventListener("pointermove", handlePointer, { passive: true });
1471
+ document.addEventListener("pointerdown", handlePointer, { passive: true });
1472
+ document.documentElement.addEventListener(
1473
+ "pointerleave",
1474
+ handlePointerLeave
1475
+ );
1038
1476
  window.addEventListener("scroll", handleScroll, {
1039
1477
  capture: true,
1040
1478
  passive: true
1041
1479
  });
1480
+ window.addEventListener("resize", handleResize, { passive: true });
1042
1481
  initialized = true;
1043
- debug7.log("stega controller started");
1482
+ debug5.log("stega controller started");
1044
1483
  };
1045
1484
  const stop = () => {
1046
1485
  if (!initialized) return;
1486
+ initialized = false;
1047
1487
  if (!tooltipEnabled) {
1048
1488
  document.removeEventListener("click", handleClick, { capture: true });
1049
- elements.cleanup();
1489
+ elements.disconnect();
1050
1490
  removeStegaStyles();
1051
- initialized = false;
1052
- debug7.log("stega controller stopped (editor mode)");
1491
+ debug5.log("stega controller stopped (headless)");
1053
1492
  return;
1054
1493
  }
1055
- if (throttledMouseMove) {
1056
- document.removeEventListener("mousemove", throttledMouseMove);
1057
- throttledMouseMove.cancel();
1058
- throttledMouseMove = null;
1059
- }
1494
+ if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
1495
+ pointerFrame = null;
1496
+ pointerTarget = null;
1497
+ hitTestPending = false;
1498
+ document.removeEventListener("pointermove", handlePointer);
1499
+ document.removeEventListener("pointerdown", handlePointer);
1500
+ document.documentElement.removeEventListener(
1501
+ "pointerleave",
1502
+ handlePointerLeave
1503
+ );
1060
1504
  window.removeEventListener("scroll", handleScroll, { capture: true });
1061
- const tooltip = overlay.getTooltip();
1062
- if (tooltip) {
1063
- tooltip.removeEventListener("mouseenter", handleTooltipMouseEnter);
1064
- tooltip.removeEventListener("mouseleave", handleTooltipMouseLeave);
1065
- }
1066
- overlay.cleanup();
1067
- proximity.clearAllHighlights();
1068
- proximity.stopObserving();
1069
- elements.cleanup();
1505
+ window.removeEventListener("resize", handleResize);
1506
+ overlay.unmount();
1507
+ elements.disconnect();
1070
1508
  removeStegaStyles();
1071
- initialized = false;
1072
- debug7.log("stega controller stopped");
1509
+ debug5.log("stega controller stopped");
1073
1510
  };
1074
1511
  return { start, stop };
1075
1512
  }
1076
1513
 
1077
1514
  // src/core/stega/scroll-to-field.ts
1078
- var debug8 = createScopedLogger("stega:scroll");
1515
+ var debug6 = createScopedLogger("stega:scroll");
1079
1516
  var FLASH_DURATION_MS = 1200;
1080
1517
  var flashTimeout = null;
1081
1518
  function scrollToField(request) {
@@ -1092,7 +1529,7 @@ function scrollToField(request) {
1092
1529
  break;
1093
1530
  }
1094
1531
  if (!target) {
1095
- debug8.log("no element found for field", field);
1532
+ debug6.log("no element found for field", field);
1096
1533
  return false;
1097
1534
  }
1098
1535
  target.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -1240,7 +1677,7 @@ function splitPath(full) {
1240
1677
  return [full.slice(0, index), full.slice(index + 1)];
1241
1678
  }
1242
1679
 
1243
- // ../../node_modules/.pnpm/preact@10.29.7/node_modules/preact/dist/preact.mjs
1680
+ // ../../node_modules/.pnpm/preact@10.29.8/node_modules/preact/dist/preact.mjs
1244
1681
  var n;
1245
1682
  var l;
1246
1683
  var u;
@@ -1311,8 +1748,8 @@ function H() {
1311
1748
  }
1312
1749
  }
1313
1750
  function L(n2, l2, u3, t3, i2, r2, o2, e2, f3, c2, a2) {
1314
- var s2, h2, p2, v2, y2, _2, g2, m2 = t3 && t3.__k || w, b2 = l2.length;
1315
- for (f3 = T(u3, l2, m2, f3, b2), s2 = 0; s2 < b2; s2++) null != (p2 = u3.__k[s2]) && (h2 = -1 != p2.__i && m2[p2.__i] || d, p2.__i = s2, _2 = q(n2, p2, h2, i2, r2, o2, e2, f3, c2, a2), v2 = p2.__e, p2.ref && h2.ref != p2.ref && (h2.ref && J(h2.ref, null, p2), a2.push(p2.ref, p2.__c || v2, p2)), null == y2 && null != v2 && (y2 = v2), (g2 = !!(4 & p2.__u)) || h2.__k === p2.__k ? (f3 = j(p2, f3, n2, g2), g2 && h2.__e && (h2.__e = null)) : "function" == typeof p2.type && void 0 !== _2 ? f3 = _2 : v2 && (f3 = v2.nextSibling), p2.__u &= -7);
1751
+ var s2, h2, p2, v2, y2, _2, g2 = t3 && t3.__k || w, m2 = l2.length;
1752
+ for (f3 = T(u3, l2, g2, f3, m2), s2 = 0; s2 < m2; s2++) null != (p2 = u3.__k[s2]) && (h2 = -1 != p2.__i && g2[p2.__i] || d, p2.__i = s2, _2 = q(n2, p2, h2, i2, r2, o2, e2, f3, c2, a2), v2 = p2.__e, p2.ref && h2.ref != p2.ref && (h2.ref && J(h2.ref, null, p2), a2.push(p2.ref, p2.__c || v2, p2)), null == y2 && null != v2 && (y2 = v2), 4 & p2.__u ? (f3 = j(p2, f3, n2), h2.__e && (h2.__e = null)) : "function" == typeof p2.type && void 0 !== _2 ? f3 = _2 : v2 && (f3 = v2.nextSibling), p2.__u &= -7);
1316
1753
  return u3.__e = y2, f3;
1317
1754
  }
1318
1755
  function T(n2, l2, u3, t3, i2) {
@@ -1321,13 +1758,13 @@ function T(n2, l2, u3, t3, i2) {
1321
1758
  if (s2) for (r2 = 0; r2 < a2; r2++) null != (e2 = u3[r2]) && 0 == (2 & e2.__u) && (e2.__e == t3 && (t3 = $(e2)), K(e2, e2));
1322
1759
  return t3;
1323
1760
  }
1324
- function j(n2, l2, u3, t3) {
1325
- var i2, r2;
1761
+ function j(n2, l2, u3) {
1762
+ var t3, i2;
1326
1763
  if ("function" == typeof n2.type) {
1327
- for (i2 = n2.__k, r2 = 0; i2 && r2 < i2.length; r2++) i2[r2] && (i2[r2].__ = n2, l2 = j(i2[r2], l2, u3, t3));
1764
+ for (t3 = n2.__k, i2 = 0; t3 && i2 < t3.length; i2++) t3[i2] && (t3[i2].__ = n2, l2 = j(t3[i2], l2, u3));
1328
1765
  return l2;
1329
1766
  }
1330
- n2.__e != l2 && (t3 && (l2 && n2.type && !l2.parentNode && (l2 = $(n2)), u3.insertBefore(n2.__e, l2 || null)), l2 = n2.__e);
1767
+ n2.__e != l2 && (l2 && n2.type && !l2.parentNode && (l2 = $(n2)), l2 = u3.insertBefore(n2.__e, l2 || null));
1331
1768
  do {
1332
1769
  l2 = l2 && l2.nextSibling;
1333
1770
  } while (null != l2 && 8 == l2.nodeType);
@@ -1373,35 +1810,35 @@ function V(n2) {
1373
1810
  };
1374
1811
  }
1375
1812
  function q(n2, u3, t3, i2, r2, o2, e2, f3, c2, a2) {
1376
- var s2, h2, p2, v2, y2, d2, _2, k2, x2, M, $2, I2, P2, A2, H2, T2, j2 = u3.type;
1813
+ var s2, h2, p2, v2, y2, d2, _2, k2, x2, M, I2, P2, A2, H2, T2, j2, F = u3.type;
1377
1814
  if (void 0 !== u3.constructor) return null;
1378
1815
  128 & t3.__u && (c2 = !!(32 & t3.__u), o2 = [f3 = u3.__e = t3.__e]), (s2 = l.__b) && s2(u3);
1379
- n: if ("function" == typeof j2) {
1816
+ n: if ("function" == typeof F) {
1380
1817
  h2 = e2.length;
1381
1818
  try {
1382
- if (x2 = u3.props, M = j2.prototype && j2.prototype.render, $2 = (s2 = j2.contextType) && i2[s2.__c], I2 = s2 ? $2 ? $2.props.value : s2.__ : i2, t3.__c ? k2 = (p2 = u3.__c = t3.__c).__ = p2.__E : (M ? u3.__c = p2 = new j2(x2, I2) : (u3.__c = p2 = new C(x2, I2), p2.constructor = j2, p2.render = Q), $2 && $2.sub(p2), p2.state || (p2.state = {}), p2.__n = i2, v2 = p2.__d = true, p2.__h = [], p2._sb = []), M && null == p2.__s && (p2.__s = p2.state), M && null != j2.getDerivedStateFromProps && (p2.__s == p2.state && (p2.__s = m({}, p2.__s)), m(p2.__s, j2.getDerivedStateFromProps(x2, p2.__s))), y2 = p2.props, d2 = p2.state, p2.__v = u3, v2) M && null == j2.getDerivedStateFromProps && null != p2.componentWillMount && p2.componentWillMount(), M && null != p2.componentDidMount && p2.__h.push(p2.componentDidMount);
1819
+ if (x2 = u3.props, M = F.prototype && F.prototype.render, I2 = (s2 = F.contextType) && i2[s2.__c], P2 = s2 ? I2 ? I2.props.value : s2.__ : i2, t3.__c ? k2 = (p2 = u3.__c = t3.__c).__ = p2.__E : (M ? u3.__c = p2 = new F(x2, P2) : (u3.__c = p2 = new C(x2, P2), p2.constructor = F, p2.render = Q), I2 && I2.sub(p2), p2.state || (p2.state = {}), p2.__n = i2, v2 = p2.__d = true, p2.__h = [], p2._sb = []), M && null == p2.__s && (p2.__s = p2.state), M && null != F.getDerivedStateFromProps && (p2.__s == p2.state && (p2.__s = m({}, p2.__s)), m(p2.__s, F.getDerivedStateFromProps(x2, p2.__s))), y2 = p2.props, d2 = p2.state, p2.__v = u3, v2) M && null == F.getDerivedStateFromProps && null != p2.componentWillMount && p2.componentWillMount(), M && null != p2.componentDidMount && p2.__h.push(p2.componentDidMount);
1383
1820
  else {
1384
- if (M && null == j2.getDerivedStateFromProps && x2 !== y2 && null != p2.componentWillReceiveProps && p2.componentWillReceiveProps(x2, I2), u3.__v == t3.__v || !p2.__e && null != p2.shouldComponentUpdate && false === p2.shouldComponentUpdate(x2, p2.__s, I2)) {
1821
+ if (M && null == F.getDerivedStateFromProps && x2 !== y2 && null != p2.componentWillReceiveProps && p2.componentWillReceiveProps(x2, P2), u3.__v == t3.__v || !p2.__e && null != p2.shouldComponentUpdate && false === p2.shouldComponentUpdate(x2, p2.__s, P2)) {
1385
1822
  u3.__v != t3.__v && (p2.props = x2, p2.state = p2.__s, p2.__d = false), u3.__e = t3.__e, u3.__k = t3.__k, u3.__k.some(function(n3) {
1386
1823
  n3 && (n3.__ = u3);
1387
- }), w.push.apply(p2.__h, p2._sb), p2._sb = [], p2.__h.length && e2.push(p2);
1824
+ }), w.push.apply(p2.__h, p2._sb), p2._sb = [], p2.__h.length && e2.push(p2), f3 = $(t3);
1388
1825
  break n;
1389
1826
  }
1390
- null != p2.componentWillUpdate && p2.componentWillUpdate(x2, p2.__s, I2), M && null != p2.componentDidUpdate && p2.__h.push(function() {
1827
+ null != p2.componentWillUpdate && p2.componentWillUpdate(x2, p2.__s, P2), M && null != p2.componentDidUpdate && p2.__h.push(function() {
1391
1828
  p2.componentDidUpdate(y2, d2, _2);
1392
1829
  });
1393
1830
  }
1394
- if (p2.context = I2, p2.props = x2, p2.__P = n2, p2.__e = false, P2 = l.__r, A2 = 0, M) p2.state = p2.__s, p2.__d = false, P2 && P2(u3), s2 = p2.render(p2.props, p2.state, p2.context), w.push.apply(p2.__h, p2._sb), p2._sb = [];
1831
+ if (p2.context = P2, p2.props = x2, p2.__P = n2, p2.__e = false, A2 = l.__r, H2 = 0, M) p2.state = p2.__s, p2.__d = false, A2 && A2(u3), s2 = p2.render(p2.props, p2.state, p2.context), w.push.apply(p2.__h, p2._sb), p2._sb = [];
1395
1832
  else do {
1396
- p2.__d = false, P2 && P2(u3), s2 = p2.render(p2.props, p2.state, p2.context), p2.state = p2.__s;
1397
- } while (p2.__d && ++A2 < 25);
1398
- p2.state = p2.__s, null != p2.getChildContext && (i2 = m(m({}, i2), p2.getChildContext())), M && !v2 && null != p2.getSnapshotBeforeUpdate && (_2 = p2.getSnapshotBeforeUpdate(y2, d2)), H2 = null != s2 && s2.type === S && null == s2.key ? E(s2.props.children) : s2, f3 = L(n2, g(H2) ? H2 : [H2], u3, t3, i2, r2, o2, e2, f3, c2, a2), p2.base = u3.__e, u3.__u &= -161, p2.__h.length && e2.push(p2), k2 && (p2.__E = p2.__ = null);
1833
+ p2.__d = false, A2 && A2(u3), s2 = p2.render(p2.props, p2.state, p2.context), p2.state = p2.__s;
1834
+ } while (p2.__d && ++H2 < 25);
1835
+ p2.state = p2.__s, null != p2.getChildContext && (i2 = m(m({}, i2), p2.getChildContext())), M && !v2 && null != p2.getSnapshotBeforeUpdate && (_2 = p2.getSnapshotBeforeUpdate(y2, d2)), T2 = null != s2 && s2.type === S && null == s2.key ? E(s2.props.children) : s2, f3 = L(n2, g(T2) ? T2 : [T2], u3, t3, i2, r2, o2, e2, f3, c2, a2), p2.base = u3.__e, u3.__u &= -161, p2.__h.length && e2.push(p2), k2 && (p2.__E = p2.__ = null);
1399
1836
  } catch (n3) {
1400
1837
  if (e2.length = h2, u3.__v = null, c2 || null != o2) {
1401
1838
  if (n3.then) {
1402
1839
  for (u3.__u |= c2 ? 160 : 128; f3 && 8 == f3.nodeType && f3.nextSibling; ) f3 = f3.nextSibling;
1403
1840
  null != o2 && (o2[o2.indexOf(f3)] = null), u3.__e = f3;
1404
- } else if (null != o2) for (T2 = o2.length; T2--; ) b(o2[T2]);
1841
+ } else if (null != o2) for (j2 = o2.length; j2--; ) b(o2[j2]);
1405
1842
  } else u3.__e = t3.__e;
1406
1843
  null == u3.__k && (u3.__k = t3.__k || []), n3.then || B(u3), l.__e(n3, u3, t3);
1407
1844
  }
@@ -1522,9 +1959,9 @@ var CLOSE_ICON_SVG = `<svg width="10" height="10" viewBox="0 0 10 10" fill="none
1522
1959
  </svg>`;
1523
1960
 
1524
1961
  // src/core/ui/toolbar.generated.ts
1525
- var TOOLBAR_CSS = ":host{--prepr-primary:#4338ca;--prepr-bg:#eef2ff;--prepr-text:#1f2937;--prepr-radius:8px;--prepr-shadow:0px 0px 40px 0px #1f29373d;--prepr-z-index:10000;--prepr-primary-50:#eef2ff;--prepr-primary-100:#e0e7ff;--prepr-primary-800:#3730a3;--prepr-indigo-600:#4f46e5;--prepr-indigo-50:#eef2ff;--prepr-indigo-700:#4338ca;--prepr-secondary-400:#fb923c;--prepr-secondary-500:#f97316;--prepr-gray-100:#f3f4f6;--prepr-gray-200:#e5e7eb;--prepr-gray-300:#d1d5db;--prepr-gray-400:#9ca3af;--prepr-gray-500:#6b7280;--prepr-gray-700:#374151;--prepr-gray-800:#1f2937;--prepr-gray-900:#111827;--prepr-shadow-sm:0px 0px 12px 0px #c3c3c3cc;color:var(--prepr-text);box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}:host *,:host :before,:host :after,*,:before,:after{box-sizing:border-box}button{font-family:inherit;font-size:inherit;margin:0}.prepr-regular-text{font-size:.875rem;font-weight:500;line-height:1.5}.prepr-backdrop{z-index:calc(var(--prepr-z-index) - 1);opacity:0;pointer-events:none;background:#0000001a;transition:opacity .2s ease-in-out;position:fixed;top:0;bottom:0;left:0;right:0}.prepr-backdrop[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-container{z-index:var(--prepr-z-index);justify-content:flex-end;align-items:center;display:flex;position:fixed;top:calc(50% - 64px);right:0}.prepr-toggle-holder{padding:.5rem}.prepr-toggle{cursor:pointer;background:var(--prepr-primary);color:#fff;border:none;border-radius:9999px;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;display:flex}.prepr-toggle>span{display:flex}.prepr-toggle svg{color:#fff}.prepr-panel{z-index:var(--prepr-z-index);opacity:0;pointer-events:none;justify-content:flex-end;padding:.75rem;transition:opacity .2s ease-in-out;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.prepr-panel[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-content{box-shadow:var(--prepr-shadow);border-radius:var(--prepr-radius);background:var(--prepr-bg);flex-direction:column;gap:2.5rem;width:100%;padding:1.5rem;display:flex}@media (min-width:640px){.prepr-panel{margin-right:4rem;padding:0}.prepr-content{width:502px;padding:2.5rem}}.prepr-header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.prepr-header-left{align-items:flex-start;gap:.75rem;display:flex}.prepr-badge{background:var(--prepr-primary-100);height:1.5rem;color:var(--prepr-primary);border-radius:4px;align-items:center;padding:.25rem .75rem;font-size:.875rem;display:flex}.prepr-header-close{cursor:pointer;background:var(--prepr-primary-100);width:2rem;height:2rem;color:var(--prepr-gray-700);border:none;border-radius:6px;justify-content:center;align-items:center;display:flex}.prepr-section{flex-direction:column;gap:.5rem;display:flex}.prepr-section-label{color:var(--prepr-gray-400);font-size:.875rem}.prepr-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem 1.5rem;display:flex}.prepr-row-title{color:var(--prepr-gray-800);margin:0;font-size:1rem;font-weight:600}.prepr-radiogroup{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);background:#fff;align-items:center;gap:.25rem;height:2.5rem;padding:.25rem;display:flex}.prepr-radiogroup[data-disabled=true]{cursor:not-allowed;opacity:.5}.prepr-radiogroup[data-disabled=true] .prepr-radio{pointer-events:none}.prepr-radio{cursor:pointer;white-space:nowrap;text-align:center;height:2rem;color:var(--prepr-gray-900);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:.5rem 1.125rem;font-size:.875rem;font-weight:500;transition:all .2s ease-in-out;display:flex}.prepr-radio[data-checked=true]{background:var(--prepr-indigo-600);color:#fff;box-shadow:var(--prepr-shadow-sm)}.prepr-radio[data-checked=true][data-off=true]{background:var(--prepr-gray-800);color:#fff}.prepr-radio-variant{width:82px}.prepr-listbox{position:relative}.prepr-segment-button{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);width:240px;height:2.5rem;color:var(--prepr-gray-500);cursor:pointer;background:#fff;flex-shrink:0;align-items:center;gap:.5rem;padding:0 .5rem;font-size:.875rem;font-weight:500;display:flex;overflow:hidden}@media (min-width:768px){.prepr-segment-button{padding:0 1rem}}.prepr-segment-button:disabled{cursor:not-allowed;background:var(--prepr-gray-200);color:var(--prepr-gray-400)}.prepr-segment-button[aria-expanded=true]{border-bottom-color:#fff;border-bottom-right-radius:0;border-bottom-left-radius:0}.prepr-segment-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-segment-caret{color:var(--prepr-gray-800);display:flex}.prepr-options{z-index:calc(var(--prepr-z-index) + 1);border-left:1px solid var(--prepr-gray-300);border-right:1px solid var(--prepr-gray-300);border-bottom:1px solid var(--prepr-gray-300);-ms-overflow-style:none;scrollbar-width:none;background:#fff;border-radius:0 0 6px 6px;width:240px;max-height:300px;margin:0;padding:0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-options::-webkit-scrollbar{display:none}.prepr-options[hidden]{display:none}.prepr-option-search{background:#fff;padding:.5rem;position:-webkit-sticky;position:sticky;top:0}.prepr-segment-search{border:1px solid var(--prepr-gray-300);border-radius:var(--prepr-radius);width:100%;height:2rem;font:inherit;color:var(--prepr-gray-900);box-sizing:border-box;background:#fff;padding:0 .5rem;font-size:.875rem}.prepr-segment-search::placeholder{color:var(--prepr-gray-400)}.prepr-segment-search:focus{border-color:var(--prepr-gray-400);outline:none}.prepr-option{cursor:pointer;width:100%;color:var(--prepr-gray-900);background:#fff;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;display:flex}.prepr-option:hover{background:var(--prepr-gray-100)}.prepr-option[aria-selected=true]{background:var(--prepr-indigo-50);color:var(--prepr-indigo-700)}.prepr-option-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-reset{background:var(--prepr-secondary-400);color:#fff;cursor:pointer;border:none;border-radius:6px;justify-content:center;align-items:center;gap:.5rem;width:100%;height:2.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:500;display:flex}.prepr-reset:hover:not(:disabled){background:var(--prepr-secondary-500)}.prepr-reset:disabled{background:var(--prepr-gray-400);color:var(--prepr-gray-500);cursor:not-allowed}@media (min-width:768px){.prepr-reset{width:108px}}.prepr-indicators{z-index:var(--prepr-z-index);gap:.75rem;display:flex;position:fixed;bottom:1.5rem;right:1.5rem}.prepr-status-pill{background:var(--prepr-primary);color:#fff;cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-status-pill[data-interactive=false]{cursor:default;opacity:1;color:#fff}.prepr-status-pill[data-interactive=true]:hover{background:var(--prepr-primary-800)}.prepr-status-pill[hidden]{display:none}.prepr-status-viewing{color:#fff9;font-size:10px}.prepr-status-segment{text-overflow:ellipsis;white-space:nowrap;max-width:120px;display:inline-block;overflow:hidden}.prepr-status-variant{text-overflow:ellipsis;white-space:nowrap;background:#fff3;border-radius:4px;max-width:80px;padding:0 .5rem;display:inline-block;overflow:hidden}.prepr-status-x{color:#fff;width:.75rem;height:.75rem}.prepr-close-edit-pill{background:var(--prepr-primary-50);color:var(--prepr-gray-800);cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-close-edit-pill:hover{background:var(--prepr-primary-100)}.prepr-close-edit-pill[hidden]{display:none}.prepr-close-edit-x{width:1rem;height:1rem;color:var(--prepr-gray-500);transition:color .15s}.prepr-close-edit-pill:hover .prepr-close-edit-x{color:var(--prepr-gray-700)}.prepr-tip{z-index:calc(var(--prepr-z-index) + 2);overflow-wrap:break-word;background:var(--prepr-gray-900);color:#fff;pointer-events:none;border-radius:6px;width:auto;max-width:min(420px,100vw - 16px);padding:.5rem .75rem;font-size:.75rem;font-weight:500;position:fixed;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-tip-arrow{border-left:6px solid #0000;border-right:6px solid #0000;border-top:6px solid var(--prepr-gray-900);width:0;height:0;position:absolute;top:100%;transform:translate(-50%)}[hidden]{display:none!important}@keyframes prepr-fade-in{0%{opacity:0}to{opacity:1}}";
1962
+ var TOOLBAR_CSS = ":host{--prepr-primary:#4338ca;--prepr-bg:#eef2ff;--prepr-text:#1f2937;--prepr-radius:8px;--prepr-shadow:0px 0px 40px 0px #1f29373d;--prepr-z-index:10000;--prepr-primary-50:#eef2ff;--prepr-primary-100:#e0e7ff;--prepr-primary-800:#3730a3;--prepr-indigo-600:#4f46e5;--prepr-indigo-50:#eef2ff;--prepr-indigo-700:#4338ca;--prepr-secondary-400:#fb923c;--prepr-secondary-500:#f97316;--prepr-gray-100:#f3f4f6;--prepr-gray-200:#e5e7eb;--prepr-gray-300:#d1d5db;--prepr-gray-400:#9ca3af;--prepr-gray-500:#6b7280;--prepr-gray-700:#374151;--prepr-gray-800:#1f2937;--prepr-gray-900:#111827;--prepr-shadow-sm:0px 0px 12px 0px #c3c3c3cc;color:var(--prepr-text);box-sizing:border-box;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica,Arial,sans-serif;font-size:14px;line-height:1.5}:host *,:host :before,:host :after,*,:before,:after{box-sizing:border-box}button{font-family:inherit;font-size:inherit;margin:0}.prepr-regular-text{font-size:.875rem;font-weight:500;line-height:1.5}.prepr-backdrop{z-index:calc(var(--prepr-z-index) - 1);opacity:0;pointer-events:none;background:#0000001a;transition:opacity .2s ease-in-out;position:fixed;top:0;bottom:0;left:0;right:0}.prepr-backdrop[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-container{z-index:var(--prepr-z-index);justify-content:flex-end;align-items:center;display:flex;position:fixed;top:calc(50% - 64px);right:0}.prepr-toggle-holder{padding:.5rem}.prepr-toggle{cursor:pointer;background:var(--prepr-primary);color:#fff;border:none;border-radius:9999px;justify-content:center;align-items:center;width:2.25rem;height:2.25rem;display:flex}.prepr-toggle>span{display:flex}.prepr-toggle svg{color:#fff}.prepr-panel{z-index:var(--prepr-z-index);opacity:0;pointer-events:none;justify-content:flex-end;padding:.75rem;transition:opacity .2s ease-in-out;display:flex;position:fixed;top:50%;right:0;transform:translateY(-50%)}.prepr-panel[data-open=true]{opacity:1;pointer-events:auto;animation:.2s ease-in-out prepr-fade-in}.prepr-content{box-shadow:var(--prepr-shadow);border-radius:var(--prepr-radius);background:var(--prepr-bg);flex-direction:column;gap:2.5rem;width:100%;padding:1.5rem;display:flex}@media (min-width:640px){.prepr-panel{margin-right:4rem;padding:0}.prepr-content{width:502px;padding:2.5rem}}.prepr-header{justify-content:space-between;align-items:center;gap:.5rem;display:flex}.prepr-header-left{align-items:flex-start;gap:.75rem;display:flex}.prepr-badge{background:var(--prepr-primary-100);height:1.5rem;color:var(--prepr-primary);border-radius:4px;align-items:center;padding:.25rem .75rem;font-size:.875rem;display:flex}.prepr-header-close{cursor:pointer;background:var(--prepr-primary-100);width:2rem;height:2rem;color:var(--prepr-gray-700);border:none;border-radius:6px;justify-content:center;align-items:center;display:flex}.prepr-section{flex-direction:column;gap:.5rem;display:flex}.prepr-section-label{color:var(--prepr-gray-400);font-size:.875rem}.prepr-row{flex-wrap:wrap;justify-content:space-between;align-items:center;gap:.5rem 1.5rem;display:flex}.prepr-row-title{color:var(--prepr-gray-800);margin:0;font-size:1rem;font-weight:600}.prepr-radiogroup{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);background:#fff;align-items:center;gap:.25rem;height:2.5rem;padding:.25rem;display:flex}.prepr-radiogroup[data-disabled=true]{cursor:not-allowed;opacity:.5}.prepr-radiogroup[data-disabled=true] .prepr-radio{pointer-events:none}.prepr-radio{cursor:pointer;white-space:nowrap;text-align:center;height:2rem;color:var(--prepr-gray-900);background:0 0;border:none;border-radius:6px;justify-content:center;align-items:center;padding:.5rem 1.125rem;font-size:.875rem;font-weight:500;transition:all .2s ease-in-out;display:flex}.prepr-radio[data-checked=true]{background:var(--prepr-indigo-600);color:#fff;box-shadow:var(--prepr-shadow-sm)}.prepr-radio[data-checked=true][data-off=true]{background:var(--prepr-gray-800);color:#fff}.prepr-radio-variant{width:82px}.prepr-listbox{position:relative}.prepr-segment-button{border-radius:var(--prepr-radius);border:1px solid var(--prepr-gray-300);width:240px;height:2.5rem;color:var(--prepr-gray-500);cursor:pointer;background:#fff;flex-shrink:0;align-items:center;gap:.5rem;padding:0 .5rem;font-size:.875rem;font-weight:500;display:flex;overflow:hidden}@media (min-width:768px){.prepr-segment-button{padding:0 1rem}}.prepr-segment-button:disabled{cursor:not-allowed;background:var(--prepr-gray-200);color:var(--prepr-gray-400)}.prepr-segment-button[aria-expanded=true]{border-bottom-color:#fff;border-bottom-right-radius:0;border-bottom-left-radius:0}.prepr-segment-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-segment-caret{color:var(--prepr-gray-800);display:flex}.prepr-options{z-index:calc(var(--prepr-z-index) + 1);border-left:1px solid var(--prepr-gray-300);border-right:1px solid var(--prepr-gray-300);border-bottom:1px solid var(--prepr-gray-300);-ms-overflow-style:none;scrollbar-width:none;background:#fff;border-radius:0 0 6px 6px;width:240px;max-height:300px;margin:0;padding:0;list-style:none;position:absolute;top:100%;left:0;overflow-y:auto;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-options::-webkit-scrollbar{display:none}.prepr-options[hidden]{display:none}.prepr-option-search{background:#fff;padding:.5rem;position:sticky;top:0}.prepr-segment-search{border:1px solid var(--prepr-gray-300);border-radius:var(--prepr-radius);width:100%;height:2rem;font:inherit;color:var(--prepr-gray-900);box-sizing:border-box;background:#fff;padding:0 .5rem;font-size:.875rem}@media (pointer:coarse){.prepr-segment-search{font-size:16px}}.prepr-segment-search::placeholder{color:var(--prepr-gray-400)}.prepr-segment-search:focus{border-color:var(--prepr-gray-400);outline:none}.prepr-option{cursor:pointer;width:100%;color:var(--prepr-gray-900);background:#fff;align-items:center;padding:.5rem 1rem;font-size:.875rem;font-weight:500;display:flex}.prepr-option:hover{background:var(--prepr-gray-100)}.prepr-option[aria-selected=true]{background:var(--prepr-indigo-50);color:var(--prepr-indigo-700)}.prepr-option-label{white-space:nowrap;text-overflow:ellipsis;text-align:left;width:100%;margin-right:auto;overflow:hidden}.prepr-reset{background:var(--prepr-secondary-400);color:#fff;cursor:pointer;border:none;border-radius:6px;justify-content:center;align-items:center;gap:.5rem;width:100%;height:2.5rem;padding:.5rem .75rem;font-size:.875rem;font-weight:500;display:flex}.prepr-reset:hover:not(:disabled){background:var(--prepr-secondary-500)}.prepr-reset:disabled{background:var(--prepr-gray-400);color:var(--prepr-gray-500);cursor:not-allowed}@media (min-width:768px){.prepr-reset{width:108px}}.prepr-indicators{z-index:var(--prepr-z-index);gap:.75rem;display:flex;position:fixed;bottom:1.5rem;right:1.5rem}.prepr-status-pill{background:var(--prepr-primary);color:#fff;cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-status-pill[data-interactive=false]{cursor:default;opacity:1;color:#fff}.prepr-status-pill[data-interactive=true]:hover{background:var(--prepr-primary-800)}.prepr-status-pill[hidden]{display:none}.prepr-status-viewing{color:#fff9;font-size:10px}.prepr-status-segment{text-overflow:ellipsis;white-space:nowrap;max-width:120px;display:inline-block;overflow:hidden}.prepr-status-variant{text-overflow:ellipsis;white-space:nowrap;background:#fff3;border-radius:4px;max-width:80px;padding:0 .5rem;display:inline-block;overflow:hidden}.prepr-status-x{color:#fff;width:.75rem;height:.75rem}.prepr-close-edit-pill{background:var(--prepr-primary-50);color:var(--prepr-gray-800);cursor:pointer;border:none;border-radius:9999px;align-items:center;gap:.5rem;padding:.5rem 1rem;font-size:.75rem;font-weight:500;transition:background-color .15s;display:flex;box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -4px #0000001a}.prepr-close-edit-pill:hover{background:var(--prepr-primary-100)}.prepr-close-edit-pill[hidden]{display:none}.prepr-close-edit-x{width:1rem;height:1rem;color:var(--prepr-gray-500);transition:color .15s}.prepr-close-edit-pill:hover .prepr-close-edit-x{color:var(--prepr-gray-700)}.prepr-tip{z-index:calc(var(--prepr-z-index) + 2);overflow-wrap:break-word;background:var(--prepr-gray-900);color:#fff;pointer-events:none;border-radius:6px;width:auto;max-width:min(420px,100vw - 16px);padding:.5rem .75rem;font-size:.75rem;font-weight:500;position:fixed;box-shadow:0 20px 25px -5px #0000001a,0 8px 10px -6px #0000001a}.prepr-tip-arrow{border-left:6px solid #0000;border-right:6px solid #0000;border-top:6px solid var(--prepr-gray-900);width:0;height:0;position:absolute;top:100%;transform:translate(-50%)}[hidden]{display:none!important}@keyframes prepr-fade-in{0%{opacity:0}to{opacity:1}}";
1526
1963
 
1527
- // ../../node_modules/.pnpm/preact@10.29.7/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
1964
+ // ../../node_modules/.pnpm/preact@10.29.8/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
1528
1965
  var f2 = 0;
1529
1966
  function u2(e2, t3, n2, o2, i2, u3) {
1530
1967
  t3 || (t3 = {});
@@ -2217,46 +2654,780 @@ function definePreprToolbar() {
2217
2654
  }
2218
2655
  }
2219
2656
 
2220
- // src/core/create-preview.ts
2221
- var debug9 = createScopedLogger("create-preview");
2222
- var controllerStores = /* @__PURE__ */ new WeakMap();
2223
- function defaultNavigation() {
2224
- return {
2225
- navigate: (url) => window.location.assign(url),
2226
- currentPath: () => window.location.pathname + window.location.search
2227
- };
2657
+ // src/core/visual-editor/asset-images.ts
2658
+ var MAX_URL_LENGTH = 4096;
2659
+ var MIN_KEY_SEGMENTS = 2;
2660
+ var TRANSFORM_OPTION = /^[a-z]{1,3}_[\w.-]*$/i;
2661
+ var FORMAT_PLACEHOLDER = "{format}";
2662
+ var PROXY_PARAMS = ["url", "href", "src", "image"];
2663
+ var ABSOLUTE_HTTP = /^https?:\/\//i;
2664
+ var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
2665
+ var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
2666
+ var ASSET_ATTR = "data-prepr-asset";
2667
+ var ASSET_SOURCE_ATTRIBUTES = [
2668
+ ...SOURCE_URL_ATTRIBUTES,
2669
+ ...SOURCE_SET_ATTRIBUTES
2670
+ ];
2671
+ var TAG_ATTRIBUTES = [
2672
+ "data-prepr-encoded",
2673
+ "data-prepr-id",
2674
+ "data-prepr-field",
2675
+ "data-prepr-entry-id",
2676
+ ASSET_ATTR
2677
+ ];
2678
+ function decodeSegment(segment) {
2679
+ try {
2680
+ return decodeURIComponent(segment);
2681
+ } catch {
2682
+ return segment;
2683
+ }
2228
2684
  }
2229
- function safeEditUrl(href) {
2685
+ function isTransformSegment(segment) {
2686
+ return segment === FORMAT_PLACEHOLDER || segment.split(",").every((option) => TRANSFORM_OPTION.test(option));
2687
+ }
2688
+ function assetKey(url, depth = 0) {
2689
+ if (!url || url.length > MAX_URL_LENGTH) return null;
2690
+ let parsed;
2230
2691
  try {
2231
- const url = new URL(href, window.location.href);
2232
- return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
2692
+ parsed = new URL(url, "https://base.invalid");
2233
2693
  } catch {
2234
2694
  return null;
2235
2695
  }
2696
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
2697
+ if (depth === 0) {
2698
+ for (const name of PROXY_PARAMS) {
2699
+ const inner = parsed.searchParams.get(name);
2700
+ if (inner && ABSOLUTE_HTTP.test(inner)) return assetKey(inner, 1);
2701
+ }
2702
+ }
2703
+ const segments = parsed.pathname.split("/").filter(Boolean).map(decodeSegment);
2704
+ const file = segments.pop();
2705
+ if (!file) return null;
2706
+ const kept = segments.filter((segment) => !isTransformSegment(segment));
2707
+ kept.push(file);
2708
+ return kept.length >= MIN_KEY_SEGMENTS ? kept.join("/") : null;
2236
2709
  }
2237
- function buildSegments(data) {
2238
- return [{ _id: "all_other_users", name: "All other users" }, ...data];
2239
- }
2240
- function resolveLocale(options) {
2241
- const explicit = options?.locale;
2242
- if (isLocale(explicit)) return explicit;
2243
- if (typeof navigator !== "undefined") {
2244
- const candidates = Array.isArray(navigator.languages) && navigator.languages.length ? navigator.languages : [navigator.language];
2245
- const match = candidates.filter(Boolean).map((l2) => l2.toLowerCase().split("-")[0]).find(isLocale);
2246
- if (match) return match;
2710
+ function srcsetUrls(srcset) {
2711
+ const urls = [];
2712
+ let expectUrl = true;
2713
+ for (const token of srcset.split(/\s+/)) {
2714
+ if (!token) continue;
2715
+ if (!expectUrl) {
2716
+ const comma = token.indexOf(",");
2717
+ if (comma === -1) continue;
2718
+ expectUrl = true;
2719
+ const rest = token.slice(comma + 1);
2720
+ if (!rest) continue;
2721
+ urls.push(rest.replace(/,+$/, ""));
2722
+ expectUrl = rest.endsWith(",");
2723
+ continue;
2724
+ }
2725
+ const url = token.replace(/^,+|,+$/g, "");
2726
+ if (url) urls.push(url);
2727
+ expectUrl = token.endsWith(",");
2247
2728
  }
2248
- return "en";
2729
+ return urls;
2249
2730
  }
2250
- function createPreprPreview(opts = {}) {
2251
- const { props, options } = opts;
2252
- const navigation = opts.navigation ?? defaultNavigation();
2253
- initDebugLogger(options?.debug ?? false);
2254
- const noop = () => ({ destroy: () => {
2255
- } });
2256
- if (typeof window === "undefined" || typeof document === "undefined") {
2257
- return noop();
2731
+ function createAssetIndex(assets) {
2732
+ const index = /* @__PURE__ */ new Map();
2733
+ for (const asset of assets) {
2734
+ const key = assetKey(asset.key);
2735
+ if (key && !index.has(key)) index.set(key, asset);
2258
2736
  }
2259
- const isIframe = window.parent !== window;
2737
+ return index;
2738
+ }
2739
+ function urlsOf(img) {
2740
+ const urls = [];
2741
+ const collect = (el) => {
2742
+ for (const name of SOURCE_URL_ATTRIBUTES) {
2743
+ const value = el.getAttribute(name);
2744
+ if (value) urls.push(value);
2745
+ }
2746
+ for (const name of SOURCE_SET_ATTRIBUTES) {
2747
+ const value = el.getAttribute(name);
2748
+ if (value) urls.push(...srcsetUrls(value));
2749
+ }
2750
+ };
2751
+ collect(img);
2752
+ const picture = img.parentElement;
2753
+ if (picture?.tagName === "PICTURE") {
2754
+ for (const source of picture.children) {
2755
+ if (source.tagName === "SOURCE") collect(source);
2756
+ }
2757
+ }
2758
+ return urls;
2759
+ }
2760
+ function matchAsset(img, index, keys) {
2761
+ for (const url of urlsOf(img)) {
2762
+ let key = keys.get(url);
2763
+ if (key === void 0) {
2764
+ key = assetKey(url);
2765
+ keys.set(url, key);
2766
+ }
2767
+ const asset = key ? index.get(key) : void 0;
2768
+ if (asset) return asset;
2769
+ }
2770
+ return void 0;
2771
+ }
2772
+ function setIfChanged(el, name, value) {
2773
+ if (value === null) {
2774
+ if (el.hasAttribute(name)) el.removeAttribute(name);
2775
+ } else if (el.getAttribute(name) !== value) {
2776
+ el.setAttribute(name, value);
2777
+ }
2778
+ }
2779
+ function untagAssetImage(img) {
2780
+ for (const name of TAG_ATTRIBUTES) img.removeAttribute(name);
2781
+ }
2782
+ var TEXT_FIELD_SELECTOR = `[data-prepr-encoded]:not([${ASSET_ATTR}])`;
2783
+ function besideOtherItem(img, isCurrentItem) {
2784
+ for (let block = img.parentElement; block && block !== document.body; block = block.parentElement) {
2785
+ const texts = block.querySelectorAll(TEXT_FIELD_SELECTOR);
2786
+ if (texts.length === 0) continue;
2787
+ for (const text of texts) {
2788
+ if (isCurrentItem(text)) return false;
2789
+ }
2790
+ return true;
2791
+ }
2792
+ return false;
2793
+ }
2794
+ function tagAssetImages(index, itemId, keys, options = {}) {
2795
+ const { isCurrentItem } = options;
2796
+ for (const img of document.getElementsByTagName("img")) {
2797
+ const ours = img.hasAttribute(ASSET_ATTR);
2798
+ if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
2799
+ const match = index.size ? matchAsset(img, index, keys) : void 0;
2800
+ const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
2801
+ if (!asset) {
2802
+ if (ours) untagAssetImage(img);
2803
+ continue;
2804
+ }
2805
+ setIfChanged(img, "data-prepr-encoded", "");
2806
+ setIfChanged(img, ASSET_ATTR, "");
2807
+ setIfChanged(img, "data-prepr-id", itemId);
2808
+ setIfChanged(img, "data-prepr-field", asset.field);
2809
+ setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
2810
+ }
2811
+ }
2812
+ function untagAssetImages() {
2813
+ document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
2814
+ }
2815
+
2816
+ // src/core/visual-editor/navigation-guard.ts
2817
+ var debug7 = createScopedLogger("navigation-guard");
2818
+ var trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
2819
+ function resolve(url) {
2820
+ try {
2821
+ return new URL(url, document.baseURI);
2822
+ } catch {
2823
+ return null;
2824
+ }
2825
+ }
2826
+ function leavesPage(url) {
2827
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
2828
+ const here = window.location;
2829
+ return url.origin !== here.origin || trimSlash(url.pathname) !== trimSlash(here.pathname);
2830
+ }
2831
+ function leavingLinkHref(target) {
2832
+ if (!(target instanceof Element)) return null;
2833
+ const link = target.closest("a[href]");
2834
+ if (!link || link.hasAttribute("download")) return null;
2835
+ const linkTarget = link.getAttribute("target");
2836
+ if (linkTarget && linkTarget !== "_self") return null;
2837
+ const href = link.getAttribute("href") ?? "";
2838
+ if (href.startsWith("#")) return null;
2839
+ const url = resolve(href);
2840
+ return url && leavesPage(url) ? url.href : null;
2841
+ }
2842
+ function submitterAttr(submitter, attr) {
2843
+ if (!submitter?.hasAttribute(attr)) return null;
2844
+ if (attr === "formaction") return submitter.formAction;
2845
+ if (attr === "formmethod") return submitter.formMethod;
2846
+ return submitter.formTarget;
2847
+ }
2848
+ function submissionUrl(form, submitter) {
2849
+ const method = (submitterAttr(submitter, "formmethod") ?? form.method).toLowerCase();
2850
+ if (method === "dialog") return null;
2851
+ const target = submitterAttr(submitter, "formtarget") ?? form.target;
2852
+ if (target && target !== "_self") return null;
2853
+ const url = resolve(
2854
+ submitterAttr(submitter, "formaction") ?? (form.action || location.href)
2855
+ );
2856
+ if (!url) return null;
2857
+ if (method === "get") {
2858
+ let data;
2859
+ try {
2860
+ data = new FormData(form, submitter);
2861
+ } catch {
2862
+ data = new FormData(form);
2863
+ }
2864
+ const query = new URLSearchParams();
2865
+ data.forEach((value, key) => {
2866
+ query.append(key, typeof value === "string" ? value : value.name);
2867
+ });
2868
+ url.search = query.toString();
2869
+ }
2870
+ return url;
2871
+ }
2872
+ function createNavigationGuard(opts) {
2873
+ let active = false;
2874
+ let permitted = null;
2875
+ let pendingForm = null;
2876
+ let originalPush = null;
2877
+ let guardedPush = null;
2878
+ const isPermitted = (url) => permitted === url.href;
2879
+ const guarding = () => active && opts.showsEditedContent();
2880
+ const report = (href) => {
2881
+ debug7.log("blocked navigation to", href);
2882
+ opts.onBlocked(href);
2883
+ };
2884
+ const onSubmit = (e2) => {
2885
+ if (e2.defaultPrevented || !(e2.target instanceof HTMLFormElement)) return;
2886
+ if (!guarding()) return;
2887
+ const submitter = e2.submitter ?? null;
2888
+ const url = submissionUrl(e2.target, submitter);
2889
+ if (!url || !leavesPage(url) || isPermitted(url)) return;
2890
+ e2.preventDefault();
2891
+ pendingForm = { form: e2.target, submitter, href: url.href };
2892
+ report(url.href);
2893
+ };
2894
+ const onNavigate = (event) => {
2895
+ const e2 = event;
2896
+ if (e2.navigationType === "traverse" || e2.navigationType === "reload") {
2897
+ return;
2898
+ }
2899
+ if (!e2.cancelable || e2.hashChange || e2.downloadRequest !== null) return;
2900
+ if (!guarding()) return;
2901
+ if (e2.navigationType === "replace" && e2.destination.sameDocument) return;
2902
+ const url = resolve(e2.destination.url);
2903
+ if (!url || !leavesPage(url) || isPermitted(url)) return;
2904
+ e2.preventDefault();
2905
+ report(url.href);
2906
+ if (e2.destination.sameDocument) window.location.reload();
2907
+ };
2908
+ const navigation = () => window.navigation;
2909
+ function installPushGuard() {
2910
+ const original = history.pushState;
2911
+ const guard = function(data, unused, url) {
2912
+ if (guarding() && guardedPush === guard && url != null) {
2913
+ const target = resolve(url);
2914
+ if (target && leavesPage(target) && !isPermitted(target)) {
2915
+ report(target.href);
2916
+ window.location.reload();
2917
+ return;
2918
+ }
2919
+ }
2920
+ return original.call(this, data, unused, url);
2921
+ };
2922
+ originalPush = original;
2923
+ guardedPush = guard;
2924
+ history.pushState = guard;
2925
+ }
2926
+ function uninstallPushGuard() {
2927
+ if (history.pushState === guardedPush && originalPush) {
2928
+ history.pushState = originalPush;
2929
+ }
2930
+ originalPush = null;
2931
+ guardedPush = null;
2932
+ }
2933
+ return {
2934
+ start() {
2935
+ if (active) return;
2936
+ active = true;
2937
+ installPushGuard();
2938
+ window.addEventListener("submit", onSubmit);
2939
+ navigation()?.addEventListener("navigate", onNavigate);
2940
+ },
2941
+ stop() {
2942
+ if (!active) return;
2943
+ active = false;
2944
+ uninstallPushGuard();
2945
+ window.removeEventListener("submit", onSubmit);
2946
+ navigation()?.removeEventListener("navigate", onNavigate);
2947
+ pendingForm = null;
2948
+ },
2949
+ isActive: guarding,
2950
+ block(href) {
2951
+ report(href);
2952
+ },
2953
+ permit(href) {
2954
+ permitted = resolve(href)?.href ?? null;
2955
+ },
2956
+ allow(href) {
2957
+ const url = resolve(href);
2958
+ if (!url || url.protocol !== "http:" && url.protocol !== "https:") {
2959
+ debug7.warn("ignored navigation to unsupported URL");
2960
+ return;
2961
+ }
2962
+ permitted = url.href;
2963
+ const form = pendingForm;
2964
+ pendingForm = null;
2965
+ if (form && form.href === url.href && form.form.isConnected) {
2966
+ try {
2967
+ form.form.requestSubmit(form.submitter ?? void 0);
2968
+ } catch {
2969
+ form.form.requestSubmit();
2970
+ }
2971
+ return;
2972
+ }
2973
+ window.location.assign(url.href);
2974
+ }
2975
+ };
2976
+ }
2977
+
2978
+ // src/core/visual-editor/index.ts
2979
+ var debug8 = createScopedLogger("visual-editor");
2980
+ var ENCODED_SELECTOR2 = "[data-prepr-encoded]";
2981
+ var REFERENCE_ATTR = "data-prepr-reference";
2982
+ var REFERENCE_SELECTOR = `[${REFERENCE_ATTR}]`;
2983
+ var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
2984
+ var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
2985
+ var SUSPENDED_ATTR = "data-prepr-ve-suspended";
2986
+ var FIND_RETRY_MS = 150;
2987
+ var FIND_RETRIES = 6;
2988
+ var MAX_CACHED_KEYS = 2e3;
2989
+ var LAYOUT_MUTATIONS = {
2990
+ childList: true,
2991
+ subtree: true,
2992
+ characterData: true,
2993
+ attributes: true,
2994
+ attributeFilter: [
2995
+ "class",
2996
+ "style",
2997
+ "hidden",
2998
+ "open",
2999
+ "data-prepr-encoded",
3000
+ // An image swapped by a carousel or lazy loader can show another asset.
3001
+ ...ASSET_SOURCE_ATTRIBUTES
3002
+ ]
3003
+ };
3004
+ function scrollIntoFrame(el) {
3005
+ const rect = el.getBoundingClientRect();
3006
+ const offset = Math.max(0, (window.innerHeight - rect.height) / 2);
3007
+ window.scrollTo({
3008
+ top: Math.max(0, window.scrollY + rect.top - offset),
3009
+ behavior: "smooth"
3010
+ });
3011
+ }
3012
+ var isVisible = (el) => typeof el.checkVisibility === "function" ? el.checkVisibility() : el.getClientRects().length > 0;
3013
+ function createVisualEditor(opts) {
3014
+ const overlay = new VisualEditorOverlay();
3015
+ let running = false;
3016
+ let currentItemId = null;
3017
+ let locale = opts.locale ?? "en";
3018
+ let suspended = false;
3019
+ let pointerFrame = null;
3020
+ let lastPointerTarget = null;
3021
+ let lastPointerX = 0;
3022
+ let lastPointerY = 0;
3023
+ let mutationFrame = null;
3024
+ let mutationObserver = null;
3025
+ let selection = null;
3026
+ let findTimeout = null;
3027
+ let settlePendingFind = null;
3028
+ let guardEnabled = false;
3029
+ let assetIndex = /* @__PURE__ */ new Map();
3030
+ const assetKeys = /* @__PURE__ */ new Map();
3031
+ const guard = createNavigationGuard({
3032
+ onBlocked: (href) => opts.onNavigationBlocked?.(href),
3033
+ showsEditedContent: () => showsEditedContent()
3034
+ });
3035
+ const encodedFrom = (node) => node instanceof Element ? node.closest(ENCODED_SELECTOR2) : null;
3036
+ const labelFor = (el) => fieldLabel(el.getAttribute("data-prepr-field"), locale);
3037
+ let references = null;
3038
+ let ownComponents = /* @__PURE__ */ new Set();
3039
+ const isCurrentItem = (el) => {
3040
+ if (currentItemId === null) return true;
3041
+ const id = el.getAttribute("data-prepr-id");
3042
+ return !id || id === currentItemId || ownComponents.has(id);
3043
+ };
3044
+ const showsEditedContent = () => {
3045
+ for (const el of document.querySelectorAll(ENCODED_SELECTOR2)) {
3046
+ if (isCurrentItem(el) && isVisible(el)) return true;
3047
+ }
3048
+ return false;
3049
+ };
3050
+ const linkedIdOf = (el) => {
3051
+ if (!references) return null;
3052
+ const id = el.getAttribute("data-prepr-id") ?? "";
3053
+ if (references.has(id)) return id;
3054
+ let container = el.closest(REFERENCE_SELECTOR);
3055
+ while (container) {
3056
+ const linked = container.getAttribute(REFERENCE_ATTR) ?? "";
3057
+ if (references.has(linked)) return linked;
3058
+ container = container.parentElement?.closest(REFERENCE_SELECTOR) ?? null;
3059
+ }
3060
+ return null;
3061
+ };
3062
+ const itemKind = (el) => {
3063
+ if (isCurrentItem(el)) return "current";
3064
+ if (!references) return "other";
3065
+ return linkedIdOf(el) ? "referenced" : null;
3066
+ };
3067
+ const referenceFieldOf = (el) => {
3068
+ const linked = linkedIdOf(el);
3069
+ return linked ? references?.get(linked) : void 0;
3070
+ };
3071
+ const hoverLabel = (el, kind) => {
3072
+ if (kind === "other") {
3073
+ return `${labelFor(el)} \xB7 ${t("visualEditor.otherItem", locale)}`;
3074
+ }
3075
+ if (kind === "referenced") {
3076
+ const via = referenceFieldOf(el);
3077
+ const prefix = via ? `${fieldLabel(via, locale)} \u203A ` : "";
3078
+ return `${prefix}${labelFor(el)} \xB7 ${t("visualEditor.linkedItem", locale)}`;
3079
+ }
3080
+ return labelFor(el);
3081
+ };
3082
+ const setSuspended = (value) => {
3083
+ if (suspended === value) return;
3084
+ suspended = value;
3085
+ document.documentElement.toggleAttribute(SUSPENDED_ATTR, value);
3086
+ if (value) overlay.setHover(null, "");
3087
+ };
3088
+ const updateHover = () => {
3089
+ pointerFrame = null;
3090
+ overlay.schedule();
3091
+ if (suspended) return;
3092
+ const encoded = encodedFrom(lastPointerTarget);
3093
+ const target = encoded === overlay.getSelectedTarget() ? null : encoded;
3094
+ if (!target) {
3095
+ overlay.setHover(null, "");
3096
+ return;
3097
+ }
3098
+ const kind = itemKind(target);
3099
+ if (!kind) {
3100
+ overlay.setHover(null, "");
3101
+ return;
3102
+ }
3103
+ overlay.setHover(target, hoverLabel(target, kind), { kind });
3104
+ };
3105
+ const handlePointerMove = (e2) => {
3106
+ setSuspended(e2.altKey);
3107
+ lastPointerTarget = e2.target;
3108
+ lastPointerX = e2.clientX;
3109
+ lastPointerY = e2.clientY;
3110
+ if (pointerFrame === null) {
3111
+ pointerFrame = requestAnimationFrame(updateHover);
3112
+ }
3113
+ };
3114
+ const handlePointerLeave = () => {
3115
+ lastPointerTarget = null;
3116
+ overlay.setHover(null, "");
3117
+ };
3118
+ const handleClick = (e2) => {
3119
+ if (e2.altKey) {
3120
+ const href = leavingLinkHref(e2.target);
3121
+ if (href && guard.isActive()) guard.permit(href);
3122
+ return;
3123
+ }
3124
+ const encoded = encodedFrom(e2.target);
3125
+ const kind = encoded ? itemKind(encoded) : null;
3126
+ if (!encoded || !kind) {
3127
+ clearSelection();
3128
+ if (e2.ctrlKey || e2.metaKey || e2.shiftKey || !showsEditedContent()) return;
3129
+ const href = leavingLinkHref(e2.target);
3130
+ if (!href) return;
3131
+ e2.preventDefault();
3132
+ if (guard.isActive()) {
3133
+ e2.stopPropagation();
3134
+ guard.block(href);
3135
+ }
3136
+ return;
3137
+ }
3138
+ e2.preventDefault();
3139
+ e2.stopPropagation();
3140
+ const item = listItemIn(encoded, e2.target);
3141
+ if (kind === "current")
3142
+ select(item?.element ?? encoded, encoded, item?.index);
3143
+ const referenceField = kind === "referenced" ? referenceFieldOf(encoded) : void 0;
3144
+ const linkedId = kind === "referenced" ? linkedIdOf(encoded) : null;
3145
+ opts.onEdit({
3146
+ ...readEditPayload(encoded),
3147
+ ...linkedId && { id: linkedId },
3148
+ ...item && { block: item.index },
3149
+ ...referenceField && { referenceField }
3150
+ });
3151
+ };
3152
+ const handleKey = (e2) => {
3153
+ if (e2.key === "Alt") setSuspended(e2.type === "keydown");
3154
+ if (e2.type === "keydown" && e2.key === "Escape") {
3155
+ clearSelection();
3156
+ }
3157
+ };
3158
+ const handleBlur = () => setSuspended(false);
3159
+ const handleScroll = () => overlay.onScroll();
3160
+ const handleResize = () => overlay.schedule();
3161
+ const handleSettled = () => overlay.schedule();
3162
+ function listItemIn(encoded, target) {
3163
+ if (!(target instanceof Element)) return null;
3164
+ const li = target.closest("li");
3165
+ if (!li || !encoded.contains(li) || !li.parentElement) return null;
3166
+ const items = Array.from(li.parentElement.children).filter(
3167
+ (child) => child.tagName === "LI"
3168
+ );
3169
+ return { element: li, index: items.indexOf(li) };
3170
+ }
3171
+ function nthListItem(encoded, block) {
3172
+ const list = encoded.querySelector("ul, ol");
3173
+ if (!list) return null;
3174
+ const items = Array.from(list.children).filter(
3175
+ (child) => child.tagName === "LI"
3176
+ );
3177
+ return items[block] ?? null;
3178
+ }
3179
+ function select(el, field, block) {
3180
+ const entryId = field.getAttribute("data-prepr-entry-id");
3181
+ selection = {
3182
+ lookup: {
3183
+ field: field.getAttribute("data-prepr-field") ?? "",
3184
+ id: field.getAttribute("data-prepr-id") ?? void 0,
3185
+ ...entryId && { entryId },
3186
+ ...block !== void 0 && { block }
3187
+ },
3188
+ label: field
3189
+ };
3190
+ overlay.setSelected(el, labelFor(field));
3191
+ if (overlay.getHoverTarget() === el) overlay.setHover(null, "");
3192
+ }
3193
+ function clearSelection() {
3194
+ selection = null;
3195
+ overlay.setSelected(null, "");
3196
+ }
3197
+ function reselect() {
3198
+ if (!selection) return;
3199
+ const current = overlay.getSelectedTarget();
3200
+ if (current?.isConnected && selection.label.isConnected) return;
3201
+ const found = findFieldElement(selection.lookup);
3202
+ if (!found || !isCurrentItem(found)) {
3203
+ overlay.setSelected(null, "");
3204
+ return;
3205
+ }
3206
+ const { block } = selection.lookup;
3207
+ const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
3208
+ select(el, found, block);
3209
+ }
3210
+ function tagAssets() {
3211
+ if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
3212
+ tagAssetImages(assetIndex, currentItemId, assetKeys, {
3213
+ isCurrentItem: (el) => isCurrentItem(el)
3214
+ });
3215
+ }
3216
+ const handleLayoutFrame = () => {
3217
+ mutationFrame = null;
3218
+ if (assetIndex.size) tagAssets();
3219
+ reselect();
3220
+ const hovered = overlay.getHoverTarget();
3221
+ if (hovered && !hovered.isConnected) {
3222
+ lastPointerTarget = document.elementFromPoint(lastPointerX, lastPointerY);
3223
+ updateHover();
3224
+ return;
3225
+ }
3226
+ overlay.schedule();
3227
+ };
3228
+ const handleMutations = () => {
3229
+ if (mutationFrame !== null) return;
3230
+ if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
3231
+ mutationFrame = requestAnimationFrame(handleLayoutFrame);
3232
+ };
3233
+ function injectCursorStyles() {
3234
+ if (document.querySelector(`style[${CURSOR_STYLE_MARKER}]`)) return;
3235
+ const style = document.createElement("style");
3236
+ style.setAttribute(CURSOR_STYLE_MARKER, "");
3237
+ style.textContent = CURSOR_STYLES;
3238
+ document.head.appendChild(style);
3239
+ }
3240
+ function start() {
3241
+ if (running) return;
3242
+ running = true;
3243
+ overlay.mount();
3244
+ injectCursorStyles();
3245
+ document.addEventListener("pointermove", handlePointerMove, {
3246
+ passive: true
3247
+ });
3248
+ document.documentElement.addEventListener(
3249
+ "pointerleave",
3250
+ handlePointerLeave
3251
+ );
3252
+ document.addEventListener("click", handleClick, { capture: true });
3253
+ window.addEventListener("keydown", handleKey);
3254
+ window.addEventListener("keyup", handleKey);
3255
+ window.addEventListener("blur", handleBlur);
3256
+ window.addEventListener("scroll", handleScroll, {
3257
+ capture: true,
3258
+ passive: true
3259
+ });
3260
+ window.addEventListener("resize", handleResize, { passive: true });
3261
+ document.addEventListener("transitionend", handleSettled, {
3262
+ capture: true,
3263
+ passive: true
3264
+ });
3265
+ document.addEventListener("animationend", handleSettled, {
3266
+ capture: true,
3267
+ passive: true
3268
+ });
3269
+ mutationObserver = new MutationObserver(handleMutations);
3270
+ mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
3271
+ if (assetIndex.size) tagAssets();
3272
+ syncGuard();
3273
+ debug8.log("visual editor started");
3274
+ }
3275
+ function stop() {
3276
+ if (!running) return;
3277
+ running = false;
3278
+ if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
3279
+ if (mutationFrame !== null) cancelAnimationFrame(mutationFrame);
3280
+ mutationObserver?.disconnect();
3281
+ mutationObserver = null;
3282
+ cancelFind();
3283
+ pointerFrame = null;
3284
+ mutationFrame = null;
3285
+ selection = null;
3286
+ lastPointerTarget = null;
3287
+ setSuspended(false);
3288
+ document.removeEventListener("pointermove", handlePointerMove);
3289
+ document.documentElement.removeEventListener(
3290
+ "pointerleave",
3291
+ handlePointerLeave
3292
+ );
3293
+ document.removeEventListener("click", handleClick, { capture: true });
3294
+ window.removeEventListener("keydown", handleKey);
3295
+ window.removeEventListener("keyup", handleKey);
3296
+ window.removeEventListener("blur", handleBlur);
3297
+ window.removeEventListener("scroll", handleScroll, { capture: true });
3298
+ window.removeEventListener("resize", handleResize);
3299
+ document.removeEventListener("transitionend", handleSettled, {
3300
+ capture: true
3301
+ });
3302
+ document.removeEventListener("animationend", handleSettled, {
3303
+ capture: true
3304
+ });
3305
+ document.querySelectorAll(`style[${CURSOR_STYLE_MARKER}]`).forEach((node) => node.remove());
3306
+ overlay.unmount();
3307
+ untagAssetImages();
3308
+ guard.stop();
3309
+ debug8.log("visual editor stopped");
3310
+ }
3311
+ function cancelFind() {
3312
+ if (findTimeout) clearTimeout(findTimeout);
3313
+ findTimeout = null;
3314
+ settlePendingFind?.();
3315
+ settlePendingFind = null;
3316
+ }
3317
+ function focusField(lookup) {
3318
+ cancelFind();
3319
+ return new Promise((resolve2) => {
3320
+ let attempt = 0;
3321
+ const finish = (found) => {
3322
+ settlePendingFind = null;
3323
+ resolve2(found);
3324
+ };
3325
+ settlePendingFind = () => resolve2(false);
3326
+ const tryFind = () => {
3327
+ findTimeout = null;
3328
+ const found = findFieldElement(lookup);
3329
+ if (found) {
3330
+ const el = lookup.block !== void 0 ? nthListItem(found, lookup.block) ?? found : found;
3331
+ scrollIntoFrame(el);
3332
+ if (running && isCurrentItem(found)) select(el, found, lookup.block);
3333
+ finish(true);
3334
+ return;
3335
+ }
3336
+ if (++attempt >= FIND_RETRIES) {
3337
+ debug8.log("no element renders field", lookup.field);
3338
+ finish(false);
3339
+ return;
3340
+ }
3341
+ findTimeout = setTimeout(tryFind, FIND_RETRY_MS);
3342
+ };
3343
+ tryFind();
3344
+ });
3345
+ }
3346
+ function setCurrentItem(itemId, referenceList, componentIds) {
3347
+ currentItemId = itemId;
3348
+ if (referenceList) setReferences(referenceList, componentIds);
3349
+ else if (componentIds) ownComponents = new Set(componentIds);
3350
+ if (running && assetIndex.size) tagAssets();
3351
+ const selected = overlay.getSelectedTarget();
3352
+ const owner = selected ? encodedFrom(selected) : null;
3353
+ if (owner && !isCurrentItem(owner)) clearSelection();
3354
+ }
3355
+ function setAssets(assets) {
3356
+ const hadAssets = assetIndex.size > 0;
3357
+ assetIndex = createAssetIndex(assets);
3358
+ if (running && (hadAssets || assetIndex.size)) tagAssets();
3359
+ }
3360
+ function setReferences(referenceList, componentIds) {
3361
+ if (componentIds) ownComponents = new Set(componentIds);
3362
+ references = /* @__PURE__ */ new Map();
3363
+ for (const { id, field } of referenceList) {
3364
+ if (!references.has(id)) references.set(id, field);
3365
+ }
3366
+ }
3367
+ function setLocale(value) {
3368
+ locale = value;
3369
+ }
3370
+ function syncGuard() {
3371
+ if (running && guardEnabled) guard.start();
3372
+ else guard.stop();
3373
+ }
3374
+ function setNavigationGuard(enabled) {
3375
+ guardEnabled = enabled;
3376
+ syncGuard();
3377
+ }
3378
+ return {
3379
+ start,
3380
+ stop,
3381
+ setCurrentItem,
3382
+ setReferences,
3383
+ setAssets,
3384
+ setLocale,
3385
+ focusField,
3386
+ setNavigationGuard,
3387
+ allowNavigation: (href) => guard.allow(href)
3388
+ };
3389
+ }
3390
+
3391
+ // src/core/create-preview.ts
3392
+ var debug9 = createScopedLogger("create-preview");
3393
+ var controllerStores = /* @__PURE__ */ new WeakMap();
3394
+ function defaultNavigation() {
3395
+ return {
3396
+ navigate: (url) => window.location.assign(url),
3397
+ currentPath: () => window.location.pathname + window.location.search
3398
+ };
3399
+ }
3400
+ function safeEditUrl(href) {
3401
+ try {
3402
+ const url = new URL(href, window.location.href);
3403
+ return url.protocol === "https:" || url.protocol === "http:" ? url.href : null;
3404
+ } catch {
3405
+ return null;
3406
+ }
3407
+ }
3408
+ function buildSegments(data) {
3409
+ return [{ _id: "all_other_users", name: "All other users" }, ...data];
3410
+ }
3411
+ function resolveLocale(options) {
3412
+ const explicit = options?.locale;
3413
+ if (isLocale(explicit)) return explicit;
3414
+ if (typeof navigator !== "undefined") {
3415
+ const candidates = Array.isArray(navigator.languages) && navigator.languages.length ? navigator.languages : [navigator.language];
3416
+ const match = candidates.filter(Boolean).map((l2) => l2.toLowerCase().split("-")[0]).find(isLocale);
3417
+ if (match) return match;
3418
+ }
3419
+ return "en";
3420
+ }
3421
+ function createPreprPreview(opts = {}) {
3422
+ const { props, options } = opts;
3423
+ const navigation = opts.navigation ?? defaultNavigation();
3424
+ initDebugLogger(options?.debug ?? false);
3425
+ const noop = () => ({ destroy: () => {
3426
+ } });
3427
+ if (typeof window === "undefined" || typeof document === "undefined") {
3428
+ return noop();
3429
+ }
3430
+ const isIframe = window.parent !== window;
2260
3431
  const search = new URLSearchParams(window.location.search);
2261
3432
  const hideBar = search.get(PARAM_HIDE_BAR) === "true";
2262
3433
  if (hideBar) {
@@ -2289,29 +3460,45 @@ function createPreprPreview(opts = {}) {
2289
3460
  el.connect(store, (key) => t(key, locale));
2290
3461
  }
2291
3462
  const editingEnabled = features.editMode || isIframe;
2292
- const stega = createStegaController({
2293
- // The CMS deep-link tooltip is chrome, so it follows the same decision as
2294
- // the bar: noise inside the editor (clicking the element itself requests
2295
- // the edit there), and unwanted in a headless preview that asked for no UI.
2296
- tooltip: mountUi,
2297
- // In the editor, ask the parent to focus the field instead of opening a new
2298
- // tab. Standalone previews keep the window.open behaviour.
2299
- onEdit: ({ href, id, field, locale: contentLocale }) => {
2300
- const safeHref = href ? safeEditUrl(href) : null;
2301
- if (href && !safeHref) {
2302
- debug9.warn("ignored edit request with unsupported href scheme");
2303
- return;
2304
- }
2305
- if (isIframe) {
2306
- sendPreprEvent("field_edit_requested", {
2307
- id,
2308
- field,
2309
- locale: contentLocale
2310
- });
2311
- } else if (safeHref) {
2312
- window.open(safeHref, "_blank", "noopener,noreferrer");
2313
- }
3463
+ const onEdit = ({
3464
+ href,
3465
+ id,
3466
+ field,
3467
+ locale: contentLocale,
3468
+ block,
3469
+ entryId,
3470
+ referenceField
3471
+ }) => {
3472
+ const safeHref = href ? safeEditUrl(href) : null;
3473
+ if (href && !safeHref) {
3474
+ debug9.warn("ignored edit request with unsupported href scheme");
3475
+ return;
2314
3476
  }
3477
+ if (isIframe) {
3478
+ sendPreprEvent("field_edit_requested", {
3479
+ id,
3480
+ field,
3481
+ locale: contentLocale,
3482
+ ...block !== void 0 && { block },
3483
+ ...entryId && { entryId },
3484
+ ...referenceField && { referenceField }
3485
+ });
3486
+ } else if (safeHref) {
3487
+ window.open(safeHref, "_blank", "noopener,noreferrer");
3488
+ }
3489
+ };
3490
+ const visualEditor = isIframe ? createVisualEditor({
3491
+ onEdit,
3492
+ locale,
3493
+ onNavigationBlocked: (href) => sendPreprEvent("navigation_blocked", { href })
3494
+ }) : null;
3495
+ const stega = visualEditor ?? createStegaController({
3496
+ // The edit-mode marks and hover label are chrome, so they follow the
3497
+ // same decision as the bar: unwanted in a headless preview that asked
3498
+ // for no UI.
3499
+ tooltip: mountUi,
3500
+ onEdit,
3501
+ locale
2315
3502
  });
2316
3503
  const autoClean = createStegaAutoClean({
2317
3504
  enabled: options?.autoClean
@@ -2334,10 +3521,30 @@ function createPreprPreview(opts = {}) {
2334
3521
  });
2335
3522
  const bridge = createIframeBridge(store, {
2336
3523
  allowedEditorOrigins: options?.allowedEditorOrigins,
3524
+ onHandshake: ({
3525
+ itemId,
3526
+ referencedIds,
3527
+ componentIds,
3528
+ uiLocale,
3529
+ navigationGuard,
3530
+ assets
3531
+ }) => {
3532
+ if (uiLocale) visualEditor?.setLocale(uiLocale);
3533
+ visualEditor?.setCurrentItem(itemId ?? null, referencedIds, componentIds);
3534
+ visualEditor?.setNavigationGuard(navigationGuard === true);
3535
+ if (assets) visualEditor?.setAssets(assets);
3536
+ },
3537
+ onAssets: (assets) => visualEditor?.setAssets(assets),
3538
+ onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
3539
+ onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
2337
3540
  // The editor selecting a field scrolls the matching element into view. The
2338
3541
  // lookup relies on the attributes the stega scan writes, so it finds
2339
3542
  // nothing until encoded content has been tagged.
2340
- onScrollToField: ({ field, id }) => {
3543
+ onScrollToField: ({ field, id, block, entryId, fieldById }) => {
3544
+ if (visualEditor) {
3545
+ void visualEditor.focusField({ field, id, block, entryId, fieldById });
3546
+ return;
3547
+ }
2341
3548
  if (!scrollToField({ field, id })) {
2342
3549
  debug9.log("no element on this page renders field", field);
2343
3550
  }
@@ -2368,6 +3575,1717 @@ function createPreprPreview(opts = {}) {
2368
3575
  return controller;
2369
3576
  }
2370
3577
 
3578
+ // src/core/preview-attrs.ts
3579
+ function previewAttrs(target, options) {
3580
+ if (!options.enabled || !target?.id || !target.field) return {};
3581
+ const attrs = {
3582
+ "data-prepr-encoded": "",
3583
+ "data-prepr-id": target.id,
3584
+ "data-prepr-field": target.field
3585
+ };
3586
+ if (target.locale) attrs["data-prepr-locale"] = target.locale;
3587
+ if (target.entryId) attrs["data-prepr-entry-id"] = target.entryId;
3588
+ return attrs;
3589
+ }
3590
+ var URL_BASE = "https://prepr.invalid";
3591
+ function stegaTarget(value, options = {}) {
3592
+ const decoded = value ? decodeStega(value) : null;
3593
+ if (!decoded) return null;
3594
+ let url;
3595
+ try {
3596
+ url = new URL(decoded.href, URL_BASE);
3597
+ } catch {
3598
+ return null;
3599
+ }
3600
+ const id = decodeURIComponent(url.pathname.split("/").pop() ?? "");
3601
+ const field = url.searchParams.get("field");
3602
+ if (!id || !field) return null;
3603
+ return {
3604
+ id,
3605
+ field: options.field ? `${field.slice(0, field.lastIndexOf(".") + 1)}${options.field}` : field,
3606
+ locale: url.searchParams.get("locale")
3607
+ };
3608
+ }
3609
+
3610
+ // src/core/live-preview/normalize-query.ts
3611
+ var import_stega6 = require("@vercel/stega");
3612
+
3613
+ // src/core/live-preview/field-name.ts
3614
+ var UPPERCASE_BOUNDARY = /(.)(?=[A-Z])/g;
3615
+ function graphqlFieldName(apiId) {
3616
+ return apiId.replace(UPPERCASE_BOUNDARY, "$1_").toLowerCase();
3617
+ }
3618
+ function fieldKeyIn(record, apiId) {
3619
+ if (Object.prototype.hasOwnProperty.call(record, apiId)) return apiId;
3620
+ const name = graphqlFieldName(apiId);
3621
+ return Object.prototype.hasOwnProperty.call(record, name) ? name : void 0;
3622
+ }
3623
+ var TEXT_BODY_FIELDS = [
3624
+ { name: "body", plain: false },
3625
+ { name: "html", plain: false },
3626
+ { name: "text", plain: true }
3627
+ ];
3628
+ var TAG_PATTERN = /<[^>]+>/g;
3629
+ function plainTextFromHtml(html) {
3630
+ return html.replace(TAG_PATTERN, "");
3631
+ }
3632
+ function isTextEntry(record) {
3633
+ const typename = record.__typename;
3634
+ if (typeof typename === "string") return typename === "Text";
3635
+ return "html" in record || "text" in record && "format" in record;
3636
+ }
3637
+
3638
+ // src/core/live-preview/index-snapshot.ts
3639
+ var debug10 = createScopedLogger("live-preview");
3640
+ function indexSnapshot(data) {
3641
+ const index = /* @__PURE__ */ new Map();
3642
+ const visited = /* @__PURE__ */ new WeakSet();
3643
+ const path = [];
3644
+ function walk2(node) {
3645
+ if (node === null || typeof node !== "object") return;
3646
+ if (visited.has(node)) return;
3647
+ visited.add(node);
3648
+ if (Array.isArray(node)) {
3649
+ for (let i2 = 0; i2 < node.length; i2++) {
3650
+ path.push(i2);
3651
+ walk2(node[i2]);
3652
+ path.pop();
3653
+ }
3654
+ return;
3655
+ }
3656
+ const record = node;
3657
+ if (typeof record._id === "string") {
3658
+ const paths = index.get(record._id);
3659
+ if (paths) paths.push([...path]);
3660
+ else index.set(record._id, [[...path]]);
3661
+ } else if (typeof record.__typename === "string") {
3662
+ debug10.log("item without _id will not live-update", {
3663
+ __typename: record.__typename
3664
+ });
3665
+ }
3666
+ for (const key in record) {
3667
+ if (!Object.prototype.hasOwnProperty.call(record, key)) continue;
3668
+ path.push(key);
3669
+ walk2(record[key]);
3670
+ path.pop();
3671
+ }
3672
+ }
3673
+ walk2(data);
3674
+ return index;
3675
+ }
3676
+
3677
+ // src/core/live-preview/patch.ts
3678
+ var import_stega5 = require("@vercel/stega");
3679
+ var INDEX_SEGMENT2 = /^\d+$/;
3680
+ var FORBIDDEN_SEGMENTS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
3681
+ function isPlainObject(value) {
3682
+ return value !== null && typeof value === "object" && !Array.isArray(value);
3683
+ }
3684
+ function mergeObject(current, incoming) {
3685
+ let changed = false;
3686
+ const next = { ...current };
3687
+ for (const key of Object.keys(current)) {
3688
+ if (!(key in incoming) || incoming[key] === void 0) continue;
3689
+ const value = isPlainObject(current[key]) && isPlainObject(incoming[key]) ? mergeObject(current[key], incoming[key]) : incoming[key];
3690
+ if (value !== current[key]) {
3691
+ next[key] = value;
3692
+ changed = true;
3693
+ }
3694
+ }
3695
+ return changed ? next : current;
3696
+ }
3697
+ function shapeLike(template, incoming) {
3698
+ const out = {};
3699
+ for (const key of Object.keys(template)) {
3700
+ const value = incoming[key];
3701
+ out[key] = value === void 0 ? null : isPlainObject(template[key]) && isPlainObject(value) ? shapeLike(template[key], value) : value;
3702
+ }
3703
+ return out;
3704
+ }
3705
+ function mergeStructured(current, incoming) {
3706
+ if (incoming === null || Array.isArray(incoming) && !incoming.length) {
3707
+ if (Array.isArray(current)) return current.length ? [] : current;
3708
+ return null;
3709
+ }
3710
+ const list = Array.isArray(incoming) ? incoming : [incoming];
3711
+ if (current === null || current === void 0) return incoming;
3712
+ if (Array.isArray(current)) {
3713
+ const template = current.find(isPlainObject);
3714
+ return list.map((entry) => {
3715
+ const match = current.find(
3716
+ (c2) => isPlainObject(c2) && c2._id !== void 0 && c2._id === entry._id
3717
+ );
3718
+ if (isPlainObject(match)) return mergeObject(match, entry);
3719
+ return isPlainObject(template) ? shapeLike(template, entry) : entry;
3720
+ });
3721
+ }
3722
+ if (isPlainObject(current)) return mergeObject(current, list[0]);
3723
+ return current;
3724
+ }
3725
+ function isPatchable(current) {
3726
+ return current === null || typeof current === "string" || typeof current === "number" || typeof current === "boolean";
3727
+ }
3728
+ function cloneNode(node) {
3729
+ return Array.isArray(node) ? [...node] : { ...node };
3730
+ }
3731
+ function patchSnapshot(snapshot, index, itemId, field, value) {
3732
+ const paths = index.get(itemId);
3733
+ if (!paths?.length) return snapshot;
3734
+ let root = snapshot;
3735
+ let changed = false;
3736
+ const markChanged = () => {
3737
+ changed = true;
3738
+ };
3739
+ for (const path of paths) {
3740
+ for (const [target, targetValue] of expandTextBody(
3741
+ root,
3742
+ path,
3743
+ field,
3744
+ value
3745
+ )) {
3746
+ root = patchAtPath(root, path, target, targetValue, markChanged);
3747
+ }
3748
+ }
3749
+ return changed ? root : snapshot;
3750
+ }
3751
+ function expandTextBody(root, path, field, value) {
3752
+ const segments = field.split(".");
3753
+ if (segments[segments.length - 1] !== "body" || typeof value !== "string") {
3754
+ return [[field, value]];
3755
+ }
3756
+ let node = root;
3757
+ for (const step of path) {
3758
+ node = node[step];
3759
+ }
3760
+ for (const segment of segments.slice(0, -1)) {
3761
+ if (node === null || typeof node !== "object") return [[field, value]];
3762
+ const record2 = node;
3763
+ node = INDEX_SEGMENT2.test(segment) ? record2[Number(segment)] : record2[fieldKeyIn(record2, segment) ?? segment];
3764
+ }
3765
+ if (node === null || typeof node !== "object" || Array.isArray(node)) {
3766
+ return [[field, value]];
3767
+ }
3768
+ const record = node;
3769
+ if (!isTextEntry(record)) return [[field, value]];
3770
+ const prefix = segments.slice(0, -1);
3771
+ const writes = [];
3772
+ for (const { name, plain } of TEXT_BODY_FIELDS) {
3773
+ if (!(name in record)) continue;
3774
+ writes.push([
3775
+ [...prefix, name].join("."),
3776
+ plain ? plainTextFromHtml(value) : value
3777
+ ]);
3778
+ }
3779
+ return writes.length ? writes : [[field, value]];
3780
+ }
3781
+ function patchAtPath(root, path, field, value, markChanged) {
3782
+ const segments = field.split(".");
3783
+ if (segments.some((s2) => FORBIDDEN_SEGMENTS.has(s2))) return root;
3784
+ let node = root;
3785
+ for (const step of path) {
3786
+ node = node[step];
3787
+ }
3788
+ const fullPath = [...path];
3789
+ for (const segment of segments) {
3790
+ let step = segment;
3791
+ if (INDEX_SEGMENT2.test(segment)) {
3792
+ step = Number(segment);
3793
+ } else if (node !== null && typeof node === "object") {
3794
+ step = fieldKeyIn(node, segment) ?? segment;
3795
+ }
3796
+ fullPath.push(step);
3797
+ node = node !== null && typeof node === "object" ? node[step] : void 0;
3798
+ }
3799
+ return writeAtPath(root, fullPath, value, markChanged);
3800
+ }
3801
+ function writeAtPath(root, fullPath, value, markChanged = () => {
3802
+ }) {
3803
+ if (!fullPath.length) return root;
3804
+ if (fullPath.some((s2) => FORBIDDEN_SEGMENTS.has(String(s2)))) return root;
3805
+ const last = fullPath.length - 1;
3806
+ let node = root;
3807
+ for (let i2 = 0; i2 < last; i2++) {
3808
+ if (node === null || typeof node !== "object") return root;
3809
+ node = node[fullPath[i2]];
3810
+ }
3811
+ if (node === null || typeof node !== "object") return root;
3812
+ const container = node;
3813
+ const key = fullPath[last];
3814
+ if (!(key in container)) return root;
3815
+ let next;
3816
+ if (value === null || typeof value === "object") {
3817
+ next = mergeStructured(container[key], value);
3818
+ } else if (!isPatchable(container[key])) {
3819
+ return root;
3820
+ } else {
3821
+ next = keepEditLink(container[key], value);
3822
+ }
3823
+ if (container[key] === next) return root;
3824
+ const newRoot = cloneNode(root);
3825
+ let cursor = newRoot;
3826
+ for (let i2 = 0; i2 < last; i2++) {
3827
+ const child = cloneNode(cursor[fullPath[i2]]);
3828
+ cursor[fullPath[i2]] = child;
3829
+ cursor = child;
3830
+ }
3831
+ cursor[key] = next;
3832
+ markChanged();
3833
+ return newRoot;
3834
+ }
3835
+ function listKeysOf(entry) {
3836
+ if (!isPlainObject(entry)) return [];
3837
+ const keys = [];
3838
+ if (typeof entry._id === "string") keys.push(entry._id);
3839
+ const first = Array.isArray(entry.items) ? entry.items[0] : null;
3840
+ if (isPlainObject(first) && typeof first._id === "string") {
3841
+ keys.push(first._id);
3842
+ }
3843
+ return keys;
3844
+ }
3845
+ function buildText(entry, template) {
3846
+ const body = typeof entry.body === "string" ? entry.body : "";
3847
+ const full = {
3848
+ __typename: "Text",
3849
+ _id: entry._id,
3850
+ body,
3851
+ html: body,
3852
+ text: plainTextFromHtml(body),
3853
+ format: entry.format ?? null
3854
+ };
3855
+ if (!template) return full;
3856
+ const out = {};
3857
+ for (const key of Object.keys(template)) {
3858
+ out[key] = key in full ? full[key] : null;
3859
+ }
3860
+ return out;
3861
+ }
3862
+ function syncList(current, entries, build) {
3863
+ if (current === null || current === void 0) {
3864
+ const built = syncList([], entries, build);
3865
+ return built.length ? built : current;
3866
+ }
3867
+ if (!Array.isArray(current)) return current;
3868
+ const byKey = /* @__PURE__ */ new Map();
3869
+ const templates = /* @__PURE__ */ new Map();
3870
+ for (const c2 of current) {
3871
+ for (const key of listKeysOf(c2)) byKey.set(key, c2);
3872
+ if (isPlainObject(c2) && typeof c2.__typename === "string") {
3873
+ if (!templates.has(c2.__typename)) templates.set(c2.__typename, c2);
3874
+ }
3875
+ }
3876
+ if (!entries.length) return current.length ? [] : current;
3877
+ if (current.length && !byKey.size) return current;
3878
+ if (current.length && !entries.some((e2) => byKey.has(e2._id))) return current;
3879
+ const next = [];
3880
+ for (const entry of entries) {
3881
+ const existing = byKey.get(entry._id);
3882
+ const built = existing === void 0 && entry.fields ? build?.(entry) : void 0;
3883
+ if (existing !== void 0) {
3884
+ next.push(existing);
3885
+ } else if (built) {
3886
+ next.push(built);
3887
+ } else if (entry.__typename === "Text") {
3888
+ next.push(buildText(entry, templates.get("Text")));
3889
+ }
3890
+ }
3891
+ const same = next.length === current.length && next.every((n2, i2) => n2 === current[i2]);
3892
+ return same ? current : next;
3893
+ }
3894
+ function syncListSnapshot(snapshot, index, itemId, field, entries) {
3895
+ const paths = index.get(itemId);
3896
+ if (!paths?.length) return snapshot;
3897
+ const fieldPath = field.split(".");
3898
+ if (fieldPath.some((s2) => FORBIDDEN_SEGMENTS.has(s2))) return snapshot;
3899
+ let root = snapshot;
3900
+ for (const path of paths) {
3901
+ root = syncListAtPath(root, [...path, ...fieldPath], entries);
3902
+ }
3903
+ return root;
3904
+ }
3905
+ function syncListAtPath(root, fullPath, entries, build) {
3906
+ if (!fullPath.length) return root;
3907
+ if (fullPath.some((s2) => FORBIDDEN_SEGMENTS.has(String(s2)))) return root;
3908
+ let node = root;
3909
+ for (const key of fullPath) {
3910
+ node = node !== null && typeof node === "object" ? node[key] : void 0;
3911
+ }
3912
+ const next = syncList(node, entries, build);
3913
+ if (next === node) return root;
3914
+ const newRoot = cloneNode(root);
3915
+ let cursor = newRoot;
3916
+ for (let i2 = 0; i2 < fullPath.length - 1; i2++) {
3917
+ const child = cloneNode(cursor[fullPath[i2]]);
3918
+ cursor[fullPath[i2]] = child;
3919
+ cursor = child;
3920
+ }
3921
+ cursor[fullPath[fullPath.length - 1]] = next;
3922
+ return newRoot;
3923
+ }
3924
+ function keepEditLink(current, value) {
3925
+ if (typeof current !== "string" || typeof value !== "string") return value;
3926
+ const { encoded } = (0, import_stega5.vercelStegaSplit)(current);
3927
+ if (!encoded || (0, import_stega5.vercelStegaSplit)(value).encoded) return value;
3928
+ return value + encoded;
3929
+ }
3930
+
3931
+ // src/core/live-preview/query-document.ts
3932
+ var NAME_START = /[_A-Za-z]/;
3933
+ var NAME_CONTINUE = /[_0-9A-Za-z]/;
3934
+ var PUNCT = /* @__PURE__ */ new Set([
3935
+ "!",
3936
+ "$",
3937
+ "&",
3938
+ "(",
3939
+ ")",
3940
+ ":",
3941
+ "=",
3942
+ "@",
3943
+ "[",
3944
+ "]",
3945
+ "{",
3946
+ "|",
3947
+ "}"
3948
+ ]);
3949
+ function tokenize(source) {
3950
+ const tokens = [];
3951
+ let i2 = 0;
3952
+ const n2 = source.length;
3953
+ while (i2 < n2) {
3954
+ const ch = source[i2];
3955
+ if (ch === " " || ch === " " || ch === "\n" || ch === "\r" || ch === "," || ch === "\uFEFF") {
3956
+ i2++;
3957
+ } else if (ch === "#") {
3958
+ while (i2 < n2 && source[i2] !== "\n" && source[i2] !== "\r") i2++;
3959
+ } else if (ch === "." && source.startsWith("...", i2)) {
3960
+ tokens.push({ type: "punct", value: "..." });
3961
+ i2 += 3;
3962
+ } else if (PUNCT.has(ch)) {
3963
+ tokens.push({ type: "punct", value: ch });
3964
+ i2++;
3965
+ } else if (NAME_START.test(ch)) {
3966
+ const start = i2;
3967
+ while (i2 < n2 && NAME_CONTINUE.test(source[i2])) i2++;
3968
+ tokens.push({ type: "name", value: source.slice(start, i2) });
3969
+ } else if (ch === '"') {
3970
+ if (source.startsWith('"""', i2)) {
3971
+ i2 += 3;
3972
+ while (i2 < n2 && !source.startsWith('"""', i2)) {
3973
+ i2 += source.startsWith('\\"""', i2) ? 4 : 1;
3974
+ }
3975
+ if (i2 >= n2) throw new SyntaxError("Unterminated block string");
3976
+ i2 += 3;
3977
+ } else {
3978
+ i2++;
3979
+ while (i2 < n2 && source[i2] !== '"') {
3980
+ if (source[i2] === "\n" || source[i2] === "\r") {
3981
+ throw new SyntaxError("Unterminated string");
3982
+ }
3983
+ i2 += source[i2] === "\\" ? 2 : 1;
3984
+ }
3985
+ if (i2 >= n2) throw new SyntaxError("Unterminated string");
3986
+ i2++;
3987
+ }
3988
+ tokens.push({ type: "value" });
3989
+ } else if (ch === "-" || ch >= "0" && ch <= "9") {
3990
+ i2++;
3991
+ while (i2 < n2 && /[0-9.eE+-]/.test(source[i2])) i2++;
3992
+ tokens.push({ type: "value" });
3993
+ } else {
3994
+ throw new SyntaxError(`Unexpected character "${ch}"`);
3995
+ }
3996
+ }
3997
+ return tokens;
3998
+ }
3999
+ function parseSource(source, operationName) {
4000
+ const tokens = tokenize(source);
4001
+ let pos = 0;
4002
+ const peek = () => tokens[pos];
4003
+ const isPunct = (value) => {
4004
+ const t3 = tokens[pos];
4005
+ return t3?.type === "punct" && t3.value === value;
4006
+ };
4007
+ const expectPunct = (value) => {
4008
+ if (!isPunct(value)) throw new SyntaxError(`Expected "${value}"`);
4009
+ pos++;
4010
+ };
4011
+ const expectName = () => {
4012
+ const t3 = tokens[pos];
4013
+ if (t3?.type !== "name") throw new SyntaxError("Expected a name");
4014
+ pos++;
4015
+ return t3.value;
4016
+ };
4017
+ const skipParens = () => {
4018
+ expectPunct("(");
4019
+ let depth = 1;
4020
+ while (depth > 0) {
4021
+ const t3 = tokens[pos++];
4022
+ if (!t3) throw new SyntaxError("Unbalanced parentheses");
4023
+ if (t3.type === "punct" && t3.value === "(") depth++;
4024
+ else if (t3.type === "punct" && t3.value === ")") depth--;
4025
+ }
4026
+ };
4027
+ const skipDirectives = () => {
4028
+ while (isPunct("@")) {
4029
+ pos++;
4030
+ expectName();
4031
+ if (isPunct("(")) skipParens();
4032
+ }
4033
+ };
4034
+ const parseSelectionSet = () => {
4035
+ expectPunct("{");
4036
+ const selections = [];
4037
+ while (!isPunct("}")) {
4038
+ if (!peek()) throw new SyntaxError("Unterminated selection set");
4039
+ selections.push(parseSelection());
4040
+ }
4041
+ pos++;
4042
+ return selections;
4043
+ };
4044
+ const parseSelection = () => {
4045
+ if (isPunct("...")) {
4046
+ pos++;
4047
+ const t3 = peek();
4048
+ if (t3?.type === "name" && t3.value !== "on") {
4049
+ pos++;
4050
+ skipDirectives();
4051
+ return { kind: "spread", name: t3.value };
4052
+ }
4053
+ let on = null;
4054
+ if (t3?.type === "name") {
4055
+ pos++;
4056
+ on = expectName();
4057
+ }
4058
+ skipDirectives();
4059
+ return { kind: "inline", on, selections: parseSelectionSet() };
4060
+ }
4061
+ let name = expectName();
4062
+ const key = name;
4063
+ if (isPunct(":")) {
4064
+ pos++;
4065
+ name = expectName();
4066
+ }
4067
+ let hasArgs = false;
4068
+ if (isPunct("(")) {
4069
+ hasArgs = true;
4070
+ skipParens();
4071
+ }
4072
+ skipDirectives();
4073
+ const selections = isPunct("{") ? parseSelectionSet() : null;
4074
+ return { kind: "field", name, key, hasArgs, selections };
4075
+ };
4076
+ const fragments = /* @__PURE__ */ new Map();
4077
+ const operations = [];
4078
+ while (pos < tokens.length) {
4079
+ if (isPunct("{")) {
4080
+ operations.push({ name: null, selections: parseSelectionSet() });
4081
+ continue;
4082
+ }
4083
+ const keyword = expectName();
4084
+ if (keyword === "fragment") {
4085
+ const name = expectName();
4086
+ if (expectName() !== "on") throw new SyntaxError('Expected "on"');
4087
+ const on = expectName();
4088
+ skipDirectives();
4089
+ fragments.set(name, { on, selections: parseSelectionSet() });
4090
+ } else if (keyword === "query" || keyword === "mutation" || keyword === "subscription") {
4091
+ const t3 = peek();
4092
+ let name = null;
4093
+ if (t3?.type === "name") name = expectName();
4094
+ if (isPunct("(")) skipParens();
4095
+ skipDirectives();
4096
+ operations.push({ name, selections: parseSelectionSet() });
4097
+ } else {
4098
+ throw new SyntaxError(`Unexpected "${keyword}"`);
4099
+ }
4100
+ }
4101
+ return { operation: pickOperation(operations, operationName), fragments };
4102
+ }
4103
+ function pickOperation(operations, operationName) {
4104
+ const op = operationName ? operations.find((o2) => o2.name === operationName) : operations[0];
4105
+ if (!op) throw new SyntaxError("The document has no operation");
4106
+ return op.selections;
4107
+ }
4108
+ function fromAstSelections(set) {
4109
+ return set.selections.map((s2) => {
4110
+ if (s2.kind === "FragmentSpread")
4111
+ return { kind: "spread", name: s2.name.value };
4112
+ if (s2.kind === "InlineFragment") {
4113
+ return {
4114
+ kind: "inline",
4115
+ on: s2.typeCondition?.name.value ?? null,
4116
+ selections: fromAstSelections(s2.selectionSet)
4117
+ };
4118
+ }
4119
+ const name = s2.name.value;
4120
+ return {
4121
+ kind: "field",
4122
+ name,
4123
+ key: s2.alias?.value ?? name,
4124
+ hasArgs: Boolean(s2.arguments?.length),
4125
+ selections: s2.selectionSet ? fromAstSelections(s2.selectionSet) : null
4126
+ };
4127
+ });
4128
+ }
4129
+ function fromAst(doc, operationName) {
4130
+ const fragments = /* @__PURE__ */ new Map();
4131
+ const operations = [];
4132
+ for (const def of doc.definitions ?? []) {
4133
+ if (def.kind === "FragmentDefinition" && def.name && def.typeCondition && def.selectionSet) {
4134
+ fragments.set(def.name.value, {
4135
+ on: def.typeCondition.name.value,
4136
+ selections: fromAstSelections(def.selectionSet)
4137
+ });
4138
+ } else if (def.kind === "OperationDefinition" && def.selectionSet) {
4139
+ operations.push({
4140
+ name: def.name?.value ?? null,
4141
+ selections: fromAstSelections(def.selectionSet)
4142
+ });
4143
+ }
4144
+ }
4145
+ return { operation: pickOperation(operations, operationName), fragments };
4146
+ }
4147
+ var astCache = /* @__PURE__ */ new WeakMap();
4148
+ var sourceCache = /* @__PURE__ */ new Map();
4149
+ var SOURCE_CACHE_LIMIT = 200;
4150
+ function readQueryDocument(query, operationName) {
4151
+ const cacheKey = operationName ?? "";
4152
+ if (typeof query === "object" && query !== null && query.kind === "Document") {
4153
+ let byName = astCache.get(query);
4154
+ if (!byName) astCache.set(query, byName = /* @__PURE__ */ new Map());
4155
+ let parsed2 = byName.get(cacheKey);
4156
+ if (!parsed2) {
4157
+ parsed2 = fromAst(
4158
+ query,
4159
+ operationName
4160
+ );
4161
+ byName.set(cacheKey, parsed2);
4162
+ }
4163
+ return parsed2;
4164
+ }
4165
+ const source = String(query);
4166
+ const sourceKey = `${cacheKey}
4167
+ ${source}`;
4168
+ let parsed = sourceCache.get(sourceKey);
4169
+ if (!parsed) {
4170
+ parsed = parseSource(source, operationName);
4171
+ if (sourceCache.size >= SOURCE_CACHE_LIMIT) {
4172
+ sourceCache.delete(sourceCache.keys().next().value);
4173
+ }
4174
+ sourceCache.set(sourceKey, parsed);
4175
+ }
4176
+ return parsed;
4177
+ }
4178
+ var fieldsCache = /* @__PURE__ */ new WeakMap();
4179
+ function collectFields(selections, typename, doc, options = {}) {
4180
+ const cacheKey = `${options.strict ? "s" : "l"}:${typename ?? ""}`;
4181
+ let cached = fieldsCache.get(selections);
4182
+ if (!cached) {
4183
+ cached = /* @__PURE__ */ new Map();
4184
+ fieldsCache.set(selections, cached);
4185
+ }
4186
+ const hit = cached.get(cacheKey);
4187
+ if (hit) return hit;
4188
+ const fields = resolveFields(selections, typename, doc, options);
4189
+ cached.set(cacheKey, fields);
4190
+ return fields;
4191
+ }
4192
+ function resolveFields(selections, typename, doc, options) {
4193
+ const exact = /* @__PURE__ */ new Map();
4194
+ const loose = /* @__PURE__ */ new Map();
4195
+ const merged = /* @__PURE__ */ new Map();
4196
+ const walkedMatched = /* @__PURE__ */ new Set();
4197
+ const walkedLoose = /* @__PURE__ */ new Set();
4198
+ const add = (target, s2) => {
4199
+ let field = target.get(s2.key);
4200
+ if (!field) {
4201
+ field = { name: s2.name, key: s2.key, hasArgs: s2.hasArgs, selections: [] };
4202
+ target.set(s2.key, field);
4203
+ merged.set(field, /* @__PURE__ */ new Set());
4204
+ }
4205
+ const done = merged.get(field);
4206
+ if (s2.selections && !done.has(s2.selections)) {
4207
+ done.add(s2.selections);
4208
+ field.selections = [...field.selections, ...s2.selections];
4209
+ }
4210
+ };
4211
+ const applies = (on, list) => conditionMatches(on, typename) || typename !== void 0 && typeConditions(list, doc).includes(typename);
4212
+ const walk2 = (list, matched) => {
4213
+ for (const s2 of list) {
4214
+ if (s2.kind === "field") {
4215
+ add(matched ? exact : loose, s2);
4216
+ } else if (s2.kind === "inline") {
4217
+ walk2(s2.selections, matched && applies(s2.on, s2.selections));
4218
+ } else {
4219
+ const fragment = doc.fragments.get(s2.name);
4220
+ if (!fragment) continue;
4221
+ const asMatched = matched && applies(fragment.on, fragment.selections);
4222
+ if (walkedMatched.has(fragment)) continue;
4223
+ if (!asMatched && walkedLoose.has(fragment)) continue;
4224
+ (asMatched ? walkedMatched : walkedLoose).add(fragment);
4225
+ walk2(fragment.selections, asMatched);
4226
+ }
4227
+ }
4228
+ };
4229
+ walk2(selections, true);
4230
+ if (!options.strict) {
4231
+ for (const [key, field] of loose) {
4232
+ if (!exact.has(key)) exact.set(key, field);
4233
+ }
4234
+ }
4235
+ return exact;
4236
+ }
4237
+ function conditionMatches(on, typename) {
4238
+ return on === null || typename === void 0 || on === typename;
4239
+ }
4240
+ var conditionsCache = /* @__PURE__ */ new WeakMap();
4241
+ function typeConditions(selections, doc) {
4242
+ const hit = conditionsCache.get(selections);
4243
+ if (hit) return hit;
4244
+ const names = /* @__PURE__ */ new Set();
4245
+ const seen = /* @__PURE__ */ new Set();
4246
+ const walk2 = (list) => {
4247
+ for (const s2 of list) {
4248
+ if (s2.kind === "inline") {
4249
+ if (s2.on) names.add(s2.on);
4250
+ walk2(s2.selections);
4251
+ } else if (s2.kind === "spread") {
4252
+ const fragment = doc.fragments.get(s2.name);
4253
+ if (!fragment || seen.has(fragment)) continue;
4254
+ seen.add(fragment);
4255
+ names.add(fragment.on);
4256
+ walk2(fragment.selections);
4257
+ }
4258
+ }
4259
+ };
4260
+ walk2(selections);
4261
+ const conditions = [...names];
4262
+ conditionsCache.set(selections, conditions);
4263
+ return conditions;
4264
+ }
4265
+
4266
+ // src/core/live-preview/validate.ts
4267
+ function isScalar(v2) {
4268
+ return typeof v2 === "string" || typeof v2 === "number" || typeof v2 === "boolean";
4269
+ }
4270
+ var MAX_DEPTH = 3;
4271
+ var MAX_ENTRIES = 50;
4272
+ var FORBIDDEN_KEYS = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
4273
+ var SAFE_URL = /^https?:\/\//i;
4274
+ var URL_KEY = /^(?:url|href|src|link)$|(?:_url|Url)$/;
4275
+ var URL_IGNORED_ANYWHERE = /[\t\n\r]/g;
4276
+ var SCRIPT_SCHEME = /^(?:javascript|vbscript|data):/i;
4277
+ function isSafeLinkValue(value) {
4278
+ const stripped = value.replace(URL_IGNORED_ANYWHERE, "");
4279
+ let start = 0;
4280
+ while (start < stripped.length && stripped.charCodeAt(start) <= 32) start++;
4281
+ return !SCRIPT_SCHEME.test(stripped.slice(start));
4282
+ }
4283
+ function isUrlKey(key) {
4284
+ return URL_KEY.test(key);
4285
+ }
4286
+ function isSafeKeyValue(key, value) {
4287
+ if (typeof value !== "string") return true;
4288
+ if (key === "url") return SAFE_URL.test(value);
4289
+ return !isUrlKey(key) || isSafeLinkValue(value);
4290
+ }
4291
+ function isLiveValue(v2, depth = 0) {
4292
+ if (v2 === null || isScalar(v2)) return true;
4293
+ if (depth >= MAX_DEPTH) return false;
4294
+ if (Array.isArray(v2)) {
4295
+ return depth === 0 && v2.length <= MAX_ENTRIES && v2.every((entry) => isPlainRecord(entry) && isLiveValue(entry, depth + 1));
4296
+ }
4297
+ if (!isPlainRecord(v2)) return false;
4298
+ const keys = Object.keys(v2);
4299
+ if (keys.length > MAX_ENTRIES) return false;
4300
+ return keys.every((key) => {
4301
+ if (FORBIDDEN_KEYS.has(key)) return false;
4302
+ const value = v2[key];
4303
+ if (!isSafeKeyValue(key, value)) return false;
4304
+ return !Array.isArray(value) && isLiveValue(value, depth + 1);
4305
+ });
4306
+ }
4307
+ function isLiveFieldChange(field, value) {
4308
+ if (!isLiveValue(value)) return false;
4309
+ const name = field.slice(field.lastIndexOf(".") + 1);
4310
+ return typeof value !== "string" || !isUrlKey(name) || isSafeLinkValue(value);
4311
+ }
4312
+ var MAX_LIST_ENTRIES = 200;
4313
+ function parseListEntries(value) {
4314
+ if (!Array.isArray(value) || value.length > MAX_LIST_ENTRIES) return null;
4315
+ const list = [];
4316
+ for (const raw of value) {
4317
+ if (!isPlainRecord(raw) || typeof raw._id !== "string" || !raw._id) {
4318
+ return null;
4319
+ }
4320
+ const entry = { _id: raw._id };
4321
+ if (typeof raw.__typename === "string") entry.__typename = raw.__typename;
4322
+ if (typeof raw.body === "string") entry.body = raw.body;
4323
+ if (typeof raw.format === "string" || raw.format === null) {
4324
+ entry.format = raw.format;
4325
+ }
4326
+ if (typeof raw.typeName === "string" && raw.typeName.length <= 200) {
4327
+ entry.typeName = raw.typeName;
4328
+ }
4329
+ if (raw.fields !== void 0) {
4330
+ if (!isPlainRecord(raw.fields) || !isFieldValue(raw.fields, 0)) {
4331
+ return null;
4332
+ }
4333
+ entry.fields = raw.fields;
4334
+ }
4335
+ list.push(entry);
4336
+ }
4337
+ return list;
4338
+ }
4339
+ function isPlainRecord(v2) {
4340
+ return v2 !== null && typeof v2 === "object" && Object.getPrototypeOf(v2) === Object.prototype;
4341
+ }
4342
+ var MAX_FIELD_DEPTH = 6;
4343
+ var MAX_FIELD_KEYS = 100;
4344
+ function isFieldValue(v2, depth) {
4345
+ if (v2 === null || isScalar(v2)) return true;
4346
+ if (depth >= MAX_FIELD_DEPTH) return false;
4347
+ if (Array.isArray(v2)) {
4348
+ return v2.length <= MAX_ENTRIES && v2.every(
4349
+ (entry) => isScalar(entry) || isPlainRecord(entry) && isFieldValue(entry, depth + 1)
4350
+ );
4351
+ }
4352
+ if (!isPlainRecord(v2)) return false;
4353
+ const keys = Object.keys(v2);
4354
+ if (keys.length > MAX_FIELD_KEYS) return false;
4355
+ return keys.every((key) => {
4356
+ if (FORBIDDEN_KEYS.has(key)) return false;
4357
+ const value = v2[key];
4358
+ if (!isSafeKeyValue(key, value)) return false;
4359
+ return isFieldValue(value, depth + 1);
4360
+ });
4361
+ }
4362
+ var MAX_EDIT_LENGTH = 1e3;
4363
+ function parseEditTarget(raw) {
4364
+ if (!isPlainRecord(raw)) return void 0;
4365
+ const { item, field } = raw;
4366
+ if (typeof item !== "string" || !item || item.length > MAX_EDIT_LENGTH) {
4367
+ return void 0;
4368
+ }
4369
+ if (typeof field !== "string" || !field || field.length > MAX_EDIT_LENGTH) {
4370
+ return void 0;
4371
+ }
4372
+ return { item, field };
4373
+ }
4374
+
4375
+ // src/core/live-preview/normalize-query.ts
4376
+ var debug11 = createScopedLogger("live-preview:normalize");
4377
+ var MAX_LOG_ENTRIES = 500;
4378
+ var MAX_LOG_SOURCE_LENGTH = 1e6;
4379
+ var INDEX_SEGMENT3 = /^\d+$/;
4380
+ var FORBIDDEN_SEGMENTS2 = /* @__PURE__ */ new Set(["__proto__", "prototype", "constructor"]);
4381
+ var ENVELOPE_KEYS = /* @__PURE__ */ new Set(["data", "errors", "extensions"]);
4382
+ function parsePatchLog(raw) {
4383
+ let value = raw;
4384
+ if (typeof value === "string") {
4385
+ if (!value || value.length > MAX_LOG_SOURCE_LENGTH) return [];
4386
+ try {
4387
+ value = JSON.parse(value);
4388
+ } catch {
4389
+ return [];
4390
+ }
4391
+ }
4392
+ if (!Array.isArray(value)) return [];
4393
+ const log = [];
4394
+ for (const entry of value.slice(-MAX_LOG_ENTRIES)) {
4395
+ if (!isPlainRecord(entry)) continue;
4396
+ const { type, itemId, field } = entry;
4397
+ if (typeof itemId !== "string" || !itemId) continue;
4398
+ if (typeof field !== "string" || !field) continue;
4399
+ if (type === "field" && isLiveFieldChange(field, entry.value)) {
4400
+ const edit = parseEditTarget(entry.edit);
4401
+ log.push(
4402
+ edit ? { type, itemId, field, value: entry.value, edit } : { type, itemId, field, value: entry.value }
4403
+ );
4404
+ } else if (type === "list") {
4405
+ const entries = parseListEntries(entry.entries);
4406
+ if (entries) log.push({ type, itemId, field, entries });
4407
+ }
4408
+ }
4409
+ return log;
4410
+ }
4411
+ function normalizeQuery(query, result, options = {}) {
4412
+ return applyPatchLog(
4413
+ query,
4414
+ result,
4415
+ parsePatchLog(options.log),
4416
+ options.operationName
4417
+ );
4418
+ }
4419
+ function applyPatchLog(query, result, log, operationName) {
4420
+ if (!log.length || result === null || typeof result !== "object") {
4421
+ return result;
4422
+ }
4423
+ if (isEnvelope(result)) {
4424
+ const data = applyPatchLog(query, result.data, log, operationName);
4425
+ return data === result.data ? result : { ...result, data };
4426
+ }
4427
+ let doc = null;
4428
+ try {
4429
+ doc = readQueryDocument(query, operationName);
4430
+ } catch (error) {
4431
+ debug11.log("could not read the query; applying fields by literal path", {
4432
+ error: String(error)
4433
+ });
4434
+ }
4435
+ let root = result;
4436
+ let index = null;
4437
+ for (const patch of log) {
4438
+ try {
4439
+ index ?? (index = indexSnapshot(root));
4440
+ const next = applyPatch(root, index, patch, doc, result);
4441
+ if (next !== root) {
4442
+ root = next;
4443
+ if (patch.type === "list" || !isScalarValue(patch.value)) {
4444
+ index = null;
4445
+ }
4446
+ }
4447
+ } catch (error) {
4448
+ debug11.log("could not apply a change", {
4449
+ itemId: patch.itemId,
4450
+ field: patch.field,
4451
+ error: String(error)
4452
+ });
4453
+ }
4454
+ }
4455
+ return root;
4456
+ }
4457
+ function isScalarValue(value) {
4458
+ return value === null || typeof value !== "object";
4459
+ }
4460
+ function applyPatch(root, index, patch, doc, result) {
4461
+ const ownerPaths = index.get(patch.itemId);
4462
+ if (!ownerPaths?.length) return root;
4463
+ const segments = patch.field.split(".");
4464
+ if (segments.some((s2) => !s2 || FORBIDDEN_SEGMENTS2.has(s2))) return root;
4465
+ const targets = [];
4466
+ const plain = /* @__PURE__ */ new Set();
4467
+ for (const ownerPath of ownerPaths) {
4468
+ if (doc) {
4469
+ const resolved = resolveTargets(root, doc, ownerPath, segments);
4470
+ root = resolved.root;
4471
+ targets.push(...resolved.targets);
4472
+ resolved.plain.forEach((target) => plain.add(target));
4473
+ } else {
4474
+ targets.push([...ownerPath, ...segments.map(toPathSegment)]);
4475
+ }
4476
+ }
4477
+ if (!targets.length) {
4478
+ debug11.log("no field in the query matches", {
4479
+ itemId: patch.itemId,
4480
+ field: patch.field
4481
+ });
4482
+ return root;
4483
+ }
4484
+ if (patch.type === "field") {
4485
+ for (const target of targets) {
4486
+ const pushed = plain.has(target) && typeof patch.value === "string" ? plainTextFromHtml(patch.value) : patch.value;
4487
+ let value = keepEditLink(readAtPath(root, target), pushed);
4488
+ if (patch.edit && typeof value === "string" && !(0, import_stega6.vercelStegaSplit)(value).encoded) {
4489
+ value = editLinksFor(result, patch.edit.item)?.encode(
4490
+ value,
4491
+ patch.edit.field
4492
+ ) ?? value;
4493
+ }
4494
+ root = writeAtPath(root, target, value);
4495
+ }
4496
+ } else {
4497
+ for (const target of targets) {
4498
+ const selections = doc ? selectionsAt(root, doc, target) : null;
4499
+ const build = doc && selections ? (entry) => buildEntry(doc, selections, entry, {
4500
+ links: editLinks(result, entry),
4501
+ lists: listKeysIn(result)
4502
+ }) : void 0;
4503
+ root = syncListAtPath(root, target, patch.entries, build);
4504
+ }
4505
+ }
4506
+ return root;
4507
+ }
4508
+ function readAtPath(root, path) {
4509
+ let node = root;
4510
+ for (const step of path) {
4511
+ if (node === null || typeof node !== "object") return void 0;
4512
+ node = node[step];
4513
+ }
4514
+ return node;
4515
+ }
4516
+ function isEnvelope(value) {
4517
+ if (Array.isArray(value) || !("data" in value)) return false;
4518
+ return Object.keys(value).every((key) => ENVELOPE_KEYS.has(key));
4519
+ }
4520
+ function toPathSegment(segment) {
4521
+ return INDEX_SEGMENT3.test(segment) ? Number(segment) : segment;
4522
+ }
4523
+ function typenameOf(node) {
4524
+ const typename = node.__typename;
4525
+ return typeof typename === "string" ? typename : void 0;
4526
+ }
4527
+ function selectionsAt(root, doc, path) {
4528
+ let node = root;
4529
+ let selections = doc.operation;
4530
+ for (const step of path) {
4531
+ if (Array.isArray(node)) {
4532
+ node = node[step];
4533
+ continue;
4534
+ }
4535
+ if (node === null || typeof node !== "object") return null;
4536
+ const field = collectFields(selections, typenameOf(node), doc).get(
4537
+ String(step)
4538
+ );
4539
+ if (!field) return null;
4540
+ selections = field.selections;
4541
+ node = node[String(step)];
4542
+ }
4543
+ return selections;
4544
+ }
4545
+ function resolveTargets(root, doc, ownerPath, segments) {
4546
+ let owner = root;
4547
+ for (const step of ownerPath) {
4548
+ owner = owner[step];
4549
+ }
4550
+ const selections = selectionsAt(root, doc, ownerPath);
4551
+ if (!selections) return { root, targets: [], plain: /* @__PURE__ */ new Set() };
4552
+ let current = root;
4553
+ const out = [];
4554
+ const plain = /* @__PURE__ */ new Set();
4555
+ const walk2 = (node, sel, i2, path) => {
4556
+ if (i2 === segments.length) {
4557
+ out.push(path);
4558
+ return;
4559
+ }
4560
+ const segment = segments[i2];
4561
+ if (Array.isArray(node)) {
4562
+ if (INDEX_SEGMENT3.test(segment)) {
4563
+ const position = Number(segment);
4564
+ if (position < node.length) {
4565
+ walk2(node[position], sel, i2 + 1, [...path, position]);
4566
+ }
4567
+ } else if (segment === "items") {
4568
+ walk2(node, sel, i2 + 1, path);
4569
+ }
4570
+ return;
4571
+ }
4572
+ if (node === null || typeof node !== "object") return;
4573
+ const record = node;
4574
+ const wanted = graphqlFieldName(segment);
4575
+ const textBody = i2 === segments.length - 1 && segment === "body" && isTextEntry(record) ? TEXT_BODY_FIELDS : null;
4576
+ let matched = false;
4577
+ for (const field of collectFields(sel, typenameOf(node), doc).values()) {
4578
+ const alias = textBody?.find((f3) => f3.name === field.name);
4579
+ if (field.name !== segment && field.name !== wanted && !alias) continue;
4580
+ if (!Object.prototype.hasOwnProperty.call(record, field.key)) continue;
4581
+ matched = true;
4582
+ const fieldPath = [...path, field.key];
4583
+ if (alias?.plain) plain.add(fieldPath);
4584
+ let value = record[field.key];
4585
+ const next = segments[i2 + 1];
4586
+ if (value === null && field.selections.length && next !== void 0 && next !== "items" && !INDEX_SEGMENT3.test(next)) {
4587
+ const typename = resolveTypename(
4588
+ doc,
4589
+ field.selections,
4590
+ void 0,
4591
+ void 0
4592
+ );
4593
+ if (typename !== void 0) {
4594
+ value = fillObject(doc, field.selections, typename, {}, void 0, {
4595
+ links: null,
4596
+ lists: listKeysIn(root)
4597
+ });
4598
+ current = writeAtPath(current, fieldPath, value);
4599
+ }
4600
+ }
4601
+ walk2(value, field.selections, i2 + 1, fieldPath);
4602
+ }
4603
+ if (!matched && segment === "items") walk2(node, sel, i2 + 1, path);
4604
+ };
4605
+ walk2(owner, selections, 0, ownerPath);
4606
+ return { root: current, targets: out, plain };
4607
+ }
4608
+ var NON_ALPHANUMERIC = /[^a-z0-9]/gi;
4609
+ var typeKey = (name) => name.replace(NON_ALPHANUMERIC, "").toLowerCase();
4610
+ function resolveTypename(doc, selections, typename, typeName) {
4611
+ const known = typeConditions(selections, doc);
4612
+ const explicit = typeof typename === "string" ? typename : void 0;
4613
+ if (!known.length) return explicit ?? null;
4614
+ if (explicit && known.includes(explicit)) return explicit;
4615
+ for (const candidate of [explicit, typeName]) {
4616
+ if (typeof candidate !== "string" || !candidate) continue;
4617
+ const match = known.find((name) => typeKey(name) === typeKey(candidate));
4618
+ if (match) return match;
4619
+ }
4620
+ const named = explicit !== void 0 || typeof typeName === "string";
4621
+ return known.length === 1 && !named ? known[0] : void 0;
4622
+ }
4623
+ function buildEntry(doc, selections, entry, ctx) {
4624
+ const typename = resolveTypename(
4625
+ doc,
4626
+ selections,
4627
+ entry.__typename,
4628
+ entry.typeName
4629
+ );
4630
+ if (typename === void 0) return void 0;
4631
+ return fillObject(
4632
+ doc,
4633
+ selections,
4634
+ typename,
4635
+ entry.fields ?? {},
4636
+ entry._id,
4637
+ ctx
4638
+ );
4639
+ }
4640
+ function fillObject(doc, selections, typename, values, id, ctx) {
4641
+ const out = {};
4642
+ const { links } = ctx;
4643
+ const lists = ctx.lists(typename);
4644
+ const edit = isPlainRecord(values.__edit) ? values.__edit : null;
4645
+ const apiIdOf = /* @__PURE__ */ new Map();
4646
+ for (const key of Object.keys(values)) {
4647
+ apiIdOf.set(key, key);
4648
+ const name = graphqlFieldName(key);
4649
+ if (!apiIdOf.has(name)) apiIdOf.set(name, key);
4650
+ }
4651
+ if (edit && isPlainRecord(edit.apiIds)) {
4652
+ for (const [name, apiId] of Object.entries(edit.apiIds)) {
4653
+ if (typeof apiId === "string") apiIdOf.set(name, apiId);
4654
+ }
4655
+ }
4656
+ const valueOf = (name) => {
4657
+ if (Object.prototype.hasOwnProperty.call(values, name)) return values[name];
4658
+ const key = apiIdOf.get(name);
4659
+ return key !== void 0 && key in values ? values[key] : void 0;
4660
+ };
4661
+ const editField = (name) => {
4662
+ if (!edit) return null;
4663
+ if (typeof edit.field === "string")
4664
+ return name === "body" ? edit.field : null;
4665
+ if (typeof edit.path !== "string") return null;
4666
+ const apiId = apiIdOf.get(name) ?? name;
4667
+ const text = Array.isArray(edit.text) ? edit.text : [];
4668
+ if (!text.includes(apiId) && !text.includes(name)) return null;
4669
+ return `${edit.path}.items.${apiId}`;
4670
+ };
4671
+ const fields = collectFields(selections, typename ?? void 0, doc, {
4672
+ strict: true
4673
+ });
4674
+ for (const field of fields.values()) {
4675
+ if (field.name === "__typename") {
4676
+ out[field.key] = typename;
4677
+ } else if (field.name === "_id") {
4678
+ out[field.key] = typeof id === "string" ? id : values._id ?? null;
4679
+ } else if (field.selections.length) {
4680
+ const value = valueOf(field.name);
4681
+ out[field.key] = value == null && lists.has(field.key) ? [] : fillNested(doc, field.selections, value, ctx);
4682
+ } else {
4683
+ const value = valueOf(field.name);
4684
+ if (Array.isArray(value) && value.every(isScalarItem)) {
4685
+ out[field.key] = value;
4686
+ continue;
4687
+ }
4688
+ if (value == null && lists.has(field.key)) {
4689
+ out[field.key] = [];
4690
+ continue;
4691
+ }
4692
+ const scalar = value === void 0 || value !== null && typeof value === "object" ? null : value;
4693
+ const target = typeof scalar === "string" ? editField(field.name) : null;
4694
+ out[field.key] = target && links ? links.encode(scalar, target) : scalar;
4695
+ }
4696
+ }
4697
+ return out;
4698
+ }
4699
+ function fillNested(doc, selections, value, ctx) {
4700
+ const one = (item) => {
4701
+ if (!isPlainRecord(item)) return null;
4702
+ const typename = resolveTypename(
4703
+ doc,
4704
+ selections,
4705
+ item.__typename,
4706
+ item.typeName
4707
+ );
4708
+ if (typename === void 0) return null;
4709
+ return fillObject(doc, selections, typename, item, item._id, ctx);
4710
+ };
4711
+ if (Array.isArray(value)) return value.map(one).filter((v2) => v2 !== null);
4712
+ return value === void 0 ? null : one(value);
4713
+ }
4714
+ function isScalarItem(value) {
4715
+ return typeof value === "string" || typeof value === "number" || typeof value === "boolean";
4716
+ }
4717
+ var NO_KEYS = /* @__PURE__ */ new Set();
4718
+ var listKeysCache = /* @__PURE__ */ new WeakMap();
4719
+ function listKeysIn(result) {
4720
+ return (typename) => {
4721
+ if (!typename || result === null || typeof result !== "object") {
4722
+ return NO_KEYS;
4723
+ }
4724
+ let byType = listKeysCache.get(result);
4725
+ if (!byType) {
4726
+ byType = /* @__PURE__ */ new Map();
4727
+ const seen = /* @__PURE__ */ new WeakSet();
4728
+ const walk2 = (node) => {
4729
+ if (node === null || typeof node !== "object" || seen.has(node)) return;
4730
+ seen.add(node);
4731
+ if (Array.isArray(node)) {
4732
+ node.forEach(walk2);
4733
+ return;
4734
+ }
4735
+ const record = node;
4736
+ const type = typeof record.__typename === "string" ? record.__typename : null;
4737
+ for (const [key, value] of Object.entries(record)) {
4738
+ if (type && Array.isArray(value)) {
4739
+ let keys = byType.get(type);
4740
+ if (!keys) byType.set(type, keys = /* @__PURE__ */ new Set());
4741
+ keys.add(key);
4742
+ }
4743
+ walk2(value);
4744
+ }
4745
+ };
4746
+ walk2(result);
4747
+ listKeysCache.set(result, byType);
4748
+ }
4749
+ return byType.get(typename) ?? NO_KEYS;
4750
+ };
4751
+ }
4752
+ var EDIT_PATH = /\/content\/edit\/[^/?#]+/;
4753
+ function editLinks(result, entry) {
4754
+ const edit = entry.fields?.__edit;
4755
+ if (!isPlainRecord(edit) || typeof edit.item !== "string") return null;
4756
+ return editLinksFor(result, edit.item);
4757
+ }
4758
+ var ITEM_ID = /^[A-Za-z0-9_-]+$/;
4759
+ function editLinksFor(result, item) {
4760
+ if (!ITEM_ID.test(item)) return null;
4761
+ const template = findEditLink(result);
4762
+ if (!template) return null;
4763
+ return {
4764
+ encode(text, field) {
4765
+ if ((0, import_stega6.vercelStegaSplit)(text).encoded) return text;
4766
+ let href;
4767
+ try {
4768
+ href = new URL(template.href);
4769
+ } catch {
4770
+ return text;
4771
+ }
4772
+ href.pathname = href.pathname.replace(
4773
+ EDIT_PATH,
4774
+ () => `/content/edit/${item}`
4775
+ );
4776
+ href.searchParams.set("field", field);
4777
+ const link = href.toString().replace(/%2E/gi, ".").replace(/%2F/gi, "/");
4778
+ return (0, import_stega6.vercelStegaCombine)(text, { origin: template.origin, href: link });
4779
+ }
4780
+ };
4781
+ }
4782
+ var linkCache = /* @__PURE__ */ new WeakMap();
4783
+ function findEditLink(root) {
4784
+ if (root === null || typeof root !== "object") return null;
4785
+ if (linkCache.has(root)) return linkCache.get(root) ?? null;
4786
+ let found = null;
4787
+ const seen = /* @__PURE__ */ new WeakSet();
4788
+ const walk2 = (node) => {
4789
+ if (found) return;
4790
+ if (typeof node === "string") {
4791
+ if (!(0, import_stega6.vercelStegaSplit)(node).encoded) return;
4792
+ try {
4793
+ const decoded = (0, import_stega6.vercelStegaDecode)(node);
4794
+ if (typeof decoded?.origin === "string" && typeof decoded.href === "string" && EDIT_PATH.test(decoded.href)) {
4795
+ found = { origin: decoded.origin, href: decoded.href };
4796
+ }
4797
+ } catch {
4798
+ }
4799
+ return;
4800
+ }
4801
+ if (node === null || typeof node !== "object" || seen.has(node)) return;
4802
+ seen.add(node);
4803
+ for (const value of Object.values(node)) walk2(value);
4804
+ };
4805
+ walk2(root);
4806
+ linkCache.set(root, found);
4807
+ return found;
4808
+ }
4809
+
4810
+ // src/core/live-preview/edit-links.ts
4811
+ var import_stega7 = require("@vercel/stega");
4812
+ var ENTRY_PARAM = "entry";
4813
+ var TOP_LEVEL_ENTRY = /^[A-Za-z0-9_]+\.\d+$/;
4814
+ function entryIdOf(element) {
4815
+ if (!isRecord(element)) return void 0;
4816
+ if (typeof element._id === "string") return element._id;
4817
+ if (element.__typename !== "Assets" || !Array.isArray(element.items)) {
4818
+ return void 0;
4819
+ }
4820
+ const first = element.items[0];
4821
+ return isRecord(first) && typeof first._id === "string" ? first._id : void 0;
4822
+ }
4823
+ function isRecord(value) {
4824
+ return typeof value === "object" && value !== null && !Array.isArray(value);
4825
+ }
4826
+ function annotate(value, ctx) {
4827
+ if (!ctx.entryId) return value;
4828
+ const { cleaned, encoded } = (0, import_stega7.vercelStegaSplit)(value);
4829
+ if (!encoded) return value;
4830
+ let payload;
4831
+ try {
4832
+ payload = (0, import_stega7.vercelStegaDecode)(encoded);
4833
+ } catch {
4834
+ return value;
4835
+ }
4836
+ if (typeof payload?.href !== "string") return value;
4837
+ let url;
4838
+ try {
4839
+ url = new URL(payload.href);
4840
+ } catch {
4841
+ return value;
4842
+ }
4843
+ if (url.searchParams.has(ENTRY_PARAM)) return value;
4844
+ url.searchParams.set(ENTRY_PARAM, ctx.entryId);
4845
+ const itemId = decodeURIComponent(url.pathname.split("/").pop() ?? "");
4846
+ if (!url.searchParams.has("field") && ctx.entryField && TOP_LEVEL_ENTRY.test(ctx.entryField) && itemId === ctx.entryOwnerId) {
4847
+ url.searchParams.set("field", ctx.entryField);
4848
+ }
4849
+ return (0, import_stega7.vercelStegaCombine)(
4850
+ cleaned,
4851
+ { ...payload, href: url.toString() },
4852
+ false
4853
+ );
4854
+ }
4855
+ function walk(value, selections, ctx) {
4856
+ if (typeof value === "string") return annotate(value, ctx);
4857
+ if (Array.isArray(value)) {
4858
+ let changed2 = false;
4859
+ const next2 = value.map((element, index) => {
4860
+ const path = [...ctx.path, String(index)];
4861
+ const entryId = ctx.inAssets ? void 0 : entryIdOf(element);
4862
+ const entry = entryId ? {
4863
+ entryId,
4864
+ entryField: ctx.ownerId ? path.join(".") : void 0,
4865
+ entryOwnerId: ctx.ownerId,
4866
+ inAssets: isRecord(element) && element._id === void 0
4867
+ } : {};
4868
+ const out = walk(element, selections, { ...ctx, ...entry, path });
4869
+ if (out !== element) changed2 = true;
4870
+ return out;
4871
+ });
4872
+ return changed2 ? next2 : value;
4873
+ }
4874
+ if (!isRecord(value)) return value;
4875
+ const id = typeof value._id === "string" ? value._id : void 0;
4876
+ const typename = typeof value.__typename === "string" ? value.__typename : void 0;
4877
+ const fields = ctx.doc && selections ? collectFields(selections, typename, ctx.doc) : null;
4878
+ let changed = false;
4879
+ const next = {};
4880
+ for (const [key, child] of Object.entries(value)) {
4881
+ const field = fields?.get(key);
4882
+ const name = field?.name ?? key;
4883
+ const out = walk(child, field?.selections ?? null, {
4884
+ ...ctx,
4885
+ ownerId: id ?? ctx.ownerId,
4886
+ path: id ? [name] : [...ctx.path, name]
4887
+ });
4888
+ if (out !== child) changed = true;
4889
+ next[key] = out;
4890
+ }
4891
+ return changed ? next : value;
4892
+ }
4893
+ function annotateEditLinks(result, query, operationName) {
4894
+ let doc = null;
4895
+ if (query !== void 0) {
4896
+ try {
4897
+ doc = readQueryDocument(query, operationName);
4898
+ } catch {
4899
+ doc = null;
4900
+ }
4901
+ }
4902
+ return walk(result, doc?.operation ?? null, { doc, path: [] });
4903
+ }
4904
+
4905
+ // src/core/live-preview/bridge-events.ts
4906
+ var debug12 = createScopedLogger("live-preview:bridge");
4907
+ function createLiveBridge(store, options = {}) {
4908
+ let parentOrigin = null;
4909
+ const onMessage = (evt) => {
4910
+ const data = evt?.data;
4911
+ if (data?.event === "prepr:initVE" && !parentOrigin) {
4912
+ if (isAllowedEditorOrigin(evt.origin, options.allowedEditorOrigins?.())) {
4913
+ parentOrigin = evt.origin;
4914
+ } else {
4915
+ debug12.warn("ignored live preview handshake from origin", evt.origin);
4916
+ }
4917
+ return;
4918
+ }
4919
+ const trusted = parentOrigin ?? getTrustedParentOrigin();
4920
+ if (!trusted || evt.origin !== trusted) return;
4921
+ if (data?.event === "prepr:fieldChange") {
4922
+ if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && isLiveFieldChange(data.field, data.value)) {
4923
+ const edit = parseEditTarget(data.edit);
4924
+ if (edit) store.pushPatch(data.itemId, data.field, data.value, edit);
4925
+ else store.pushPatch(data.itemId, data.field, data.value);
4926
+ } else {
4927
+ debug12.log("dropped malformed fieldChange", data);
4928
+ }
4929
+ return;
4930
+ }
4931
+ if (data?.event === "prepr:listChange") {
4932
+ const list = parseListEntries(data.entries);
4933
+ if (typeof data.itemId === "string" && typeof data.field === "string" && data.field && list) {
4934
+ store.pushList(data.itemId, data.field, list);
4935
+ } else {
4936
+ debug12.log("dropped malformed listChange", data);
4937
+ }
4938
+ return;
4939
+ }
4940
+ if (data?.event === "prepr:itemChanged") {
4941
+ if (typeof data.itemId === "string") {
4942
+ store.invalidate(data.itemId);
4943
+ } else {
4944
+ debug12.log("dropped malformed itemChanged", data);
4945
+ }
4946
+ }
4947
+ };
4948
+ return {
4949
+ start() {
4950
+ window.addEventListener("message", onMessage);
4951
+ sendPreprEvent(
4952
+ "live_ready",
4953
+ options.strictMode?.() ? { strictMode: true } : void 0,
4954
+ { allowUntrustedTarget: true }
4955
+ );
4956
+ },
4957
+ stop() {
4958
+ window.removeEventListener("message", onMessage);
4959
+ }
4960
+ };
4961
+ }
4962
+
4963
+ // src/core/live-preview/live-store.ts
4964
+ var debug13 = createScopedLogger("live-preview");
4965
+ var INVALIDATE_DEBOUNCE_MS = 150;
4966
+ var MAX_LOG_ENTRIES2 = 500;
4967
+ function withPatch(log, patch) {
4968
+ let previous = -1;
4969
+ for (let i2 = log.length - 1; i2 >= 0; i2--) {
4970
+ const p2 = log[i2];
4971
+ if (p2.type === patch.type && p2.itemId === patch.itemId && p2.field === patch.field) {
4972
+ previous = i2;
4973
+ break;
4974
+ }
4975
+ }
4976
+ if (previous === -1) return [...log, patch];
4977
+ const since = log.slice(previous + 1);
4978
+ const listPrefix = `${patch.field}.`;
4979
+ const dependsOnPosition = since.some(
4980
+ (p2) => p2.type === "list" || patch.type === "list" && p2.itemId === patch.itemId && p2.field.startsWith(listPrefix)
4981
+ );
4982
+ if (dependsOnPosition) return [...log, patch];
4983
+ return [...log.slice(0, previous), ...since, patch];
4984
+ }
4985
+ function createLiveStore(options = {}) {
4986
+ const accepts = (patch) => options.accept ? options.accept(patch) : true;
4987
+ const entries = /* @__PURE__ */ new Set();
4988
+ const invalidateListeners = /* @__PURE__ */ new Set();
4989
+ const pendingInvalidates = /* @__PURE__ */ new Map();
4990
+ let defaultFetcher = null;
4991
+ let log = [];
4992
+ const logListeners = /* @__PURE__ */ new Set();
4993
+ function record(patch) {
4994
+ log = withPatch(log, patch).slice(-MAX_LOG_ENTRIES2);
4995
+ logListeners.forEach((fn) => fn(log));
4996
+ }
4997
+ function notify(entry) {
4998
+ entry.listeners.forEach((fn) => fn());
4999
+ }
5000
+ async function refetch(entry) {
5001
+ const fetcher = entry.opts.fetcher ?? defaultFetcher;
5002
+ if (!fetcher || entry.opts.query == null) return;
5003
+ const token = ++entry.fetchToken;
5004
+ try {
5005
+ const result = await fetcher(entry.opts.query, entry.opts.variables);
5006
+ if (token !== entry.fetchToken || !entries.has(entry)) return;
5007
+ entry.snapshot = result;
5008
+ entry.index = indexSnapshot(result);
5009
+ notify(entry);
5010
+ } catch (error) {
5011
+ debug13.log("refetch failed, keeping previous snapshot", error);
5012
+ }
5013
+ }
5014
+ function runInvalidate(itemId) {
5015
+ pendingInvalidates.delete(itemId);
5016
+ invalidateListeners.forEach((fn) => fn(itemId));
5017
+ for (const entry of entries) {
5018
+ if (entry.index.has(itemId)) void refetch(entry);
5019
+ }
5020
+ }
5021
+ return {
5022
+ register(data, opts = {}) {
5023
+ const entry = {
5024
+ snapshot: data,
5025
+ index: indexSnapshot(data),
5026
+ opts,
5027
+ listeners: /* @__PURE__ */ new Set(),
5028
+ fetchToken: 0
5029
+ };
5030
+ for (const patch of log) {
5031
+ if (patch.type === "field") {
5032
+ entry.snapshot = patchSnapshot(
5033
+ entry.snapshot,
5034
+ entry.index,
5035
+ patch.itemId,
5036
+ patch.field,
5037
+ patch.value
5038
+ );
5039
+ } else {
5040
+ const next = syncListSnapshot(
5041
+ entry.snapshot,
5042
+ entry.index,
5043
+ patch.itemId,
5044
+ patch.field,
5045
+ patch.entries
5046
+ );
5047
+ if (next !== entry.snapshot) {
5048
+ entry.snapshot = next;
5049
+ entry.index = indexSnapshot(next);
5050
+ }
5051
+ }
5052
+ }
5053
+ entries.add(entry);
5054
+ return {
5055
+ getSnapshot: () => entry.snapshot,
5056
+ subscribe(fn) {
5057
+ entry.listeners.add(fn);
5058
+ return () => entry.listeners.delete(fn);
5059
+ },
5060
+ unregister() {
5061
+ entry.listeners.clear();
5062
+ entries.delete(entry);
5063
+ }
5064
+ };
5065
+ },
5066
+ pushPatch(itemId, field, value, edit) {
5067
+ const patch = edit ? { type: "field", itemId, field, value, edit } : { type: "field", itemId, field, value };
5068
+ if (!accepts(patch)) return;
5069
+ record(patch);
5070
+ for (const entry of entries) {
5071
+ const next = patchSnapshot(
5072
+ entry.snapshot,
5073
+ entry.index,
5074
+ itemId,
5075
+ field,
5076
+ value
5077
+ );
5078
+ if (next !== entry.snapshot) {
5079
+ entry.snapshot = next;
5080
+ notify(entry);
5081
+ }
5082
+ }
5083
+ },
5084
+ pushList(itemId, field, list) {
5085
+ const patch = { type: "list", itemId, field, entries: list };
5086
+ if (!accepts(patch)) return;
5087
+ record(patch);
5088
+ for (const entry of entries) {
5089
+ const next = syncListSnapshot(
5090
+ entry.snapshot,
5091
+ entry.index,
5092
+ itemId,
5093
+ field,
5094
+ list
5095
+ );
5096
+ if (next !== entry.snapshot) {
5097
+ entry.snapshot = next;
5098
+ entry.index = indexSnapshot(next);
5099
+ notify(entry);
5100
+ }
5101
+ }
5102
+ },
5103
+ invalidate(itemId) {
5104
+ if (log.length) {
5105
+ log = [];
5106
+ logListeners.forEach((fn) => fn(log));
5107
+ }
5108
+ const pending = pendingInvalidates.get(itemId);
5109
+ if (pending) clearTimeout(pending);
5110
+ pendingInvalidates.set(
5111
+ itemId,
5112
+ setTimeout(() => runInvalidate(itemId), INVALIDATE_DEBOUNCE_MS)
5113
+ );
5114
+ },
5115
+ setFetcher(fetcher) {
5116
+ defaultFetcher = fetcher;
5117
+ },
5118
+ onInvalidate(fn) {
5119
+ invalidateListeners.add(fn);
5120
+ return () => invalidateListeners.delete(fn);
5121
+ },
5122
+ getLog: () => log,
5123
+ onLogChange(fn) {
5124
+ logListeners.add(fn);
5125
+ return () => logListeners.delete(fn);
5126
+ }
5127
+ };
5128
+ }
5129
+
5130
+ // src/core/live-preview/morph.ts
5131
+ function morphElement(current, next) {
5132
+ syncAttributes(current, next);
5133
+ morphChildren(current, next);
5134
+ }
5135
+ var SKIP_ATTR = "data-prepr-morph-skip";
5136
+ var RUNTIME_ATTR_PREFIXES = ["data-prepr-"];
5137
+ function isRuntimeAttribute(name) {
5138
+ return RUNTIME_ATTR_PREFIXES.some((prefix) => name.startsWith(prefix));
5139
+ }
5140
+ function syncAttributes(current, next) {
5141
+ for (const { name } of Array.from(current.attributes)) {
5142
+ if (!next.hasAttribute(name) && !isRuntimeAttribute(name)) {
5143
+ current.removeAttribute(name);
5144
+ }
5145
+ }
5146
+ for (const { name, value } of Array.from(next.attributes)) {
5147
+ if (current.getAttribute(name) !== value) current.setAttribute(name, value);
5148
+ }
5149
+ }
5150
+ function isSkipped(node) {
5151
+ return node.nodeType === Node.ELEMENT_NODE && (node.hasAttribute(SKIP_ATTR) || node.localName === "prepr-toolbar");
5152
+ }
5153
+ function sameKind(a2, b2) {
5154
+ if (a2.nodeType !== b2.nodeType) return false;
5155
+ if (a2.nodeType !== Node.ELEMENT_NODE) return true;
5156
+ const ea = a2;
5157
+ const eb = b2;
5158
+ return ea.localName === eb.localName && ea.id === eb.id;
5159
+ }
5160
+ function morphChildren(current, next) {
5161
+ const wanted = Array.from(next.childNodes).filter((n2) => !isSkipped(n2));
5162
+ const existing = Array.from(current.childNodes).filter((n2) => !isSkipped(n2));
5163
+ wanted.forEach((target, i2) => {
5164
+ const node = existing[i2];
5165
+ if (node && sameKind(node, target)) {
5166
+ if (node.nodeType === Node.ELEMENT_NODE) {
5167
+ const element = node;
5168
+ if (element.localName === "script") return;
5169
+ if (element.localName === "astro-island" && !element.isEqualNode(target)) {
5170
+ element.replaceWith(adopt(target, current));
5171
+ return;
5172
+ }
5173
+ morphElement(element, target);
5174
+ } else if (node.nodeValue !== target.nodeValue) {
5175
+ node.nodeValue = target.nodeValue;
5176
+ }
5177
+ return;
5178
+ }
5179
+ const replacement = adopt(target, current);
5180
+ if (node) node.replaceWith(replacement);
5181
+ else current.appendChild(replacement);
5182
+ });
5183
+ for (const extra of existing.slice(wanted.length)) extra.remove();
5184
+ }
5185
+ function adopt(node, parent) {
5186
+ const copy = parent.ownerDocument.importNode(node, true);
5187
+ if (copy.nodeType === Node.ELEMENT_NODE) {
5188
+ const element = copy;
5189
+ const scripts = element.localName === "script" ? [element] : Array.from(element.querySelectorAll("script"));
5190
+ scripts.forEach((s2) => s2.setAttribute("data-prepr-morphed", ""));
5191
+ }
5192
+ return copy;
5193
+ }
5194
+
5195
+ // src/core/live-preview/html-live.ts
5196
+ var debug14 = createScopedLogger("live-preview:html");
5197
+ var COMPLETE_DOCUMENT = /<\/html>\s*$/i;
5198
+ var SYNC_DEBOUNCE_MS = 150;
5199
+ function startHtmlLivePreview(options = {}) {
5200
+ if (typeof window === "undefined" || window.self === window.top) {
5201
+ return { stop() {
5202
+ } };
5203
+ }
5204
+ const store = createLiveStore();
5205
+ const bridge = createLiveBridge(store, {
5206
+ allowedEditorOrigins: () => options.allowedEditorOrigins,
5207
+ strictMode: () => options.strictMode
5208
+ });
5209
+ bridge.start();
5210
+ let timer = null;
5211
+ let inFlight = false;
5212
+ let pending = null;
5213
+ let stopped = false;
5214
+ const render = async (log) => {
5215
+ inFlight = true;
5216
+ try {
5217
+ const response = await fetch(window.location.href, {
5218
+ method: log.length ? "POST" : "GET",
5219
+ headers: log.length ? { "Content-Type": "application/json", [HEADER_LIVE_RENDER]: "1" } : {},
5220
+ body: log.length ? JSON.stringify({ log }) : void 0,
5221
+ credentials: "same-origin",
5222
+ cache: "no-store"
5223
+ });
5224
+ if (!response.ok) throw new Error(`HTTP ${response.status}`);
5225
+ const html = await response.text();
5226
+ if (stopped) return;
5227
+ if (!COMPLETE_DOCUMENT.test(html)) {
5228
+ throw new Error("incomplete page; a component failed to render");
5229
+ }
5230
+ const next = new DOMParser().parseFromString(html, "text/html");
5231
+ if (next.title && next.title !== document.title)
5232
+ document.title = next.title;
5233
+ morphElement(document.body, next.body);
5234
+ } catch (error) {
5235
+ debug14.log("re-render failed", error);
5236
+ } finally {
5237
+ inFlight = false;
5238
+ }
5239
+ if (pending && !stopped) {
5240
+ const queued = pending;
5241
+ pending = null;
5242
+ void render(queued);
5243
+ }
5244
+ };
5245
+ const schedule = (log) => {
5246
+ if (timer) clearTimeout(timer);
5247
+ timer = setTimeout(() => {
5248
+ timer = null;
5249
+ if (inFlight) pending = log;
5250
+ else void render(log);
5251
+ }, SYNC_DEBOUNCE_MS);
5252
+ };
5253
+ const offLog = store.onLogChange((log) => {
5254
+ if (log.length) schedule(log);
5255
+ });
5256
+ const offInvalidate = store.onInvalidate(() => schedule([]));
5257
+ return {
5258
+ stop() {
5259
+ stopped = true;
5260
+ if (timer) clearTimeout(timer);
5261
+ offLog();
5262
+ offInvalidate();
5263
+ bridge.stop();
5264
+ }
5265
+ };
5266
+ }
5267
+
5268
+ // src/core/live-preview/live-request.ts
5269
+ var MAX_BODY_BYTES = 1e6;
5270
+ async function readLiveLogFromRequest(request) {
5271
+ try {
5272
+ if (request.method !== "POST") return [];
5273
+ if (request.headers.get(HEADER_LIVE_RENDER) !== "1") return [];
5274
+ const origin = request.headers.get("origin");
5275
+ if (!origin || origin !== new URL(request.url).origin) return [];
5276
+ const type = request.headers.get("content-type") ?? "";
5277
+ if (!type.toLowerCase().startsWith("application/json")) return [];
5278
+ const length = Number(request.headers.get("content-length") ?? "0");
5279
+ if (length > MAX_BODY_BYTES) return [];
5280
+ const text = await request.clone().text();
5281
+ if (text.length > MAX_BODY_BYTES) return [];
5282
+ const body = JSON.parse(text);
5283
+ return parsePatchLog(body?.log);
5284
+ } catch {
5285
+ return [];
5286
+ }
5287
+ }
5288
+
2371
5289
  // src/core/server.ts
2372
5290
  var PreprError = class extends Error {
2373
5291
  constructor(message, code, context, originalError) {
@@ -2755,6 +5673,7 @@ function setTrackingParam(key, value) {
2755
5673
  PreprError,
2756
5674
  PreprToolbarElement,
2757
5675
  VERSION,
5676
+ annotateEditLinks,
2758
5677
  createPreprPreview,
2759
5678
  createToolbarStore,
2760
5679
  definePreprToolbar,
@@ -2766,10 +5685,16 @@ function setTrackingParam(key, value) {
2766
5685
  getPreprUUIDFromHeaders,
2767
5686
  getToolbarPropsFromHeaders,
2768
5687
  loadTrackingPixel,
5688
+ normalizeQuery,
5689
+ parsePatchLog,
5690
+ previewAttrs,
2769
5691
  processPreprRequest,
5692
+ readLiveLogFromRequest,
2770
5693
  resolveFeatures,
2771
5694
  setTrackingParam,
5695
+ startHtmlLivePreview,
2772
5696
  stegaClean,
5697
+ stegaTarget,
2773
5698
  trackEvent,
2774
5699
  validatePreprToken
2775
5700
  });