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

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 +430 -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-4JNKYIEV.js → chunk-6423Q744.js} +1449 -642
  7. package/dist/chunk-AOHA5INA.js +26 -0
  8. package/dist/chunk-IPMB32AZ.js +246 -0
  9. package/dist/chunk-KD3GK6TO.js +1221 -0
  10. package/dist/chunk-QSXIHRH4.js +184 -0
  11. package/dist/chunk-S3VT4UWM.js +75 -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 +3367 -416
  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 +1608 -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 +1608 -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 +1608 -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
@@ -71,6 +71,40 @@ var ALL_FEATURES_ENABLED = Object.freeze({
71
71
  editMode: true
72
72
  });
73
73
 
74
+ // src/core/locales/de.json
75
+ var de_default = {
76
+ common: {
77
+ viewingAs: "Ansicht als:",
78
+ user: "Benutzer",
79
+ off: "Aus",
80
+ on: "An",
81
+ reset: "Zur\xFCcksetzen",
82
+ toolbar: "Symbolleiste"
83
+ },
84
+ adaptiveContent: {
85
+ adaptiveContent: "Adaptive Inhalte",
86
+ enablePreview: "Vorschaumodus",
87
+ segment: "Segment",
88
+ ABVariant: "A/B-Variante",
89
+ chooseSegment: "Segment w\xE4hlen",
90
+ searchSegments: "Segmente suchen",
91
+ none: "Keine Segmente",
92
+ allOtherUsers: "Alle anderen Benutzer",
93
+ offDescription: "Die Website als echter Benutzer ansehen (Cookies verwenden)",
94
+ onDescription: "Mit der Symbolleiste Segmente und A/B-Varianten simulieren"
95
+ },
96
+ editingTools: {
97
+ editingTools: "Bearbeitungswerkzeuge",
98
+ editMode: "Bearbeitungsmodus",
99
+ ariaCloseEditMode: "Bearbeitungsmodus schlie\xDFen"
100
+ },
101
+ visualEditor: {
102
+ edit: "Bearbeiten",
103
+ otherItem: "anderes Element",
104
+ linkedItem: "verkn\xFCpftes Element"
105
+ }
106
+ };
107
+
74
108
  // src/core/locales/en.json
75
109
  var en_default = {
76
110
  common: {
@@ -97,6 +131,45 @@ var en_default = {
97
131
  editingTools: "Editing tools",
98
132
  editMode: "Edit mode",
99
133
  ariaCloseEditMode: "Close edit mode"
134
+ },
135
+ visualEditor: {
136
+ edit: "Edit",
137
+ otherItem: "other item",
138
+ linkedItem: "linked item"
139
+ }
140
+ };
141
+
142
+ // src/core/locales/fr.json
143
+ var fr_default = {
144
+ common: {
145
+ viewingAs: "Affichage en tant que :",
146
+ user: "Utilisateur",
147
+ off: "D\xE9sactiv\xE9",
148
+ on: "Activ\xE9",
149
+ reset: "R\xE9initialiser",
150
+ toolbar: "Barre d'outils"
151
+ },
152
+ adaptiveContent: {
153
+ adaptiveContent: "Contenu adaptatif",
154
+ enablePreview: "Mode aper\xE7u",
155
+ segment: "Segment",
156
+ ABVariant: "Variante A/B",
157
+ chooseSegment: "Choisir un segment",
158
+ searchSegments: "Rechercher des segments",
159
+ none: "Aucun segment",
160
+ allOtherUsers: "Tous les autres utilisateurs",
161
+ offDescription: "Voir le site comme un vrai utilisateur (utiliser les cookies)",
162
+ onDescription: "Utilisez la barre d'outils pour simuler des segments et des variantes A/B"
163
+ },
164
+ editingTools: {
165
+ editingTools: "Outils d'\xE9dition",
166
+ editMode: "Mode \xE9dition",
167
+ ariaCloseEditMode: "Fermer le mode \xE9dition"
168
+ },
169
+ visualEditor: {
170
+ edit: "Modifier",
171
+ otherItem: "autre \xE9l\xE9ment",
172
+ linkedItem: "\xE9l\xE9ment li\xE9"
100
173
  }
101
174
  };
102
175
 
@@ -126,13 +199,20 @@ var nl_default = {
126
199
  editingTools: "Editing Tools",
127
200
  editMode: "Edit mode",
128
201
  ariaCloseEditMode: "Edit mode sluiten"
202
+ },
203
+ visualEditor: {
204
+ edit: "Bewerken",
205
+ otherItem: "ander item",
206
+ linkedItem: "gekoppeld item"
129
207
  }
130
208
  };
131
209
 
132
210
  // src/core/i18n.ts
133
211
  var dictionaries = {
134
212
  en: en_default,
135
- nl: nl_default
213
+ nl: nl_default,
214
+ fr: fr_default,
215
+ de: de_default
136
216
  };
137
217
  var LOCALES = Object.keys(dictionaries);
138
218
  function isLocale(value) {
@@ -225,52 +305,53 @@ function createScopedLogger(scopeName) {
225
305
  prefix: `[Prepr][${scopeName}]`
226
306
  });
227
307
  }
228
- function throttle(func, delay) {
229
- let timeoutId = null;
230
- let lastExecTime = 0;
231
- const throttledFunc = ((...args) => {
232
- const currentTime = Date.now();
233
- const timeSinceLastExec = currentTime - lastExecTime;
234
- if (timeSinceLastExec >= delay) {
235
- func(...args);
236
- lastExecTime = currentTime;
237
- } else {
238
- if (timeoutId) {
239
- clearTimeout(timeoutId);
240
- }
241
- timeoutId = setTimeout(() => {
242
- func(...args);
243
- lastExecTime = Date.now();
244
- timeoutId = null;
245
- }, delay - timeSinceLastExec);
246
- }
247
- });
248
- throttledFunc.cancel = () => {
249
- if (timeoutId) {
250
- clearTimeout(timeoutId);
251
- timeoutId = null;
252
- }
253
- };
254
- return throttledFunc;
255
- }
256
- function createElementCache(query, ttl = 1e3) {
257
- let cache = null;
258
- let lastCacheTime = 0;
259
- return () => {
260
- const now = Date.now();
261
- if (!cache || now - lastCacheTime > ttl) {
262
- cache = document.querySelectorAll(query);
263
- lastCacheTime = now;
264
- }
265
- return cache;
266
- };
267
- }
268
308
 
269
309
  // src/core/iframe-bridge.ts
270
310
  var debug = createScopedLogger("iframe-bridge");
311
+ var MAX_REFERENCES = 500;
312
+ var MAX_HREF_LENGTH = 8192;
313
+ var MAX_ASSETS = 500;
314
+ var MAX_ASSET_KEY_LENGTH = 2048;
315
+ var MAX_ASSET_FIELD_LENGTH = 512;
316
+ function parseIds(value) {
317
+ if (!Array.isArray(value)) return void 0;
318
+ return value.slice(0, MAX_REFERENCES).filter((id) => typeof id === "string" && id.length > 0);
319
+ }
320
+ function parseReferences(value) {
321
+ if (!Array.isArray(value)) return void 0;
322
+ const list = [];
323
+ for (const entry of value.slice(0, MAX_REFERENCES)) {
324
+ const id = entry?.id;
325
+ const field = entry?.field;
326
+ if (typeof id === "string" && id && typeof field === "string" && field) {
327
+ list.push({ id, field });
328
+ }
329
+ }
330
+ return list;
331
+ }
332
+ function boundedString(value, max) {
333
+ return typeof value === "string" && value && value.length <= max ? value : void 0;
334
+ }
335
+ function parseAssets(value) {
336
+ if (!Array.isArray(value)) return void 0;
337
+ const list = [];
338
+ for (const entry of value.slice(0, MAX_ASSETS)) {
339
+ if (!entry || typeof entry !== "object") continue;
340
+ const raw = entry;
341
+ const key = boundedString(raw.key, MAX_ASSET_KEY_LENGTH);
342
+ const field = boundedString(raw.field, MAX_ASSET_FIELD_LENGTH);
343
+ if (!key || !field) continue;
344
+ const entryId = boundedString(raw.entryId, MAX_ASSET_FIELD_LENGTH);
345
+ list.push(entryId ? { key, field, entryId } : { key, field });
346
+ }
347
+ return list;
348
+ }
271
349
  var EDITOR_BASE_DOMAIN = "prepr.io";
272
350
  function isAllowedEditorOrigin(origin, allowed) {
273
- if (allowed) return allowed.includes(origin);
351
+ if (allowed) {
352
+ if (allowed.includes("*")) return origin !== "null";
353
+ return allowed.includes(origin);
354
+ }
274
355
  let url;
275
356
  try {
276
357
  url = new URL(origin);
@@ -286,6 +367,10 @@ function isAllowedEditorOrigin(origin, allowed) {
286
367
  function createIframeBridge(store, options = {}) {
287
368
  const allowedOrigins = options.allowedEditorOrigins;
288
369
  const onScrollToField = options.onScrollToField;
370
+ const onHandshake = options.onHandshake;
371
+ const onReferences = options.onReferences;
372
+ const onAssets = options.onAssets;
373
+ const onAllowNavigation = options.onAllowNavigation;
289
374
  let parentOrigin = null;
290
375
  const onKeyDown = (event) => {
291
376
  const key = event.key.toLowerCase();
@@ -295,7 +380,10 @@ function createIframeBridge(store, options = {}) {
295
380
  const onMessage = (evt) => {
296
381
  const data = evt?.data;
297
382
  if (data?.event === "prepr:initVE" && !parentOrigin) {
298
- if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) return;
383
+ if (!isAllowedEditorOrigin(evt.origin, allowedOrigins)) {
384
+ debug.warn("ignored editor handshake from origin", evt.origin);
385
+ return;
386
+ }
299
387
  parentOrigin = evt.origin;
300
388
  setTrustedParentOrigin(parentOrigin);
301
389
  if (data.scrollPosition != null) {
@@ -303,8 +391,43 @@ function createIframeBridge(store, options = {}) {
303
391
  setTimeout(() => window.scrollTo(0, top), 1);
304
392
  }
305
393
  store?.set({ previewMode: true, editMode: data.editMode ?? true });
394
+ const itemId = typeof data.itemId === "string" && data.itemId ? data.itemId : void 0;
395
+ const referencedIds = parseReferences(data.referencedIds);
396
+ const componentIds = parseIds(data.componentIds);
397
+ const uiLocale = isLocale(data.uiLocale) ? data.uiLocale : void 0;
398
+ const navigationGuard = data.navigationGuard === true;
399
+ const assets = parseAssets(data.assets);
400
+ try {
401
+ onHandshake?.({
402
+ ...itemId && { itemId },
403
+ ...referencedIds && { referencedIds },
404
+ ...componentIds && { componentIds },
405
+ ...uiLocale && { uiLocale },
406
+ ...navigationGuard && { navigationGuard },
407
+ ...assets && { assets }
408
+ });
409
+ } catch (error) {
410
+ debug.warn("handshake handler failed", error);
411
+ }
306
412
  }
307
413
  if (!parentOrigin || evt.origin !== parentOrigin) return;
414
+ if (data?.event === "prepr:references") {
415
+ const assets = parseAssets(data.assets);
416
+ if (assets) {
417
+ try {
418
+ onAssets?.(assets);
419
+ } catch (error) {
420
+ debug.warn("assets handler failed", error);
421
+ }
422
+ }
423
+ const referencedIds = parseReferences(data.referencedIds);
424
+ if (!referencedIds) return;
425
+ try {
426
+ onReferences?.(referencedIds, parseIds(data.componentIds));
427
+ } catch (error) {
428
+ debug.warn("references handler failed", error);
429
+ }
430
+ }
308
431
  if (data?.event === "prepr:getScrollPosition") {
309
432
  const currentScrollY = window.scrollY || document.documentElement.scrollTop;
310
433
  sendPreprEvent("getScrollPosition", { value: currentScrollY });
@@ -312,12 +435,32 @@ function createIframeBridge(store, options = {}) {
312
435
  if (data?.event === "prepr:scrollToField") {
313
436
  if (typeof data.field !== "string" || !data.field) return;
314
437
  if (data.id !== void 0 && typeof data.id !== "string") return;
438
+ const block = typeof data.block === "number" && Number.isInteger(data.block) ? data.block : void 0;
439
+ const entryId = typeof data.entryId === "string" && data.entryId ? data.entryId : void 0;
440
+ const fieldById = typeof data.fieldById === "string" && data.fieldById ? data.fieldById : void 0;
315
441
  try {
316
- onScrollToField?.({ field: data.field, id: data.id });
442
+ onScrollToField?.({
443
+ field: data.field,
444
+ id: data.id,
445
+ block,
446
+ entryId,
447
+ fieldById
448
+ });
317
449
  } catch (error) {
318
450
  debug.warn("scrollToField handler failed", error);
319
451
  }
320
452
  }
453
+ if (data?.event === "prepr:allowNavigation") {
454
+ const href = data.href;
455
+ if (typeof href !== "string" || !href || href.length > MAX_HREF_LENGTH) {
456
+ return;
457
+ }
458
+ try {
459
+ onAllowNavigation?.(href);
460
+ } catch (error) {
461
+ debug.warn("allowNavigation handler failed", error);
462
+ }
463
+ }
321
464
  };
322
465
  return {
323
466
  start() {
@@ -369,6 +512,7 @@ function decodeStega(str) {
369
512
 
370
513
  // src/core/stega/dom.ts
371
514
  var IGNORED_ANCESTORS = "script, style, noscript";
515
+ var AUTO_ENTRY_ATTR = "data-prepr-entry-auto";
372
516
  function isIgnoredTextNode(node) {
373
517
  if (node.parentElement?.closest(IGNORED_ANCESTORS)) return true;
374
518
  return !node.textContent?.trim();
@@ -402,7 +546,9 @@ function parseEditHref(href) {
402
546
  return {
403
547
  id: url.pathname.split("/").pop() || void 0,
404
548
  field: url.searchParams.get("field") || void 0,
405
- locale: url.searchParams.get("locale") || void 0
549
+ locale: url.searchParams.get("locale") || void 0,
550
+ // Added by the toolkit's `annotateEditLinks`, not by Prepr.
551
+ entry: url.searchParams.get("entry") || void 0
406
552
  };
407
553
  } catch {
408
554
  return {};
@@ -416,45 +562,68 @@ function setOrRemove(element, name, value) {
416
562
  }
417
563
  }
418
564
  function tagEncodedElement(element, decoded) {
565
+ if (isSiteTagged(element)) return false;
419
566
  element.setAttribute("data-prepr-encoded", "");
420
567
  element.setAttribute("data-prepr-href", decoded.href);
421
568
  element.setAttribute("data-prepr-origin", decoded.origin);
422
- const { id, field, locale } = parseEditHref(decoded.href);
569
+ const { id, field, locale, entry } = parseEditHref(decoded.href);
423
570
  setOrRemove(element, "data-prepr-id", id);
424
- setOrRemove(element, "data-prepr-field", field);
571
+ const fieldHint = element.getAttribute("data-prepr-edit-target")?.trim() || void 0;
572
+ setOrRemove(element, "data-prepr-field", field ?? fieldHint);
425
573
  setOrRemove(element, "data-prepr-locale", locale);
574
+ const siteEntry = element.hasAttribute("data-prepr-entry-id") && !element.hasAttribute(AUTO_ENTRY_ATTR);
575
+ if (!siteEntry) {
576
+ setOrRemove(element, "data-prepr-entry-id", entry);
577
+ setOrRemove(element, AUTO_ENTRY_ATTR, entry && "true");
578
+ }
579
+ return true;
580
+ }
581
+ function isSiteTagged(element) {
582
+ return element.hasAttribute("data-prepr-encoded") && !element.hasAttribute("data-prepr-href");
583
+ }
584
+ function readEditPayload(element) {
585
+ return {
586
+ href: element.getAttribute("data-prepr-href") ?? "",
587
+ origin: element.getAttribute("data-prepr-origin") ?? "",
588
+ id: element.getAttribute("data-prepr-id") ?? void 0,
589
+ field: element.getAttribute("data-prepr-field") ?? void 0,
590
+ locale: element.getAttribute("data-prepr-locale") ?? void 0,
591
+ entryId: element.getAttribute("data-prepr-entry-id") || void 0
592
+ };
426
593
  }
427
594
 
428
595
  // src/core/stega/auto-clean.ts
429
596
  var debug3 = createScopedLogger("stega:auto-clean");
597
+ var cleanedNodes = /* @__PURE__ */ new WeakMap();
430
598
  function createStegaAutoClean(options) {
431
599
  const stripText = options?.enabled ?? true;
432
600
  let observer = null;
433
601
  let processing = false;
434
- let cleaned = /* @__PURE__ */ new WeakMap();
435
602
  let pendingHidden = /* @__PURE__ */ new Set();
436
603
  let pendingDirty = /* @__PURE__ */ new Set();
437
604
  let debounceTimeout = null;
438
605
  function cleanTextNode(textNode) {
439
606
  if (isIgnoredTextNode(textNode)) return false;
440
607
  const textContent = textNode.textContent;
441
- const info = cleaned.get(textNode);
608
+ const info = cleanedNodes.get(textNode);
442
609
  const settled = stripText ? info?.cleaned : info?.original;
443
610
  if (info && textContent === settled) {
444
611
  if (!info.decoded) return false;
445
612
  const shown = resolveEditTarget(textNode);
446
- if (!shown) return false;
613
+ if (!shown) {
614
+ pendingHidden.add(textNode);
615
+ return false;
616
+ }
447
617
  pendingHidden.delete(textNode);
448
618
  if (shown.hasAttribute("data-prepr-encoded")) return false;
449
- tagEncodedElement(shown, info.decoded);
450
- return true;
619
+ return tagEncodedElement(shown, info.decoded);
451
620
  }
452
621
  try {
453
622
  const { cleaned: stripped, encoded } = vercelStegaSplit(textContent);
454
623
  if (!encoded || stripped === textContent) return false;
455
624
  const decoded = decodeStega(textContent);
456
625
  if (!decoded?.href && !stripText) return false;
457
- cleaned.set(textNode, {
626
+ cleanedNodes.set(textNode, {
458
627
  original: textContent,
459
628
  cleaned: stripped,
460
629
  decoded: decoded?.href ? decoded : null
@@ -464,8 +633,7 @@ function createStegaAutoClean(options) {
464
633
  const target = resolveEditTarget(textNode);
465
634
  if (target) {
466
635
  pendingHidden.delete(textNode);
467
- tagEncodedElement(target, decoded);
468
- return true;
636
+ return tagEncodedElement(target, decoded);
469
637
  }
470
638
  pendingHidden.add(textNode);
471
639
  } catch (error) {
@@ -496,7 +664,7 @@ function createStegaAutoClean(options) {
496
664
  }
497
665
  if (mutation.type === "characterData" && mutation.target.nodeType === Node.TEXT_NODE) {
498
666
  const textNode = mutation.target;
499
- const info = cleaned.get(textNode);
667
+ const info = cleanedNodes.get(textNode);
500
668
  const text = textNode.textContent ?? "";
501
669
  const settled = stripText ? info?.cleaned : info?.original;
502
670
  if (text !== settled && vercelStegaSplit(text).encoded) {
@@ -539,7 +707,16 @@ function createStegaAutoClean(options) {
539
707
  retryPendingHidden();
540
708
  }, 50);
541
709
  }
710
+ let resizeTimeout = null;
711
+ function handleResize() {
712
+ if (pendingHidden.size === 0 || resizeTimeout) return;
713
+ resizeTimeout = setTimeout(() => {
714
+ resizeTimeout = null;
715
+ retryPendingHidden();
716
+ }, 100);
717
+ }
542
718
  function setupObserver() {
719
+ window.addEventListener("resize", handleResize, { passive: true });
543
720
  observer = new MutationObserver(handleMutations);
544
721
  observer.observe(document.body, {
545
722
  childList: true,
@@ -562,11 +739,15 @@ function createStegaAutoClean(options) {
562
739
  clearTimeout(debounceTimeout);
563
740
  debounceTimeout = null;
564
741
  }
742
+ if (resizeTimeout) {
743
+ clearTimeout(resizeTimeout);
744
+ resizeTimeout = null;
745
+ }
565
746
  if (observer) {
566
747
  observer.disconnect();
567
748
  observer = null;
749
+ window.removeEventListener("resize", handleResize);
568
750
  }
569
- cleaned = /* @__PURE__ */ new WeakMap();
570
751
  pendingHidden = /* @__PURE__ */ new Set();
571
752
  pendingDirty = /* @__PURE__ */ new Set();
572
753
  debug3.log("stega auto-clean stopped");
@@ -574,6 +755,439 @@ function createStegaAutoClean(options) {
574
755
  return { start, stop };
575
756
  }
576
757
 
758
+ // src/core/visual-editor/overlay.ts
759
+ var HOST_TAG = "prepr-visual-editor";
760
+ var SCROLL_SETTLE_MS = 120;
761
+ var LABEL_SPACE_PX = 22;
762
+ var BOX_PADDING_PX = 3;
763
+ var LABEL_MARGIN_PX = 4;
764
+ var PROBE_POINTS = [
765
+ [0.5, 0.5],
766
+ [0.15, 0.2],
767
+ [0.85, 0.2],
768
+ [0.15, 0.8],
769
+ [0.85, 0.8]
770
+ ];
771
+ function overlapsBox(area, rect) {
772
+ 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;
773
+ }
774
+ function overlaps(a2, b2) {
775
+ return a2.left < b2.right && b2.left < a2.right && a2.top < b2.bottom && b2.top < a2.bottom;
776
+ }
777
+ function coverOf(hit, target, x2, y2) {
778
+ let cover = hit.getBoundingClientRect();
779
+ for (let el = hit.parentElement; el && !el.contains(target) && el !== document.body; el = el.parentElement) {
780
+ const r2 = el.getBoundingClientRect();
781
+ if (x2 < r2.left || x2 >= r2.right || y2 < r2.top || y2 >= r2.bottom) break;
782
+ cover = r2;
783
+ }
784
+ return cover;
785
+ }
786
+ function mergeAreas(areas) {
787
+ const merged = areas.slice();
788
+ for (let i2 = 0; i2 < merged.length; i2++) {
789
+ for (let j2 = i2 + 1; j2 < merged.length; j2++) {
790
+ if (!overlaps(merged[i2], merged[j2])) continue;
791
+ merged[i2] = {
792
+ left: Math.min(merged[i2].left, merged[j2].left),
793
+ top: Math.min(merged[i2].top, merged[j2].top),
794
+ right: Math.max(merged[i2].right, merged[j2].right),
795
+ bottom: Math.max(merged[i2].bottom, merged[j2].bottom)
796
+ };
797
+ merged.splice(j2, 1);
798
+ j2 = i2;
799
+ }
800
+ }
801
+ return merged;
802
+ }
803
+ function findCovers(target, rect) {
804
+ const width = window.innerWidth;
805
+ const height = window.innerHeight;
806
+ const covers = [];
807
+ const seen = /* @__PURE__ */ new Set();
808
+ let probed = false;
809
+ let reached = false;
810
+ for (const [fx, fy] of PROBE_POINTS) {
811
+ const x2 = rect.left + rect.width * fx;
812
+ const y2 = rect.top + rect.height * fy;
813
+ if (x2 < 0 || y2 < 0 || x2 >= width || y2 >= height) continue;
814
+ probed = true;
815
+ const hit = document.elementFromPoint(x2, y2);
816
+ if (!hit || hit === target || target.contains(hit)) {
817
+ reached = true;
818
+ continue;
819
+ }
820
+ if (seen.has(hit)) continue;
821
+ seen.add(hit);
822
+ if (covers.some(
823
+ (c2) => x2 >= c2.left && x2 < c2.right && y2 >= c2.top && y2 < c2.bottom
824
+ )) {
825
+ continue;
826
+ }
827
+ covers.push(coverOf(hit, target, x2, y2));
828
+ }
829
+ if (!probed) return [];
830
+ return reached ? mergeAreas(covers) : null;
831
+ }
832
+ function clipPath(x2, y2, covers) {
833
+ const far = 1e5;
834
+ const points = [
835
+ `${-far}px ${-far}px`,
836
+ `${far}px ${-far}px`,
837
+ `${far}px ${far}px`,
838
+ `${-far}px ${far}px`,
839
+ `${-far}px ${-far}px`
840
+ ];
841
+ for (const c2 of covers) {
842
+ const l2 = c2.left - x2;
843
+ const t3 = c2.top - y2;
844
+ const r2 = c2.right - x2;
845
+ const b2 = c2.bottom - y2;
846
+ points.push(
847
+ `${l2}px ${t3}px`,
848
+ `${r2}px ${t3}px`,
849
+ `${r2}px ${b2}px`,
850
+ `${l2}px ${b2}px`,
851
+ `${l2}px ${t3}px`,
852
+ `${-far}px ${-far}px`
853
+ );
854
+ }
855
+ return `polygon(evenodd, ${points.join(", ")})`;
856
+ }
857
+ var STYLES = `
858
+ :host {
859
+ all: initial;
860
+ position: fixed;
861
+ inset: 0;
862
+ pointer-events: none;
863
+ z-index: 2147483646;
864
+ contain: strict;
865
+ }
866
+ .box {
867
+ position: absolute;
868
+ top: 0;
869
+ left: 0;
870
+ box-sizing: border-box;
871
+ border-radius: 6px;
872
+ opacity: 0;
873
+ will-change: transform, width, height;
874
+ transition:
875
+ transform 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
876
+ width 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
877
+ height 180ms cubic-bezier(0.2, 0.8, 0.2, 1),
878
+ opacity 140ms ease;
879
+ }
880
+ .box[data-visible] { opacity: 1; }
881
+ .box[data-instant], :host([data-scrolling]) .box {
882
+ transition: opacity 140ms ease;
883
+ }
884
+ .hover {
885
+ border: 1.5px solid rgba(79, 70, 229, 0.9);
886
+ background: rgba(79, 70, 229, 0.06);
887
+ }
888
+ .selected {
889
+ border: 2px solid #4f46e5;
890
+ box-shadow: 0 0 0 4px rgba(79, 70, 229, 0.18);
891
+ }
892
+ .label {
893
+ position: absolute;
894
+ left: -2px;
895
+ bottom: 100%;
896
+ margin-bottom: 4px;
897
+ padding: 3px 7px;
898
+ border-radius: 5px;
899
+ background: #4f46e5;
900
+ color: #fff;
901
+ font: 500 11px/1.3 ui-sans-serif, system-ui, -apple-system, 'Segoe UI', sans-serif;
902
+ letter-spacing: 0.01em;
903
+ white-space: nowrap;
904
+ display: flex;
905
+ max-width: 240px;
906
+ box-shadow: 0 1px 3px rgba(15, 23, 42, 0.25);
907
+ }
908
+ .text {
909
+ min-width: 0;
910
+ overflow: hidden;
911
+ text-overflow: ellipsis;
912
+ }
913
+ .hover .label { background: rgba(79, 70, 229, 0.92); }
914
+ /* Content of another item than the one open in the editor: still clickable,
915
+ but visibly not part of the item being edited. */
916
+ .hover[data-item-kind='other'] {
917
+ border: 1.5px dashed #a5b4fc;
918
+ background: rgba(100, 116, 139, 0.05);
919
+ }
920
+ .hover[data-item-kind='other'] .label { background: #64748b; }
921
+ .hover[data-item-kind='referenced'] {
922
+ border: 1.5px solid #a5b4fc;
923
+ background: rgba(79, 70, 229, 0.03);
924
+ }
925
+ .hover[data-item-kind='referenced'] .label { background: #818cf8; }
926
+ .box[data-label-covered] .label { visibility: hidden; }
927
+ .box[data-label-inside] .label {
928
+ bottom: auto;
929
+ top: 2px;
930
+ left: 2px;
931
+ margin: 0;
932
+ }
933
+ /* A clickable hover label (edit mode on the live site). Only while shown, so
934
+ a fading box never leaves an invisible click target behind. */
935
+ :host([data-label-action]) .hover[data-visible] .label {
936
+ pointer-events: auto;
937
+ cursor: pointer;
938
+ }
939
+ :host([data-label-action]) .hover .label::after {
940
+ content: '\\2197';
941
+ margin-left: 5px;
942
+ }
943
+ :host([data-label-action]) .hover .label:hover { background: #4338ca; }
944
+ /* Bridges the gap between the label and its element, so the pointer can
945
+ travel from one to the other without the hover ending on the way. */
946
+ :host([data-label-action]) .hover .label::before {
947
+ content: '';
948
+ position: absolute;
949
+ left: 0;
950
+ right: 0;
951
+ top: 100%;
952
+ height: ${LABEL_MARGIN_PX + BOX_PADDING_PX + 2}px;
953
+ }
954
+ @media (prefers-reduced-motion: reduce) {
955
+ .box { transition: opacity 80ms linear; }
956
+ }
957
+ `;
958
+ var VisualEditorOverlay = class {
959
+ constructor(options = {}) {
960
+ this.host = null;
961
+ this.hoverBox = null;
962
+ this.selectedBox = null;
963
+ this.frame = null;
964
+ this.scrollTimeout = null;
965
+ this.resizeObserver = null;
966
+ this.onLabelClick = options.onLabelClick;
967
+ }
968
+ mount() {
969
+ if (this.host) return;
970
+ const host = document.createElement(HOST_TAG);
971
+ const shadow = host.attachShadow({ mode: "open" });
972
+ const style = document.createElement("style");
973
+ style.textContent = STYLES;
974
+ shadow.appendChild(style);
975
+ this.hoverBox = this.createBox(shadow, "hover");
976
+ this.selectedBox = this.createBox(shadow, "selected");
977
+ const onLabelClick = this.onLabelClick;
978
+ if (onLabelClick) {
979
+ host.setAttribute("data-label-action", "");
980
+ const hoverBox = this.hoverBox;
981
+ hoverBox.label.addEventListener("click", () => {
982
+ if (hoverBox.target) onLabelClick(hoverBox.target);
983
+ });
984
+ }
985
+ document.documentElement.appendChild(host);
986
+ this.host = host;
987
+ if (typeof ResizeObserver !== "undefined") {
988
+ this.resizeObserver = new ResizeObserver(() => this.schedule());
989
+ }
990
+ }
991
+ unmount() {
992
+ if (this.frame !== null) cancelAnimationFrame(this.frame);
993
+ if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
994
+ this.frame = null;
995
+ this.scrollTimeout = null;
996
+ this.resizeObserver?.disconnect();
997
+ this.resizeObserver = null;
998
+ this.host?.remove();
999
+ this.host = null;
1000
+ this.hoverBox = null;
1001
+ this.selectedBox = null;
1002
+ }
1003
+ /**
1004
+ * Whether a pointer event target is this chrome: events from inside the
1005
+ * shadow root reach the page retargeted to its host.
1006
+ */
1007
+ owns(node) {
1008
+ return node !== null && node === this.host;
1009
+ }
1010
+ getHoverTarget() {
1011
+ return this.hoverBox?.target ?? null;
1012
+ }
1013
+ getSelectedTarget() {
1014
+ return this.selectedBox?.target ?? null;
1015
+ }
1016
+ setHover(target, label, options = {}) {
1017
+ if (target) {
1018
+ this.hoverBox?.root.setAttribute(
1019
+ "data-item-kind",
1020
+ options.kind ?? "current"
1021
+ );
1022
+ }
1023
+ this.retarget(this.hoverBox, target, label);
1024
+ }
1025
+ setSelected(target, label) {
1026
+ this.retarget(this.selectedBox, target, label);
1027
+ }
1028
+ /** Call on every scroll; positions follow in the next frame, untransitioned. */
1029
+ onScroll() {
1030
+ if (!this.host) return;
1031
+ this.host.setAttribute("data-scrolling", "");
1032
+ if (this.scrollTimeout) clearTimeout(this.scrollTimeout);
1033
+ this.scrollTimeout = setTimeout(() => {
1034
+ this.host?.removeAttribute("data-scrolling");
1035
+ this.scrollTimeout = null;
1036
+ }, SCROLL_SETTLE_MS);
1037
+ this.schedule();
1038
+ }
1039
+ /** Re-measure both boxes in the next animation frame. */
1040
+ schedule() {
1041
+ if (this.frame !== null || !this.host) return;
1042
+ this.frame = requestAnimationFrame(() => {
1043
+ this.frame = null;
1044
+ this.render();
1045
+ });
1046
+ }
1047
+ createBox(shadow, kind) {
1048
+ const root = document.createElement("div");
1049
+ root.className = `box ${kind}`;
1050
+ const label = document.createElement("div");
1051
+ label.className = "label";
1052
+ const text = document.createElement("span");
1053
+ text.className = "text";
1054
+ label.appendChild(text);
1055
+ root.appendChild(label);
1056
+ shadow.appendChild(root);
1057
+ return { root, label, text, target: null };
1058
+ }
1059
+ retarget(box, target, label) {
1060
+ if (!box || box.target === target) return;
1061
+ const previous = box.target;
1062
+ if (previous) this.resizeObserver?.unobserve(previous);
1063
+ box.target = target;
1064
+ if (target) {
1065
+ this.resizeObserver?.observe(target);
1066
+ box.text.textContent = label;
1067
+ if (!box.root.hasAttribute("data-visible")) {
1068
+ box.root.setAttribute("data-instant", "");
1069
+ }
1070
+ }
1071
+ this.schedule();
1072
+ }
1073
+ render() {
1074
+ const hover = this.hoverBox ? this.measure(this.hoverBox) : null;
1075
+ const selected = this.selectedBox ? this.measure(this.selectedBox) : null;
1076
+ if (this.hoverBox) this.paint(this.hoverBox, hover, selected);
1077
+ if (this.selectedBox) this.paint(this.selectedBox, selected, hover);
1078
+ }
1079
+ measure(box) {
1080
+ const target = box.target;
1081
+ if (!target) return null;
1082
+ if (!target.isConnected) {
1083
+ this.resizeObserver?.unobserve(target);
1084
+ box.target = null;
1085
+ return null;
1086
+ }
1087
+ const rect = target.getBoundingClientRect();
1088
+ if (rect.width === 0 && rect.height === 0) return null;
1089
+ const covers = findCovers(target, rect);
1090
+ if (!covers) return null;
1091
+ return {
1092
+ rect,
1093
+ covers,
1094
+ labelWidth: covers.length ? box.label.offsetWidth : 0,
1095
+ labelHeight: covers.length ? box.label.offsetHeight : 0
1096
+ };
1097
+ }
1098
+ paint(box, measurement, other = null) {
1099
+ const root = box.root;
1100
+ if (!measurement) {
1101
+ root.removeAttribute("data-visible");
1102
+ return;
1103
+ }
1104
+ const { rect, covers } = measurement;
1105
+ const x2 = rect.left - BOX_PADDING_PX;
1106
+ const y2 = rect.top - BOX_PADDING_PX;
1107
+ const labelAbove = {
1108
+ left: x2 - 2,
1109
+ top: y2 - LABEL_SPACE_PX,
1110
+ right: x2 - 2 + (measurement.labelWidth || Math.min(240, rect.width)),
1111
+ bottom: y2
1112
+ };
1113
+ const labelInside = y2 < LABEL_SPACE_PX || other !== null && other.rect !== rect && overlapsBox(labelAbove, other.rect);
1114
+ root.style.transform = `translate3d(${x2}px, ${y2}px, 0)`;
1115
+ root.style.width = `${rect.width + BOX_PADDING_PX * 2}px`;
1116
+ root.style.height = `${rect.height + BOX_PADDING_PX * 2}px`;
1117
+ root.toggleAttribute("data-label-inside", labelInside);
1118
+ root.style.clipPath = covers.length ? clipPath(x2, y2, covers) : "";
1119
+ const labelLeft = labelInside ? x2 + 2 : x2 - 2;
1120
+ const labelTop = labelInside ? y2 + 2 : y2 - LABEL_MARGIN_PX - measurement.labelHeight;
1121
+ const label = {
1122
+ left: labelLeft,
1123
+ top: labelTop,
1124
+ right: labelLeft + measurement.labelWidth,
1125
+ bottom: labelTop + measurement.labelHeight
1126
+ };
1127
+ root.toggleAttribute(
1128
+ "data-label-covered",
1129
+ covers.some((c2) => overlaps(c2, label))
1130
+ );
1131
+ if (root.hasAttribute("data-instant")) {
1132
+ root.setAttribute("data-visible", "");
1133
+ requestAnimationFrame(() => root.removeAttribute("data-instant"));
1134
+ return;
1135
+ }
1136
+ root.setAttribute("data-visible", "");
1137
+ }
1138
+ };
1139
+
1140
+ // src/core/visual-editor/field-path.ts
1141
+ var INDEX_SEGMENT = /^\d+$/;
1142
+ var WORD_SEPARATORS = /[_-]+/g;
1143
+ function normalizeFieldPath(path) {
1144
+ return path.split(".").map(
1145
+ (segment) => INDEX_SEGMENT.test(segment) ? String(Number.parseInt(segment, 10)) : segment
1146
+ ).join(".");
1147
+ }
1148
+ var GENERIC_LEAVES = /* @__PURE__ */ new Set(["value", "text", "body", "content", "label"]);
1149
+ function humanize(name) {
1150
+ const words = name.replace(WORD_SEPARATORS, " ").trim();
1151
+ return words.charAt(0).toUpperCase() + words.slice(1);
1152
+ }
1153
+ function fieldLabel(path, locale = "en") {
1154
+ if (!path) return t("visualEditor.edit", locale);
1155
+ const segments = path.split(".").filter((s2) => s2 && s2 !== "items" && !INDEX_SEGMENT.test(s2));
1156
+ const name = segments[segments.length - 1];
1157
+ if (!name) return t("visualEditor.edit", locale);
1158
+ const parent = segments[segments.length - 2];
1159
+ if (parent && GENERIC_LEAVES.has(name)) {
1160
+ return `${humanize(parent)} \u203A ${humanize(name)}`;
1161
+ }
1162
+ return humanize(name);
1163
+ }
1164
+ function findFieldElement(lookup) {
1165
+ if (!lookup.field) return null;
1166
+ const candidates = document.querySelectorAll(
1167
+ "[data-prepr-encoded]"
1168
+ );
1169
+ if (lookup.entryId) {
1170
+ for (const candidate of candidates) {
1171
+ if (candidate.getAttribute("data-prepr-entry-id") === lookup.entryId) {
1172
+ return candidate;
1173
+ }
1174
+ }
1175
+ }
1176
+ for (const path of [lookup.field, lookup.fieldById]) {
1177
+ if (!path) continue;
1178
+ const wanted = normalizeFieldPath(path);
1179
+ for (const candidate of candidates) {
1180
+ const field = candidate.getAttribute("data-prepr-field");
1181
+ if (!field || normalizeFieldPath(field) !== wanted) continue;
1182
+ if (lookup.id && candidate.getAttribute("data-prepr-id") !== lookup.id) {
1183
+ continue;
1184
+ }
1185
+ return candidate;
1186
+ }
1187
+ }
1188
+ return null;
1189
+ }
1190
+
577
1191
  // src/core/stega/elements.ts
578
1192
  var debug4 = createScopedLogger("stega:elements");
579
1193
  var StegaElements = class {
@@ -663,235 +1277,22 @@ var StegaElements = class {
663
1277
  attributeFilter: ["class", "style", "hidden", "open", "aria-expanded"]
664
1278
  });
665
1279
  }
666
- /** Drops the active class but keeps the data attributes, unlike cleanup(). */
667
- cleanupVisuals() {
668
- document.querySelectorAll(".prepr-overlay-active").forEach((element) => element.classList.remove("prepr-overlay-active"));
669
- }
670
- cleanup() {
1280
+ /**
1281
+ * Stops watching for new content. Tags stay on their elements: the page's
1282
+ * stega characters are stripped once they are read, so a tag removed here
1283
+ * could never be rebuilt when edit mode is turned back on.
1284
+ */
1285
+ disconnect() {
671
1286
  if (this.observer) {
672
1287
  this.observer.disconnect();
673
1288
  this.observer = null;
674
1289
  }
675
- document.querySelectorAll("[data-prepr-encoded]").forEach((element) => {
676
- element.removeAttribute("data-prepr-encoded");
677
- element.removeAttribute("data-prepr-href");
678
- element.removeAttribute("data-prepr-origin");
679
- element.removeAttribute("data-prepr-id");
680
- element.removeAttribute("data-prepr-field");
681
- element.removeAttribute("data-prepr-locale");
682
- element.classList.remove("prepr-overlay-active");
683
- });
684
1290
  this.elements = void 0;
685
1291
  }
686
1292
  };
687
1293
 
688
- // src/core/stega/overlay.ts
689
- var debug5 = createScopedLogger("stega:overlay");
690
- var StegaOverlay = class {
691
- constructor(onEdit) {
692
- this.overlay = null;
693
- this.tooltip = null;
694
- this.hideTimeout = null;
695
- this.currentElement = null;
696
- this.onEdit = onEdit;
697
- }
698
- create() {
699
- const overlay = document.createElement("div");
700
- overlay.className = "prepr-overlay";
701
- overlay.style.display = "none";
702
- const tooltip = document.createElement("div");
703
- tooltip.className = "prepr-tooltip";
704
- tooltip.style.display = "none";
705
- document.body.appendChild(overlay);
706
- document.body.appendChild(tooltip);
707
- this.overlay = overlay;
708
- this.tooltip = tooltip;
709
- return { overlay, tooltip };
710
- }
711
- getTooltip() {
712
- return this.tooltip;
713
- }
714
- hasCurrentElement() {
715
- return this.currentElement !== null;
716
- }
717
- clearHideTimeout() {
718
- if (this.hideTimeout) {
719
- clearTimeout(this.hideTimeout);
720
- this.hideTimeout = null;
721
- }
722
- }
723
- show(element) {
724
- if (!this.overlay || !this.tooltip) return;
725
- this.clearHideTimeout();
726
- if (this.currentElement && this.currentElement !== element) {
727
- this.currentElement.classList.remove("prepr-overlay-active");
728
- }
729
- const rect = element.getBoundingClientRect();
730
- const href = element.getAttribute("data-prepr-href");
731
- const origin = element.getAttribute("data-prepr-origin");
732
- const id = element.getAttribute("data-prepr-id") ?? void 0;
733
- const field = element.getAttribute("data-prepr-field") ?? void 0;
734
- const locale = element.getAttribute("data-prepr-locale") ?? void 0;
735
- debug5.log("showing overlay", { href, origin });
736
- const overlay = this.overlay;
737
- overlay.style.display = "block";
738
- overlay.style.top = `${rect.top + window.scrollY - 2}px`;
739
- overlay.style.left = `${rect.left + window.scrollX - 4}px`;
740
- overlay.style.width = `${rect.width + 8}px`;
741
- overlay.style.height = `${rect.height + 4}px`;
742
- const tooltip = this.tooltip;
743
- if (href && origin) {
744
- const MIN_WIDTH_FOR_TEXT = 80;
745
- const isCompact = rect.width < MIN_WIDTH_FOR_TEXT;
746
- tooltip.textContent = isCompact ? "\u2197" : `${origin} \u2197`;
747
- tooltip.style.display = "block";
748
- tooltip.style.minWidth = isCompact ? "auto" : "80px";
749
- requestAnimationFrame(() => {
750
- let top = rect.top + window.scrollY - tooltip.clientHeight - 2;
751
- let left = rect.right + 4 - tooltip.clientWidth;
752
- const minTop = window.scrollY + 4;
753
- const maxTop = window.scrollY + window.innerHeight - tooltip.clientHeight - 4;
754
- const minLeft = window.scrollX + 4;
755
- const maxLeft = window.scrollX + window.innerWidth - tooltip.clientWidth - 4;
756
- const below = top < minTop;
757
- if (below) {
758
- top = rect.bottom + window.scrollY + 2;
759
- }
760
- top = Math.max(minTop, Math.min(top, maxTop));
761
- left = Math.max(minLeft, Math.min(left, maxLeft));
762
- tooltip.dataset.preprPlacement = below ? "below" : "above";
763
- tooltip.style.top = `${top}px`;
764
- tooltip.style.left = `${left}px`;
765
- });
766
- tooltip.onclick = () => {
767
- this.onEdit({ href, origin, id, field, locale });
768
- };
769
- }
770
- this.currentElement = element;
771
- element.classList.add("prepr-overlay-active");
772
- }
773
- hideImmediate() {
774
- this.clearHideTimeout();
775
- if (this.overlay) this.overlay.style.display = "none";
776
- if (this.tooltip) this.tooltip.style.display = "none";
777
- if (this.currentElement) {
778
- this.currentElement.classList.remove("prepr-overlay-active");
779
- this.currentElement = null;
780
- }
781
- }
782
- hide() {
783
- if (!this.overlay || !this.tooltip) return;
784
- this.clearHideTimeout();
785
- this.hideTimeout = setTimeout(() => this.hideImmediate(), 100);
786
- }
787
- cleanup() {
788
- this.clearHideTimeout();
789
- if (this.overlay) {
790
- this.overlay.style.display = "none";
791
- this.overlay.parentNode?.removeChild(this.overlay);
792
- this.overlay = null;
793
- }
794
- if (this.tooltip) {
795
- this.tooltip.style.display = "none";
796
- this.tooltip.parentNode?.removeChild(this.tooltip);
797
- this.tooltip = null;
798
- }
799
- if (this.currentElement) {
800
- this.currentElement.classList.remove("prepr-overlay-active");
801
- this.currentElement = null;
802
- }
803
- }
804
- };
805
-
806
- // src/core/stega/proximity.ts
807
- var debug6 = createScopedLogger("stega:proximity");
808
- var StegaProximity = class {
809
- constructor() {
810
- this.highlighted = /* @__PURE__ */ new Set();
811
- this.visible = /* @__PURE__ */ new Set();
812
- this.observer = null;
813
- this.getEncodedElements = createElementCache(
814
- "[data-prepr-encoded]",
815
- 200
816
- );
817
- }
818
- /** Call after the encoded element set changes — rebuilds the observer. */
819
- refreshObserving() {
820
- try {
821
- if (typeof IntersectionObserver === "undefined") return;
822
- if (this.observer) {
823
- this.observer.disconnect();
824
- this.observer = null;
825
- }
826
- this.visible = /* @__PURE__ */ new Set();
827
- this.observer = new IntersectionObserver(
828
- (entries) => {
829
- entries.forEach((entry) => {
830
- const el = entry.target;
831
- if (entry.isIntersecting) {
832
- this.visible.add(el);
833
- } else {
834
- this.visible.delete(el);
835
- }
836
- });
837
- },
838
- { root: null, rootMargin: "0px", threshold: 0 }
839
- );
840
- const nodes = this.getEncodedElements();
841
- nodes.forEach((el) => this.observer.observe(el));
842
- debug6.log("observing", nodes.length, "encoded elements");
843
- } catch (error) {
844
- debug6.log("error setting up IntersectionObserver", error);
845
- }
846
- }
847
- stopObserving() {
848
- if (this.observer) {
849
- this.observer.disconnect();
850
- this.observer = null;
851
- this.visible.clear();
852
- }
853
- }
854
- updateElementGradients(cursorX, cursorY) {
855
- const candidates = this.visible.size > 0 ? Array.from(this.visible) : Array.from(this.getEncodedElements());
856
- const newHighlighted = /* @__PURE__ */ new Set();
857
- candidates.forEach((element) => {
858
- const rect = element.getBoundingClientRect();
859
- const distance = Math.min(
860
- Math.abs(cursorX - rect.left),
861
- Math.abs(cursorX - rect.right),
862
- Math.abs(cursorY - rect.top),
863
- Math.abs(cursorY - rect.bottom)
864
- );
865
- if (distance < 150) {
866
- const relativeX = cursorX - rect.left;
867
- const relativeY = cursorY - rect.top;
868
- element.style.setProperty("--cursor-x", `${relativeX}px`);
869
- element.style.setProperty("--cursor-y", `${relativeY}px`);
870
- const baseGradientSize = Math.max(
871
- 150,
872
- Math.max(rect.width, rect.height) * 1.1
873
- );
874
- const distanceScale = Math.max(0, (400 - distance) / 400);
875
- const gradientSize = baseGradientSize * distanceScale;
876
- element.style.setProperty("--gradient-size", `${gradientSize}px`);
877
- element.classList.add("prepr-proximity-highlight");
878
- newHighlighted.add(element);
879
- } else {
880
- element.classList.remove("prepr-proximity-highlight");
881
- }
882
- });
883
- this.highlighted = newHighlighted;
884
- }
885
- clearAllHighlights() {
886
- this.highlighted.forEach(
887
- (element) => element.classList.remove("prepr-proximity-highlight")
888
- );
889
- this.highlighted.clear();
890
- }
891
- };
892
-
893
1294
  // src/core/stega/stega.generated.ts
894
- 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}';
1295
+ 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}';
895
1296
 
896
1297
  // src/core/stega/styles.ts
897
1298
  var STEGA_STYLE_MARKER = "data-prepr-stega";
@@ -911,46 +1312,80 @@ function removeStegaStyles() {
911
1312
  }
912
1313
 
913
1314
  // src/core/stega/scan.ts
914
- var debug7 = createScopedLogger("stega:scan");
1315
+ var debug5 = createScopedLogger("stega:scan");
1316
+ var ENCODED_SELECTOR = "[data-prepr-encoded]";
1317
+ var PRESENCE_FADE_MS = 400;
915
1318
  var EDITOR_MODE_STYLES = "[data-prepr-encoded]{cursor:pointer}";
1319
+ function fadeInPresence() {
1320
+ const root = document.documentElement;
1321
+ if (typeof root.animate !== "function") return;
1322
+ if (window.matchMedia?.("(prefers-reduced-motion: reduce)").matches) return;
1323
+ root.animate(
1324
+ { "--prepr-presence": ["0", "1"] },
1325
+ { duration: PRESENCE_FADE_MS, easing: "ease-out" }
1326
+ );
1327
+ }
916
1328
  function createStegaController(opts) {
917
1329
  const tooltipEnabled = opts.tooltip ?? true;
918
- const overlay = new StegaOverlay(opts.onEdit);
919
- const proximity = new StegaProximity();
1330
+ const locale = opts.locale ?? "en";
920
1331
  const elements = new StegaElements();
1332
+ const overlay = new VisualEditorOverlay({
1333
+ onLabelClick: (target) => opts.onEdit(readEditPayload(target))
1334
+ });
921
1335
  let initialized = false;
922
- let throttledMouseMove = null;
923
- const handleTooltipMouseEnter = () => {
924
- overlay.clearHideTimeout();
1336
+ let pointerFrame = null;
1337
+ let pointerTarget = null;
1338
+ let pointerX = 0;
1339
+ let pointerY = 0;
1340
+ let hitTestPending = false;
1341
+ const updateHover = () => {
1342
+ pointerFrame = null;
1343
+ if (hitTestPending) {
1344
+ hitTestPending = false;
1345
+ pointerTarget = document.elementFromPoint(pointerX, pointerY);
1346
+ }
1347
+ if (overlay.owns(pointerTarget)) return;
1348
+ const encoded = pointerTarget instanceof Element ? pointerTarget.closest(ENCODED_SELECTOR) : null;
1349
+ overlay.setHover(
1350
+ encoded,
1351
+ encoded ? fieldLabel(encoded.getAttribute("data-prepr-field"), locale) : ""
1352
+ );
925
1353
  };
926
- const handleTooltipMouseLeave = () => {
927
- if (!overlay.hasCurrentElement()) {
928
- overlay.hide();
1354
+ const scheduleHover = () => {
1355
+ if (pointerFrame === null) {
1356
+ pointerFrame = requestAnimationFrame(updateHover);
929
1357
  }
930
1358
  };
1359
+ const handlePointer = (e2) => {
1360
+ pointerTarget = e2.target;
1361
+ pointerX = e2.clientX;
1362
+ pointerY = e2.clientY;
1363
+ hitTestPending = false;
1364
+ scheduleHover();
1365
+ };
1366
+ const handlePointerLeave = () => {
1367
+ pointerTarget = null;
1368
+ hitTestPending = false;
1369
+ overlay.setHover(null, "");
1370
+ };
931
1371
  const handleScroll = () => {
932
- overlay.hideImmediate();
933
- proximity.clearAllHighlights();
1372
+ overlay.onScroll();
1373
+ if (pointerTarget === null) return;
1374
+ hitTestPending = true;
1375
+ scheduleHover();
934
1376
  };
1377
+ const handleResize = () => overlay.schedule();
935
1378
  const handleClick = (e2) => {
936
1379
  const target = e2.target;
937
- const encoded = target?.closest(
938
- "[data-prepr-encoded]"
939
- );
1380
+ const encoded = target?.closest(ENCODED_SELECTOR);
940
1381
  if (!encoded) return;
941
1382
  e2.preventDefault();
942
1383
  e2.stopPropagation();
943
- opts.onEdit({
944
- href: encoded.getAttribute("data-prepr-href") ?? "",
945
- origin: encoded.getAttribute("data-prepr-origin") ?? "",
946
- id: encoded.getAttribute("data-prepr-id") ?? void 0,
947
- field: encoded.getAttribute("data-prepr-field") ?? void 0,
948
- locale: encoded.getAttribute("data-prepr-locale") ?? void 0
949
- });
1384
+ opts.onEdit(readEditPayload(encoded));
950
1385
  };
951
1386
  const start = () => {
952
1387
  if (initialized) {
953
- debug7.log("already initialized, skipping setup");
1388
+ debug5.log("already initialized, skipping setup");
954
1389
  return;
955
1390
  }
956
1391
  if (!tooltipEnabled) {
@@ -960,73 +1395,64 @@ function createStegaController(opts) {
960
1395
  });
961
1396
  document.addEventListener("click", handleClick, { capture: true });
962
1397
  initialized = true;
963
- debug7.log("stega controller started (editor mode)");
1398
+ debug5.log("stega controller started (headless)");
964
1399
  return;
965
1400
  }
966
1401
  injectStegaStyles();
967
- const { tooltip } = overlay.create();
968
- tooltip.addEventListener("mouseenter", handleTooltipMouseEnter);
969
- tooltip.addEventListener("mouseleave", handleTooltipMouseLeave);
1402
+ fadeInPresence();
1403
+ overlay.mount();
970
1404
  elements.scanDocument(true);
971
- proximity.refreshObserving();
972
- elements.setupMutationObserver(() => proximity.refreshObserving());
973
- throttledMouseMove = throttle((e2) => {
974
- const mouseEvent = e2;
975
- const target = mouseEvent.target;
976
- if (!target) return;
977
- if (target.closest(".prepr-tooltip")) return;
978
- proximity.updateElementGradients(mouseEvent.clientX, mouseEvent.clientY);
979
- const encoded = target.closest("[data-prepr-encoded]");
980
- if (encoded) {
981
- overlay.show(encoded);
982
- } else {
983
- overlay.hide();
984
- }
985
- }, 16);
986
- document.addEventListener("mousemove", throttledMouseMove, {
987
- passive: true
1405
+ elements.setupMutationObserver(() => {
1406
+ if (pointerTarget === null) return;
1407
+ hitTestPending = true;
1408
+ scheduleHover();
988
1409
  });
1410
+ document.addEventListener("pointermove", handlePointer, { passive: true });
1411
+ document.addEventListener("pointerdown", handlePointer, { passive: true });
1412
+ document.documentElement.addEventListener(
1413
+ "pointerleave",
1414
+ handlePointerLeave
1415
+ );
989
1416
  window.addEventListener("scroll", handleScroll, {
990
1417
  capture: true,
991
1418
  passive: true
992
1419
  });
1420
+ window.addEventListener("resize", handleResize, { passive: true });
993
1421
  initialized = true;
994
- debug7.log("stega controller started");
1422
+ debug5.log("stega controller started");
995
1423
  };
996
1424
  const stop = () => {
997
1425
  if (!initialized) return;
1426
+ initialized = false;
998
1427
  if (!tooltipEnabled) {
999
1428
  document.removeEventListener("click", handleClick, { capture: true });
1000
- elements.cleanup();
1429
+ elements.disconnect();
1001
1430
  removeStegaStyles();
1002
- initialized = false;
1003
- debug7.log("stega controller stopped (editor mode)");
1431
+ debug5.log("stega controller stopped (headless)");
1004
1432
  return;
1005
1433
  }
1006
- if (throttledMouseMove) {
1007
- document.removeEventListener("mousemove", throttledMouseMove);
1008
- throttledMouseMove.cancel();
1009
- throttledMouseMove = null;
1010
- }
1434
+ if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
1435
+ pointerFrame = null;
1436
+ pointerTarget = null;
1437
+ hitTestPending = false;
1438
+ document.removeEventListener("pointermove", handlePointer);
1439
+ document.removeEventListener("pointerdown", handlePointer);
1440
+ document.documentElement.removeEventListener(
1441
+ "pointerleave",
1442
+ handlePointerLeave
1443
+ );
1011
1444
  window.removeEventListener("scroll", handleScroll, { capture: true });
1012
- const tooltip = overlay.getTooltip();
1013
- if (tooltip) {
1014
- tooltip.removeEventListener("mouseenter", handleTooltipMouseEnter);
1015
- tooltip.removeEventListener("mouseleave", handleTooltipMouseLeave);
1016
- }
1017
- overlay.cleanup();
1018
- proximity.clearAllHighlights();
1019
- proximity.stopObserving();
1020
- elements.cleanup();
1445
+ window.removeEventListener("resize", handleResize);
1446
+ overlay.unmount();
1447
+ elements.disconnect();
1021
1448
  removeStegaStyles();
1022
- initialized = false;
1023
- debug7.log("stega controller stopped");
1449
+ debug5.log("stega controller stopped");
1024
1450
  };
1025
1451
  return { start, stop };
1026
1452
  }
1027
1453
 
1028
1454
  // src/core/stega/scroll-to-field.ts
1029
- var debug8 = createScopedLogger("stega:scroll");
1455
+ var debug6 = createScopedLogger("stega:scroll");
1030
1456
  var FLASH_DURATION_MS = 1200;
1031
1457
  var flashTimeout = null;
1032
1458
  function scrollToField(request) {
@@ -1043,7 +1469,7 @@ function scrollToField(request) {
1043
1469
  break;
1044
1470
  }
1045
1471
  if (!target) {
1046
- debug8.log("no element found for field", field);
1472
+ debug6.log("no element found for field", field);
1047
1473
  return false;
1048
1474
  }
1049
1475
  target.scrollIntoView({ behavior: "smooth", block: "center" });
@@ -1191,7 +1617,7 @@ function splitPath(full) {
1191
1617
  return [full.slice(0, index), full.slice(index + 1)];
1192
1618
  }
1193
1619
 
1194
- // ../../node_modules/.pnpm/preact@10.29.7/node_modules/preact/dist/preact.mjs
1620
+ // ../../node_modules/.pnpm/preact@10.29.8/node_modules/preact/dist/preact.mjs
1195
1621
  var n;
1196
1622
  var l;
1197
1623
  var u;
@@ -1262,8 +1688,8 @@ function H() {
1262
1688
  }
1263
1689
  }
1264
1690
  function L(n2, l2, u3, t3, i2, r2, o2, e2, f3, c2, a2) {
1265
- var s2, h2, p2, v2, y2, _2, g2, m2 = t3 && t3.__k || w, b2 = l2.length;
1266
- 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);
1691
+ var s2, h2, p2, v2, y2, _2, g2 = t3 && t3.__k || w, m2 = l2.length;
1692
+ 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);
1267
1693
  return u3.__e = y2, f3;
1268
1694
  }
1269
1695
  function T(n2, l2, u3, t3, i2) {
@@ -1272,13 +1698,13 @@ function T(n2, l2, u3, t3, i2) {
1272
1698
  if (s2) for (r2 = 0; r2 < a2; r2++) null != (e2 = u3[r2]) && 0 == (2 & e2.__u) && (e2.__e == t3 && (t3 = $(e2)), K(e2, e2));
1273
1699
  return t3;
1274
1700
  }
1275
- function j(n2, l2, u3, t3) {
1276
- var i2, r2;
1701
+ function j(n2, l2, u3) {
1702
+ var t3, i2;
1277
1703
  if ("function" == typeof n2.type) {
1278
- for (i2 = n2.__k, r2 = 0; i2 && r2 < i2.length; r2++) i2[r2] && (i2[r2].__ = n2, l2 = j(i2[r2], l2, u3, t3));
1704
+ for (t3 = n2.__k, i2 = 0; t3 && i2 < t3.length; i2++) t3[i2] && (t3[i2].__ = n2, l2 = j(t3[i2], l2, u3));
1279
1705
  return l2;
1280
1706
  }
1281
- n2.__e != l2 && (t3 && (l2 && n2.type && !l2.parentNode && (l2 = $(n2)), u3.insertBefore(n2.__e, l2 || null)), l2 = n2.__e);
1707
+ n2.__e != l2 && (l2 && n2.type && !l2.parentNode && (l2 = $(n2)), l2 = u3.insertBefore(n2.__e, l2 || null));
1282
1708
  do {
1283
1709
  l2 = l2 && l2.nextSibling;
1284
1710
  } while (null != l2 && 8 == l2.nodeType);
@@ -1324,35 +1750,35 @@ function V(n2) {
1324
1750
  };
1325
1751
  }
1326
1752
  function q(n2, u3, t3, i2, r2, o2, e2, f3, c2, a2) {
1327
- var s2, h2, p2, v2, y2, d2, _2, k2, x2, M, $2, I2, P2, A2, H2, T2, j2 = u3.type;
1753
+ var s2, h2, p2, v2, y2, d2, _2, k2, x2, M, I2, P2, A2, H2, T2, j2, F = u3.type;
1328
1754
  if (void 0 !== u3.constructor) return null;
1329
1755
  128 & t3.__u && (c2 = !!(32 & t3.__u), o2 = [f3 = u3.__e = t3.__e]), (s2 = l.__b) && s2(u3);
1330
- n: if ("function" == typeof j2) {
1756
+ n: if ("function" == typeof F) {
1331
1757
  h2 = e2.length;
1332
1758
  try {
1333
- 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);
1759
+ 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);
1334
1760
  else {
1335
- 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)) {
1761
+ 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)) {
1336
1762
  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) {
1337
1763
  n3 && (n3.__ = u3);
1338
- }), w.push.apply(p2.__h, p2._sb), p2._sb = [], p2.__h.length && e2.push(p2);
1764
+ }), w.push.apply(p2.__h, p2._sb), p2._sb = [], p2.__h.length && e2.push(p2), f3 = $(t3);
1339
1765
  break n;
1340
1766
  }
1341
- null != p2.componentWillUpdate && p2.componentWillUpdate(x2, p2.__s, I2), M && null != p2.componentDidUpdate && p2.__h.push(function() {
1767
+ null != p2.componentWillUpdate && p2.componentWillUpdate(x2, p2.__s, P2), M && null != p2.componentDidUpdate && p2.__h.push(function() {
1342
1768
  p2.componentDidUpdate(y2, d2, _2);
1343
1769
  });
1344
1770
  }
1345
- 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 = [];
1771
+ 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 = [];
1346
1772
  else do {
1347
- p2.__d = false, P2 && P2(u3), s2 = p2.render(p2.props, p2.state, p2.context), p2.state = p2.__s;
1348
- } while (p2.__d && ++A2 < 25);
1349
- 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);
1773
+ p2.__d = false, A2 && A2(u3), s2 = p2.render(p2.props, p2.state, p2.context), p2.state = p2.__s;
1774
+ } while (p2.__d && ++H2 < 25);
1775
+ 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);
1350
1776
  } catch (n3) {
1351
1777
  if (e2.length = h2, u3.__v = null, c2 || null != o2) {
1352
1778
  if (n3.then) {
1353
1779
  for (u3.__u |= c2 ? 160 : 128; f3 && 8 == f3.nodeType && f3.nextSibling; ) f3 = f3.nextSibling;
1354
1780
  null != o2 && (o2[o2.indexOf(f3)] = null), u3.__e = f3;
1355
- } else if (null != o2) for (T2 = o2.length; T2--; ) b(o2[T2]);
1781
+ } else if (null != o2) for (j2 = o2.length; j2--; ) b(o2[j2]);
1356
1782
  } else u3.__e = t3.__e;
1357
1783
  null == u3.__k && (u3.__k = t3.__k || []), n3.then || B(u3), l.__e(n3, u3, t3);
1358
1784
  }
@@ -1473,9 +1899,9 @@ var CLOSE_ICON_SVG = `<svg width="10" height="10" viewBox="0 0 10 10" fill="none
1473
1899
  </svg>`;
1474
1900
 
1475
1901
  // src/core/ui/toolbar.generated.ts
1476
- 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}}";
1902
+ 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}}";
1477
1903
 
1478
- // ../../node_modules/.pnpm/preact@10.29.7/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
1904
+ // ../../node_modules/.pnpm/preact@10.29.8/node_modules/preact/jsx-runtime/dist/jsxRuntime.mjs
1479
1905
  var f2 = 0;
1480
1906
  function u2(e2, t3, n2, o2, i2, u3) {
1481
1907
  t3 || (t3 = {});
@@ -2168,6 +2594,764 @@ function definePreprToolbar() {
2168
2594
  }
2169
2595
  }
2170
2596
 
2597
+ // src/core/visual-editor/asset-images.ts
2598
+ var MAX_URL_LENGTH = 4096;
2599
+ var MIN_KEY_SEGMENTS = 2;
2600
+ var TRANSFORM_OPTION = /^[a-z]{1,3}_[\w.-]*$/i;
2601
+ var FORMAT_PLACEHOLDER = "{format}";
2602
+ var PROXY_PARAMS = ["url", "href", "src", "image"];
2603
+ var ABSOLUTE_HTTP = /^https?:\/\//i;
2604
+ var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
2605
+ var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
2606
+ var ASSET_ATTR = "data-prepr-asset";
2607
+ var ASSET_SOURCE_ATTRIBUTES = [
2608
+ ...SOURCE_URL_ATTRIBUTES,
2609
+ ...SOURCE_SET_ATTRIBUTES
2610
+ ];
2611
+ var TAG_ATTRIBUTES = [
2612
+ "data-prepr-encoded",
2613
+ "data-prepr-id",
2614
+ "data-prepr-field",
2615
+ "data-prepr-entry-id",
2616
+ ASSET_ATTR
2617
+ ];
2618
+ function decodeSegment(segment) {
2619
+ try {
2620
+ return decodeURIComponent(segment);
2621
+ } catch {
2622
+ return segment;
2623
+ }
2624
+ }
2625
+ function isTransformSegment(segment) {
2626
+ return segment === FORMAT_PLACEHOLDER || segment.split(",").every((option) => TRANSFORM_OPTION.test(option));
2627
+ }
2628
+ function assetKey(url, depth = 0) {
2629
+ if (!url || url.length > MAX_URL_LENGTH) return null;
2630
+ let parsed;
2631
+ try {
2632
+ parsed = new URL(url, "https://base.invalid");
2633
+ } catch {
2634
+ return null;
2635
+ }
2636
+ if (parsed.protocol !== "https:" && parsed.protocol !== "http:") return null;
2637
+ if (depth === 0) {
2638
+ for (const name of PROXY_PARAMS) {
2639
+ const inner = parsed.searchParams.get(name);
2640
+ if (inner && ABSOLUTE_HTTP.test(inner)) return assetKey(inner, 1);
2641
+ }
2642
+ }
2643
+ const segments = parsed.pathname.split("/").filter(Boolean).map(decodeSegment);
2644
+ const file = segments.pop();
2645
+ if (!file) return null;
2646
+ const kept = segments.filter((segment) => !isTransformSegment(segment));
2647
+ kept.push(file);
2648
+ return kept.length >= MIN_KEY_SEGMENTS ? kept.join("/") : null;
2649
+ }
2650
+ function srcsetUrls(srcset) {
2651
+ const urls = [];
2652
+ let expectUrl = true;
2653
+ for (const token of srcset.split(/\s+/)) {
2654
+ if (!token) continue;
2655
+ if (!expectUrl) {
2656
+ const comma = token.indexOf(",");
2657
+ if (comma === -1) continue;
2658
+ expectUrl = true;
2659
+ const rest = token.slice(comma + 1);
2660
+ if (!rest) continue;
2661
+ urls.push(rest.replace(/,+$/, ""));
2662
+ expectUrl = rest.endsWith(",");
2663
+ continue;
2664
+ }
2665
+ const url = token.replace(/^,+|,+$/g, "");
2666
+ if (url) urls.push(url);
2667
+ expectUrl = token.endsWith(",");
2668
+ }
2669
+ return urls;
2670
+ }
2671
+ function createAssetIndex(assets) {
2672
+ const index = /* @__PURE__ */ new Map();
2673
+ for (const asset of assets) {
2674
+ const key = assetKey(asset.key);
2675
+ if (key && !index.has(key)) index.set(key, asset);
2676
+ }
2677
+ return index;
2678
+ }
2679
+ function urlsOf(img) {
2680
+ const urls = [];
2681
+ const collect = (el) => {
2682
+ for (const name of SOURCE_URL_ATTRIBUTES) {
2683
+ const value = el.getAttribute(name);
2684
+ if (value) urls.push(value);
2685
+ }
2686
+ for (const name of SOURCE_SET_ATTRIBUTES) {
2687
+ const value = el.getAttribute(name);
2688
+ if (value) urls.push(...srcsetUrls(value));
2689
+ }
2690
+ };
2691
+ collect(img);
2692
+ const picture = img.parentElement;
2693
+ if (picture?.tagName === "PICTURE") {
2694
+ for (const source of picture.children) {
2695
+ if (source.tagName === "SOURCE") collect(source);
2696
+ }
2697
+ }
2698
+ return urls;
2699
+ }
2700
+ function matchAsset(img, index, keys) {
2701
+ for (const url of urlsOf(img)) {
2702
+ let key = keys.get(url);
2703
+ if (key === void 0) {
2704
+ key = assetKey(url);
2705
+ keys.set(url, key);
2706
+ }
2707
+ const asset = key ? index.get(key) : void 0;
2708
+ if (asset) return asset;
2709
+ }
2710
+ return void 0;
2711
+ }
2712
+ function setIfChanged(el, name, value) {
2713
+ if (value === null) {
2714
+ if (el.hasAttribute(name)) el.removeAttribute(name);
2715
+ } else if (el.getAttribute(name) !== value) {
2716
+ el.setAttribute(name, value);
2717
+ }
2718
+ }
2719
+ function untagAssetImage(img) {
2720
+ for (const name of TAG_ATTRIBUTES) img.removeAttribute(name);
2721
+ }
2722
+ var TEXT_FIELD_SELECTOR = `[data-prepr-encoded]:not([${ASSET_ATTR}])`;
2723
+ function besideOtherItem(img, isCurrentItem) {
2724
+ for (let block = img.parentElement; block && block !== document.body; block = block.parentElement) {
2725
+ const texts = block.querySelectorAll(TEXT_FIELD_SELECTOR);
2726
+ if (texts.length === 0) continue;
2727
+ for (const text of texts) {
2728
+ if (isCurrentItem(text)) return false;
2729
+ }
2730
+ return true;
2731
+ }
2732
+ return false;
2733
+ }
2734
+ function tagAssetImages(index, itemId, keys, options = {}) {
2735
+ const { isCurrentItem } = options;
2736
+ for (const img of document.getElementsByTagName("img")) {
2737
+ const ours = img.hasAttribute(ASSET_ATTR);
2738
+ if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
2739
+ const match = index.size ? matchAsset(img, index, keys) : void 0;
2740
+ const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
2741
+ if (!asset) {
2742
+ if (ours) untagAssetImage(img);
2743
+ continue;
2744
+ }
2745
+ setIfChanged(img, "data-prepr-encoded", "");
2746
+ setIfChanged(img, ASSET_ATTR, "");
2747
+ setIfChanged(img, "data-prepr-id", itemId);
2748
+ setIfChanged(img, "data-prepr-field", asset.field);
2749
+ setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
2750
+ }
2751
+ }
2752
+ function untagAssetImages() {
2753
+ document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
2754
+ }
2755
+
2756
+ // src/core/visual-editor/navigation-guard.ts
2757
+ var debug7 = createScopedLogger("navigation-guard");
2758
+ var trimSlash = (path) => path.length > 1 && path.endsWith("/") ? path.slice(0, -1) : path;
2759
+ function resolve(url) {
2760
+ try {
2761
+ return new URL(url, document.baseURI);
2762
+ } catch {
2763
+ return null;
2764
+ }
2765
+ }
2766
+ function leavesPage(url) {
2767
+ if (url.protocol !== "http:" && url.protocol !== "https:") return false;
2768
+ const here = window.location;
2769
+ return url.origin !== here.origin || trimSlash(url.pathname) !== trimSlash(here.pathname);
2770
+ }
2771
+ function leavingLinkHref(target) {
2772
+ if (!(target instanceof Element)) return null;
2773
+ const link = target.closest("a[href]");
2774
+ if (!link || link.hasAttribute("download")) return null;
2775
+ const linkTarget = link.getAttribute("target");
2776
+ if (linkTarget && linkTarget !== "_self") return null;
2777
+ const href = link.getAttribute("href") ?? "";
2778
+ if (href.startsWith("#")) return null;
2779
+ const url = resolve(href);
2780
+ return url && leavesPage(url) ? url.href : null;
2781
+ }
2782
+ function submitterAttr(submitter, attr) {
2783
+ if (!submitter?.hasAttribute(attr)) return null;
2784
+ if (attr === "formaction") return submitter.formAction;
2785
+ if (attr === "formmethod") return submitter.formMethod;
2786
+ return submitter.formTarget;
2787
+ }
2788
+ function submissionUrl(form, submitter) {
2789
+ const method = (submitterAttr(submitter, "formmethod") ?? form.method).toLowerCase();
2790
+ if (method === "dialog") return null;
2791
+ const target = submitterAttr(submitter, "formtarget") ?? form.target;
2792
+ if (target && target !== "_self") return null;
2793
+ const url = resolve(
2794
+ submitterAttr(submitter, "formaction") ?? (form.action || location.href)
2795
+ );
2796
+ if (!url) return null;
2797
+ if (method === "get") {
2798
+ let data;
2799
+ try {
2800
+ data = new FormData(form, submitter);
2801
+ } catch {
2802
+ data = new FormData(form);
2803
+ }
2804
+ const query = new URLSearchParams();
2805
+ data.forEach((value, key) => {
2806
+ query.append(key, typeof value === "string" ? value : value.name);
2807
+ });
2808
+ url.search = query.toString();
2809
+ }
2810
+ return url;
2811
+ }
2812
+ function createNavigationGuard(opts) {
2813
+ let active = false;
2814
+ let permitted = null;
2815
+ let pendingForm = null;
2816
+ let originalPush = null;
2817
+ let guardedPush = null;
2818
+ const isPermitted = (url) => permitted === url.href;
2819
+ const guarding = () => active && opts.showsEditedContent();
2820
+ const report = (href) => {
2821
+ debug7.log("blocked navigation to", href);
2822
+ opts.onBlocked(href);
2823
+ };
2824
+ const onSubmit = (e2) => {
2825
+ if (e2.defaultPrevented || !(e2.target instanceof HTMLFormElement)) return;
2826
+ if (!guarding()) return;
2827
+ const submitter = e2.submitter ?? null;
2828
+ const url = submissionUrl(e2.target, submitter);
2829
+ if (!url || !leavesPage(url) || isPermitted(url)) return;
2830
+ e2.preventDefault();
2831
+ pendingForm = { form: e2.target, submitter, href: url.href };
2832
+ report(url.href);
2833
+ };
2834
+ const onNavigate = (event) => {
2835
+ const e2 = event;
2836
+ if (e2.navigationType === "traverse" || e2.navigationType === "reload") {
2837
+ return;
2838
+ }
2839
+ if (!e2.cancelable || e2.hashChange || e2.downloadRequest !== null) return;
2840
+ if (!guarding()) return;
2841
+ if (e2.navigationType === "replace" && e2.destination.sameDocument) return;
2842
+ const url = resolve(e2.destination.url);
2843
+ if (!url || !leavesPage(url) || isPermitted(url)) return;
2844
+ e2.preventDefault();
2845
+ report(url.href);
2846
+ if (e2.destination.sameDocument) window.location.reload();
2847
+ };
2848
+ const navigation = () => window.navigation;
2849
+ function installPushGuard() {
2850
+ const original = history.pushState;
2851
+ const guard = function(data, unused, url) {
2852
+ if (guarding() && guardedPush === guard && url != null) {
2853
+ const target = resolve(url);
2854
+ if (target && leavesPage(target) && !isPermitted(target)) {
2855
+ report(target.href);
2856
+ window.location.reload();
2857
+ return;
2858
+ }
2859
+ }
2860
+ return original.call(this, data, unused, url);
2861
+ };
2862
+ originalPush = original;
2863
+ guardedPush = guard;
2864
+ history.pushState = guard;
2865
+ }
2866
+ function uninstallPushGuard() {
2867
+ if (history.pushState === guardedPush && originalPush) {
2868
+ history.pushState = originalPush;
2869
+ }
2870
+ originalPush = null;
2871
+ guardedPush = null;
2872
+ }
2873
+ return {
2874
+ start() {
2875
+ if (active) return;
2876
+ active = true;
2877
+ installPushGuard();
2878
+ window.addEventListener("submit", onSubmit);
2879
+ navigation()?.addEventListener("navigate", onNavigate);
2880
+ },
2881
+ stop() {
2882
+ if (!active) return;
2883
+ active = false;
2884
+ uninstallPushGuard();
2885
+ window.removeEventListener("submit", onSubmit);
2886
+ navigation()?.removeEventListener("navigate", onNavigate);
2887
+ pendingForm = null;
2888
+ },
2889
+ isActive: guarding,
2890
+ block(href) {
2891
+ report(href);
2892
+ },
2893
+ permit(href) {
2894
+ permitted = resolve(href)?.href ?? null;
2895
+ },
2896
+ allow(href) {
2897
+ const url = resolve(href);
2898
+ if (!url || url.protocol !== "http:" && url.protocol !== "https:") {
2899
+ debug7.warn("ignored navigation to unsupported URL");
2900
+ return;
2901
+ }
2902
+ permitted = url.href;
2903
+ const form = pendingForm;
2904
+ pendingForm = null;
2905
+ if (form && form.href === url.href && form.form.isConnected) {
2906
+ try {
2907
+ form.form.requestSubmit(form.submitter ?? void 0);
2908
+ } catch {
2909
+ form.form.requestSubmit();
2910
+ }
2911
+ return;
2912
+ }
2913
+ window.location.assign(url.href);
2914
+ }
2915
+ };
2916
+ }
2917
+
2918
+ // src/core/visual-editor/index.ts
2919
+ var debug8 = createScopedLogger("visual-editor");
2920
+ var ENCODED_SELECTOR2 = "[data-prepr-encoded]";
2921
+ var REFERENCE_ATTR = "data-prepr-reference";
2922
+ var REFERENCE_SELECTOR = `[${REFERENCE_ATTR}]`;
2923
+ var OTHER_ITEM_ATTR = "data-prepr-other-item";
2924
+ var OTHER_ITEM_SELECTOR = `[${OTHER_ITEM_ATTR}="on"],[${OTHER_ITEM_ATTR}="off"]`;
2925
+ var CHROME_SELECTOR = "header, footer, nav, [role=banner], [role=contentinfo], [role=navigation]";
2926
+ var SECTIONING_SELECTOR = "article, aside, main, nav, section";
2927
+ var PLAIN_HEADER_FOOTER = "header:not([role]), footer:not([role])";
2928
+ var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
2929
+ var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
2930
+ var SUSPENDED_ATTR = "data-prepr-ve-suspended";
2931
+ var FIND_RETRY_MS = 150;
2932
+ var FIND_RETRIES = 6;
2933
+ var MAX_CACHED_KEYS = 2e3;
2934
+ var LAYOUT_MUTATIONS = {
2935
+ childList: true,
2936
+ subtree: true,
2937
+ characterData: true,
2938
+ attributes: true,
2939
+ attributeFilter: [
2940
+ "class",
2941
+ "style",
2942
+ "hidden",
2943
+ "open",
2944
+ "data-prepr-encoded",
2945
+ // An image swapped by a carousel or lazy loader can show another asset.
2946
+ ...ASSET_SOURCE_ATTRIBUTES
2947
+ ]
2948
+ };
2949
+ function scrollIntoFrame(el) {
2950
+ const rect = el.getBoundingClientRect();
2951
+ const offset = Math.max(0, (window.innerHeight - rect.height) / 2);
2952
+ window.scrollTo({
2953
+ top: Math.max(0, window.scrollY + rect.top - offset),
2954
+ behavior: "smooth"
2955
+ });
2956
+ }
2957
+ var isVisible = (el) => typeof el.checkVisibility === "function" ? el.checkVisibility() : el.getClientRects().length > 0;
2958
+ function createVisualEditor(opts) {
2959
+ const overlay = new VisualEditorOverlay();
2960
+ let running = false;
2961
+ let currentItemId = null;
2962
+ let locale = opts.locale ?? "en";
2963
+ let suspended = false;
2964
+ let pointerFrame = null;
2965
+ let lastPointerTarget = null;
2966
+ let lastPointerX = 0;
2967
+ let lastPointerY = 0;
2968
+ let mutationFrame = null;
2969
+ let mutationObserver = null;
2970
+ let selection = null;
2971
+ let findTimeout = null;
2972
+ let settlePendingFind = null;
2973
+ let guardEnabled = false;
2974
+ let assetIndex = /* @__PURE__ */ new Map();
2975
+ const assetKeys = /* @__PURE__ */ new Map();
2976
+ const guard = createNavigationGuard({
2977
+ onBlocked: (href) => opts.onNavigationBlocked?.(href),
2978
+ showsEditedContent: () => showsEditedContent()
2979
+ });
2980
+ const encodedFrom = (node) => node instanceof Element ? node.closest(ENCODED_SELECTOR2) : null;
2981
+ const labelFor = (el) => fieldLabel(el.getAttribute("data-prepr-field"), locale);
2982
+ let references = null;
2983
+ let ownComponents = /* @__PURE__ */ new Set();
2984
+ const isCurrentItem = (el) => {
2985
+ if (currentItemId === null) return true;
2986
+ const id = el.getAttribute("data-prepr-id");
2987
+ return !id || id === currentItemId || ownComponents.has(id);
2988
+ };
2989
+ const showsEditedContent = () => {
2990
+ for (const el of document.querySelectorAll(ENCODED_SELECTOR2)) {
2991
+ if (isCurrentItem(el) && isVisible(el)) return true;
2992
+ }
2993
+ return false;
2994
+ };
2995
+ const linkedIdOf = (el) => {
2996
+ if (!references) return null;
2997
+ const id = el.getAttribute("data-prepr-id") ?? "";
2998
+ if (references.has(id)) return id;
2999
+ let container = el.closest(REFERENCE_SELECTOR);
3000
+ while (container) {
3001
+ const linked = container.getAttribute(REFERENCE_ATTR) ?? "";
3002
+ if (references.has(linked)) return linked;
3003
+ container = container.parentElement?.closest(REFERENCE_SELECTOR) ?? null;
3004
+ }
3005
+ return null;
3006
+ };
3007
+ const inSiteChrome = (el) => {
3008
+ let chrome = el.closest(CHROME_SELECTOR);
3009
+ while (chrome) {
3010
+ if (!chrome.matches(PLAIN_HEADER_FOOTER) || !chrome.parentElement?.closest(SECTIONING_SELECTOR)) {
3011
+ return true;
3012
+ }
3013
+ chrome = chrome.parentElement?.closest(CHROME_SELECTOR) ?? null;
3014
+ }
3015
+ return false;
3016
+ };
3017
+ const showsOtherItem = (el) => {
3018
+ const choice = el.closest(OTHER_ITEM_SELECTOR);
3019
+ if (choice) return choice.getAttribute(OTHER_ITEM_ATTR) === "on";
3020
+ return !inSiteChrome(el);
3021
+ };
3022
+ const itemKind = (el) => {
3023
+ if (isCurrentItem(el)) return "current";
3024
+ if (!references) return "other";
3025
+ if (linkedIdOf(el)) return "referenced";
3026
+ return showsOtherItem(el) ? "other" : null;
3027
+ };
3028
+ const referenceFieldOf = (el) => {
3029
+ const linked = linkedIdOf(el);
3030
+ return linked ? references?.get(linked) : void 0;
3031
+ };
3032
+ const hoverLabel = (el, kind) => {
3033
+ if (kind === "other") {
3034
+ return `${labelFor(el)} \xB7 ${t("visualEditor.otherItem", locale)}`;
3035
+ }
3036
+ if (kind === "referenced") {
3037
+ const via = referenceFieldOf(el);
3038
+ const prefix = via ? `${fieldLabel(via, locale)} \u203A ` : "";
3039
+ return `${prefix}${labelFor(el)} \xB7 ${t("visualEditor.linkedItem", locale)}`;
3040
+ }
3041
+ return labelFor(el);
3042
+ };
3043
+ const setSuspended = (value) => {
3044
+ if (suspended === value) return;
3045
+ suspended = value;
3046
+ document.documentElement.toggleAttribute(SUSPENDED_ATTR, value);
3047
+ if (value) overlay.setHover(null, "");
3048
+ };
3049
+ const updateHover = () => {
3050
+ pointerFrame = null;
3051
+ overlay.schedule();
3052
+ if (suspended) return;
3053
+ const encoded = encodedFrom(lastPointerTarget);
3054
+ const target = encoded === overlay.getSelectedTarget() ? null : encoded;
3055
+ if (!target) {
3056
+ overlay.setHover(null, "");
3057
+ return;
3058
+ }
3059
+ const kind = itemKind(target);
3060
+ if (!kind) {
3061
+ overlay.setHover(null, "");
3062
+ return;
3063
+ }
3064
+ overlay.setHover(target, hoverLabel(target, kind), { kind });
3065
+ };
3066
+ const handlePointerMove = (e2) => {
3067
+ setSuspended(e2.altKey);
3068
+ lastPointerTarget = e2.target;
3069
+ lastPointerX = e2.clientX;
3070
+ lastPointerY = e2.clientY;
3071
+ if (pointerFrame === null) {
3072
+ pointerFrame = requestAnimationFrame(updateHover);
3073
+ }
3074
+ };
3075
+ const handlePointerLeave = () => {
3076
+ lastPointerTarget = null;
3077
+ overlay.setHover(null, "");
3078
+ };
3079
+ const handleClick = (e2) => {
3080
+ if (e2.altKey) {
3081
+ const href = leavingLinkHref(e2.target);
3082
+ if (href && guard.isActive()) guard.permit(href);
3083
+ return;
3084
+ }
3085
+ const encoded = encodedFrom(e2.target);
3086
+ const kind = encoded ? itemKind(encoded) : null;
3087
+ if (!encoded || !kind) {
3088
+ clearSelection();
3089
+ if (e2.ctrlKey || e2.metaKey || e2.shiftKey || !showsEditedContent()) return;
3090
+ const href = leavingLinkHref(e2.target);
3091
+ if (!href) return;
3092
+ e2.preventDefault();
3093
+ if (guard.isActive()) {
3094
+ e2.stopPropagation();
3095
+ guard.block(href);
3096
+ }
3097
+ return;
3098
+ }
3099
+ if (kind === "other" && (e2.ctrlKey || e2.metaKey || e2.shiftKey)) return;
3100
+ e2.preventDefault();
3101
+ e2.stopPropagation();
3102
+ const item = listItemIn(encoded, e2.target);
3103
+ if (kind === "current")
3104
+ select(item?.element ?? encoded, encoded, item?.index);
3105
+ const referenceField = kind === "referenced" ? referenceFieldOf(encoded) : void 0;
3106
+ const linkedId = kind === "referenced" ? linkedIdOf(encoded) : null;
3107
+ const linkHref = kind === "other" ? leavingLinkHref(e2.target) : null;
3108
+ opts.onEdit({
3109
+ ...readEditPayload(encoded),
3110
+ ...linkedId && { id: linkedId },
3111
+ ...item && { block: item.index },
3112
+ ...referenceField && { referenceField },
3113
+ ...linkHref && { linkHref }
3114
+ });
3115
+ };
3116
+ const handleKey = (e2) => {
3117
+ if (e2.key === "Alt") setSuspended(e2.type === "keydown");
3118
+ if (e2.type === "keydown" && e2.key === "Escape") {
3119
+ clearSelection();
3120
+ }
3121
+ };
3122
+ const handleBlur = () => setSuspended(false);
3123
+ const handleScroll = () => overlay.onScroll();
3124
+ const handleResize = () => overlay.schedule();
3125
+ const handleSettled = () => overlay.schedule();
3126
+ function listItemIn(encoded, target) {
3127
+ if (!(target instanceof Element)) return null;
3128
+ const li = target.closest("li");
3129
+ if (!li || !encoded.contains(li) || !li.parentElement) return null;
3130
+ const items = Array.from(li.parentElement.children).filter(
3131
+ (child) => child.tagName === "LI"
3132
+ );
3133
+ return { element: li, index: items.indexOf(li) };
3134
+ }
3135
+ function nthListItem(encoded, block) {
3136
+ const list = encoded.querySelector("ul, ol");
3137
+ if (!list) return null;
3138
+ const items = Array.from(list.children).filter(
3139
+ (child) => child.tagName === "LI"
3140
+ );
3141
+ return items[block] ?? null;
3142
+ }
3143
+ function select(el, field, block) {
3144
+ const entryId = field.getAttribute("data-prepr-entry-id");
3145
+ selection = {
3146
+ lookup: {
3147
+ field: field.getAttribute("data-prepr-field") ?? "",
3148
+ id: field.getAttribute("data-prepr-id") ?? void 0,
3149
+ ...entryId && { entryId },
3150
+ ...block !== void 0 && { block }
3151
+ },
3152
+ label: field
3153
+ };
3154
+ overlay.setSelected(el, labelFor(field));
3155
+ if (overlay.getHoverTarget() === el) overlay.setHover(null, "");
3156
+ }
3157
+ function clearSelection() {
3158
+ selection = null;
3159
+ overlay.setSelected(null, "");
3160
+ }
3161
+ function reselect() {
3162
+ if (!selection) return;
3163
+ const current = overlay.getSelectedTarget();
3164
+ if (current?.isConnected && selection.label.isConnected) return;
3165
+ const found = findFieldElement(selection.lookup);
3166
+ if (!found || !isCurrentItem(found)) {
3167
+ overlay.setSelected(null, "");
3168
+ return;
3169
+ }
3170
+ const { block } = selection.lookup;
3171
+ const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
3172
+ select(el, found, block);
3173
+ }
3174
+ function tagAssets() {
3175
+ if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
3176
+ tagAssetImages(assetIndex, currentItemId, assetKeys, {
3177
+ isCurrentItem: (el) => isCurrentItem(el)
3178
+ });
3179
+ }
3180
+ const handleLayoutFrame = () => {
3181
+ mutationFrame = null;
3182
+ if (assetIndex.size) tagAssets();
3183
+ reselect();
3184
+ const hovered = overlay.getHoverTarget();
3185
+ if (hovered && !hovered.isConnected) {
3186
+ lastPointerTarget = document.elementFromPoint(lastPointerX, lastPointerY);
3187
+ updateHover();
3188
+ return;
3189
+ }
3190
+ overlay.schedule();
3191
+ };
3192
+ const handleMutations = () => {
3193
+ if (mutationFrame !== null) return;
3194
+ if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
3195
+ mutationFrame = requestAnimationFrame(handleLayoutFrame);
3196
+ };
3197
+ function injectCursorStyles() {
3198
+ if (document.querySelector(`style[${CURSOR_STYLE_MARKER}]`)) return;
3199
+ const style = document.createElement("style");
3200
+ style.setAttribute(CURSOR_STYLE_MARKER, "");
3201
+ style.textContent = CURSOR_STYLES;
3202
+ document.head.appendChild(style);
3203
+ }
3204
+ function start() {
3205
+ if (running) return;
3206
+ running = true;
3207
+ overlay.mount();
3208
+ injectCursorStyles();
3209
+ document.addEventListener("pointermove", handlePointerMove, {
3210
+ passive: true
3211
+ });
3212
+ document.documentElement.addEventListener(
3213
+ "pointerleave",
3214
+ handlePointerLeave
3215
+ );
3216
+ document.addEventListener("click", handleClick, { capture: true });
3217
+ window.addEventListener("keydown", handleKey);
3218
+ window.addEventListener("keyup", handleKey);
3219
+ window.addEventListener("blur", handleBlur);
3220
+ window.addEventListener("scroll", handleScroll, {
3221
+ capture: true,
3222
+ passive: true
3223
+ });
3224
+ window.addEventListener("resize", handleResize, { passive: true });
3225
+ document.addEventListener("transitionend", handleSettled, {
3226
+ capture: true,
3227
+ passive: true
3228
+ });
3229
+ document.addEventListener("animationend", handleSettled, {
3230
+ capture: true,
3231
+ passive: true
3232
+ });
3233
+ mutationObserver = new MutationObserver(handleMutations);
3234
+ mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
3235
+ if (assetIndex.size) tagAssets();
3236
+ syncGuard();
3237
+ debug8.log("visual editor started");
3238
+ }
3239
+ function stop() {
3240
+ if (!running) return;
3241
+ running = false;
3242
+ if (pointerFrame !== null) cancelAnimationFrame(pointerFrame);
3243
+ if (mutationFrame !== null) cancelAnimationFrame(mutationFrame);
3244
+ mutationObserver?.disconnect();
3245
+ mutationObserver = null;
3246
+ cancelFind();
3247
+ pointerFrame = null;
3248
+ mutationFrame = null;
3249
+ selection = null;
3250
+ lastPointerTarget = null;
3251
+ setSuspended(false);
3252
+ document.removeEventListener("pointermove", handlePointerMove);
3253
+ document.documentElement.removeEventListener(
3254
+ "pointerleave",
3255
+ handlePointerLeave
3256
+ );
3257
+ document.removeEventListener("click", handleClick, { capture: true });
3258
+ window.removeEventListener("keydown", handleKey);
3259
+ window.removeEventListener("keyup", handleKey);
3260
+ window.removeEventListener("blur", handleBlur);
3261
+ window.removeEventListener("scroll", handleScroll, { capture: true });
3262
+ window.removeEventListener("resize", handleResize);
3263
+ document.removeEventListener("transitionend", handleSettled, {
3264
+ capture: true
3265
+ });
3266
+ document.removeEventListener("animationend", handleSettled, {
3267
+ capture: true
3268
+ });
3269
+ document.querySelectorAll(`style[${CURSOR_STYLE_MARKER}]`).forEach((node) => node.remove());
3270
+ overlay.unmount();
3271
+ untagAssetImages();
3272
+ guard.stop();
3273
+ debug8.log("visual editor stopped");
3274
+ }
3275
+ function cancelFind() {
3276
+ if (findTimeout) clearTimeout(findTimeout);
3277
+ findTimeout = null;
3278
+ settlePendingFind?.();
3279
+ settlePendingFind = null;
3280
+ }
3281
+ function focusField(lookup) {
3282
+ cancelFind();
3283
+ return new Promise((resolve2) => {
3284
+ let attempt = 0;
3285
+ const finish = (found) => {
3286
+ settlePendingFind = null;
3287
+ resolve2(found);
3288
+ };
3289
+ settlePendingFind = () => resolve2(false);
3290
+ const tryFind = () => {
3291
+ findTimeout = null;
3292
+ const found = findFieldElement(lookup);
3293
+ if (found) {
3294
+ const el = lookup.block !== void 0 ? nthListItem(found, lookup.block) ?? found : found;
3295
+ scrollIntoFrame(el);
3296
+ if (running && isCurrentItem(found)) select(el, found, lookup.block);
3297
+ finish(true);
3298
+ return;
3299
+ }
3300
+ if (++attempt >= FIND_RETRIES) {
3301
+ debug8.log("no element renders field", lookup.field);
3302
+ finish(false);
3303
+ return;
3304
+ }
3305
+ findTimeout = setTimeout(tryFind, FIND_RETRY_MS);
3306
+ };
3307
+ tryFind();
3308
+ });
3309
+ }
3310
+ function setCurrentItem(itemId, referenceList, componentIds) {
3311
+ currentItemId = itemId;
3312
+ if (referenceList) setReferences(referenceList, componentIds);
3313
+ else if (componentIds) ownComponents = new Set(componentIds);
3314
+ if (running && assetIndex.size) tagAssets();
3315
+ const selected = overlay.getSelectedTarget();
3316
+ const owner = selected ? encodedFrom(selected) : null;
3317
+ if (owner && !isCurrentItem(owner)) clearSelection();
3318
+ }
3319
+ function setAssets(assets) {
3320
+ const hadAssets = assetIndex.size > 0;
3321
+ assetIndex = createAssetIndex(assets);
3322
+ if (running && (hadAssets || assetIndex.size)) tagAssets();
3323
+ }
3324
+ function setReferences(referenceList, componentIds) {
3325
+ if (componentIds) ownComponents = new Set(componentIds);
3326
+ references = /* @__PURE__ */ new Map();
3327
+ for (const { id, field } of referenceList) {
3328
+ if (!references.has(id)) references.set(id, field);
3329
+ }
3330
+ }
3331
+ function setLocale(value) {
3332
+ locale = value;
3333
+ }
3334
+ function syncGuard() {
3335
+ if (running && guardEnabled) guard.start();
3336
+ else guard.stop();
3337
+ }
3338
+ function setNavigationGuard(enabled) {
3339
+ guardEnabled = enabled;
3340
+ syncGuard();
3341
+ }
3342
+ return {
3343
+ start,
3344
+ stop,
3345
+ setCurrentItem,
3346
+ setReferences,
3347
+ setAssets,
3348
+ setLocale,
3349
+ focusField,
3350
+ setNavigationGuard,
3351
+ allowNavigation: (href) => guard.allow(href)
3352
+ };
3353
+ }
3354
+
2171
3355
  // src/core/create-preview.ts
2172
3356
  var debug9 = createScopedLogger("create-preview");
2173
3357
  var controllerStores = /* @__PURE__ */ new WeakMap();
@@ -2240,29 +3424,47 @@ function createPreprPreview(opts = {}) {
2240
3424
  el.connect(store, (key) => t(key, locale));
2241
3425
  }
2242
3426
  const editingEnabled = features.editMode || isIframe;
2243
- const stega = createStegaController({
2244
- // The CMS deep-link tooltip is chrome, so it follows the same decision as
2245
- // the bar: noise inside the editor (clicking the element itself requests
2246
- // the edit there), and unwanted in a headless preview that asked for no UI.
2247
- tooltip: mountUi,
2248
- // In the editor, ask the parent to focus the field instead of opening a new
2249
- // tab. Standalone previews keep the window.open behaviour.
2250
- onEdit: ({ href, id, field, locale: contentLocale }) => {
2251
- const safeHref = href ? safeEditUrl(href) : null;
2252
- if (href && !safeHref) {
2253
- debug9.warn("ignored edit request with unsupported href scheme");
2254
- return;
2255
- }
2256
- if (isIframe) {
2257
- sendPreprEvent("field_edit_requested", {
2258
- id,
2259
- field,
2260
- locale: contentLocale
2261
- });
2262
- } else if (safeHref) {
2263
- window.open(safeHref, "_blank", "noopener,noreferrer");
2264
- }
3427
+ const onEdit = ({
3428
+ href,
3429
+ id,
3430
+ field,
3431
+ locale: contentLocale,
3432
+ block,
3433
+ entryId,
3434
+ referenceField,
3435
+ linkHref
3436
+ }) => {
3437
+ const safeHref = href ? safeEditUrl(href) : null;
3438
+ if (href && !safeHref) {
3439
+ debug9.warn("ignored edit request with unsupported href scheme");
3440
+ return;
2265
3441
  }
3442
+ if (isIframe) {
3443
+ sendPreprEvent("field_edit_requested", {
3444
+ id,
3445
+ field,
3446
+ locale: contentLocale,
3447
+ ...block !== void 0 && { block },
3448
+ ...entryId && { entryId },
3449
+ ...referenceField && { referenceField },
3450
+ ...linkHref && { linkHref }
3451
+ });
3452
+ } else if (safeHref) {
3453
+ window.open(safeHref, "_blank", "noopener,noreferrer");
3454
+ }
3455
+ };
3456
+ const visualEditor = isIframe ? createVisualEditor({
3457
+ onEdit,
3458
+ locale,
3459
+ onNavigationBlocked: (href) => sendPreprEvent("navigation_blocked", { href })
3460
+ }) : null;
3461
+ const stega = visualEditor ?? createStegaController({
3462
+ // The edit-mode marks and hover label are chrome, so they follow the
3463
+ // same decision as the bar: unwanted in a headless preview that asked
3464
+ // for no UI.
3465
+ tooltip: mountUi,
3466
+ onEdit,
3467
+ locale
2266
3468
  });
2267
3469
  const autoClean = createStegaAutoClean({
2268
3470
  enabled: options?.autoClean
@@ -2285,10 +3487,30 @@ function createPreprPreview(opts = {}) {
2285
3487
  });
2286
3488
  const bridge = createIframeBridge(store, {
2287
3489
  allowedEditorOrigins: options?.allowedEditorOrigins,
3490
+ onHandshake: ({
3491
+ itemId,
3492
+ referencedIds,
3493
+ componentIds,
3494
+ uiLocale,
3495
+ navigationGuard,
3496
+ assets
3497
+ }) => {
3498
+ if (uiLocale) visualEditor?.setLocale(uiLocale);
3499
+ visualEditor?.setCurrentItem(itemId ?? null, referencedIds, componentIds);
3500
+ visualEditor?.setNavigationGuard(navigationGuard === true);
3501
+ if (assets) visualEditor?.setAssets(assets);
3502
+ },
3503
+ onAssets: (assets) => visualEditor?.setAssets(assets),
3504
+ onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
3505
+ onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
2288
3506
  // The editor selecting a field scrolls the matching element into view. The
2289
3507
  // lookup relies on the attributes the stega scan writes, so it finds
2290
3508
  // nothing until encoded content has been tagged.
2291
- onScrollToField: ({ field, id }) => {
3509
+ onScrollToField: ({ field, id, block, entryId, fieldById }) => {
3510
+ if (visualEditor) {
3511
+ void visualEditor.focusField({ field, id, block, entryId, fieldById });
3512
+ return;
3513
+ }
2292
3514
  if (!scrollToField({ field, id })) {
2293
3515
  debug9.log("no element on this page renders field", field);
2294
3516
  }
@@ -2320,7 +3542,7 @@ function createPreprPreview(opts = {}) {
2320
3542
  }
2321
3543
 
2322
3544
  // src/version.ts
2323
- var VERSION = "0.5.0-rc.1";
3545
+ var VERSION = "0.5.0-rc.11";
2324
3546
 
2325
3547
  // src/core/pixel.ts
2326
3548
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";