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

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 (34) hide show
  1. package/README.md +7 -0
  2. package/dist/astro/index.cjs +31 -2
  3. package/dist/astro/index.js +31 -2
  4. package/dist/{chunk-KD3GK6TO.js → chunk-CABGJW5X.js} +30 -1
  5. package/dist/{chunk-URXSDJOU.js → chunk-H3AFUPYD.js} +9 -0
  6. package/dist/{chunk-AOHA5INA.js → chunk-HVTNUCF7.js} +1 -1
  7. package/dist/{chunk-IPMB32AZ.js → chunk-LWUSRWFE.js} +2 -2
  8. package/dist/{chunk-6423Q744.js → chunk-M7FT7XXV.js} +63 -16
  9. package/dist/{chunk-QSXIHRH4.js → chunk-TJ7VALNA.js} +1 -1
  10. package/dist/index.cjs +101 -16
  11. package/dist/index.d.cts +6 -1
  12. package/dist/index.d.ts +6 -1
  13. package/dist/index.js +6 -6
  14. package/dist/{live-preview-DLXGXRE5.js → live-preview-SFBGATWC.js} +3 -3
  15. package/dist/nextjs/components.cjs +71 -15
  16. package/dist/nextjs/components.js +3 -3
  17. package/dist/nextjs/index.cjs +31 -2
  18. package/dist/nextjs/index.js +31 -2
  19. package/dist/nextjs/live-action.js +3 -3
  20. package/dist/nextjs/live.cjs +30 -1
  21. package/dist/nextjs/live.js +1 -1
  22. package/dist/nuxt/index.cjs +1 -1
  23. package/dist/nuxt/index.js +1 -1
  24. package/dist/nuxt/{live-preview-2RCU35W5.js → live-preview-XMQQZHMZ.js} +30 -1
  25. package/dist/nuxt/live.cjs +30 -1
  26. package/dist/nuxt/live.js +1 -1
  27. package/dist/react/components.cjs +71 -15
  28. package/dist/react/components.js +71 -15
  29. package/dist/sveltekit/index.cjs +1 -1
  30. package/dist/sveltekit/index.js +1 -1
  31. package/dist/sveltekit/{live-preview-2RCU35W5.js → live-preview-XMQQZHMZ.js} +30 -1
  32. package/dist/sveltekit/live.cjs +30 -1
  33. package/dist/sveltekit/live.js +1 -1
  34. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -51,7 +51,7 @@ __export(src_exports, {
51
51
  module.exports = __toCommonJS(src_exports);
52
52
 
53
53
  // src/version.ts
54
- var VERSION = "0.5.0-rc.11";
54
+ var VERSION = "0.5.0-rc.12";
55
55
 
56
56
  // src/core/features.ts
57
57
  function isEnabled(config) {
@@ -427,6 +427,7 @@ function createIframeBridge(store, options = {}) {
427
427
  const onReferences = options.onReferences;
428
428
  const onAssets = options.onAssets;
429
429
  const onAllowNavigation = options.onAllowNavigation;
430
+ const onItemChanged = options.onItemChanged;
430
431
  let parentOrigin = null;
431
432
  const onKeyDown = (event) => {
432
433
  const key = event.key.toLowerCase();
@@ -517,6 +518,14 @@ function createIframeBridge(store, options = {}) {
517
518
  debug.warn("allowNavigation handler failed", error);
518
519
  }
519
520
  }
521
+ if (data?.event === "prepr:itemChanged") {
522
+ if (typeof data.itemId !== "string" || !data.itemId) return;
523
+ try {
524
+ onItemChanged?.(data.itemId);
525
+ } catch (error) {
526
+ debug.warn("itemChanged handler failed", error);
527
+ }
528
+ }
520
529
  };
521
530
  return {
522
531
  start() {
@@ -2664,6 +2673,7 @@ var ABSOLUTE_HTTP = /^https?:\/\//i;
2664
2673
  var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
2665
2674
  var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
2666
2675
  var ASSET_ATTR = "data-prepr-asset";
2676
+ var REMOVED_ATTR = "data-prepr-removed";
2667
2677
  var ASSET_SOURCE_ATTRIBUTES = [
2668
2678
  ...SOURCE_URL_ATTRIBUTES,
2669
2679
  ...SOURCE_SET_ATTRIBUTES
@@ -2673,7 +2683,8 @@ var TAG_ATTRIBUTES = [
2673
2683
  "data-prepr-id",
2674
2684
  "data-prepr-field",
2675
2685
  "data-prepr-entry-id",
2676
- ASSET_ATTR
2686
+ ASSET_ATTR,
2687
+ REMOVED_ATTR
2677
2688
  ];
2678
2689
  function decodeSegment(segment) {
2679
2690
  try {
@@ -2792,11 +2803,22 @@ function besideOtherItem(img, isCurrentItem) {
2792
2803
  return false;
2793
2804
  }
2794
2805
  function tagAssetImages(index, itemId, keys, options = {}) {
2795
- const { isCurrentItem } = options;
2806
+ const { isCurrentItem, removed } = options;
2807
+ let showsRemoved = false;
2796
2808
  for (const img of document.getElementsByTagName("img")) {
2797
2809
  const ours = img.hasAttribute(ASSET_ATTR);
2798
- if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
2799
- const match = index.size ? matchAsset(img, index, keys) : void 0;
2810
+ let match = index.size ? matchAsset(img, index, keys) : void 0;
2811
+ let isRemoved = false;
2812
+ if (!match && removed?.size) {
2813
+ match = matchAsset(img, removed, keys);
2814
+ isRemoved = match !== void 0;
2815
+ }
2816
+ if (!ours && img.hasAttribute("data-prepr-encoded")) {
2817
+ const mark = isRemoved && (!isCurrentItem || isCurrentItem(img));
2818
+ setIfChanged(img, REMOVED_ATTR, mark ? "" : null);
2819
+ showsRemoved || (showsRemoved = mark);
2820
+ continue;
2821
+ }
2800
2822
  const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
2801
2823
  if (!asset) {
2802
2824
  if (ours) untagAssetImage(img);
@@ -2807,10 +2829,14 @@ function tagAssetImages(index, itemId, keys, options = {}) {
2807
2829
  setIfChanged(img, "data-prepr-id", itemId);
2808
2830
  setIfChanged(img, "data-prepr-field", asset.field);
2809
2831
  setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
2832
+ setIfChanged(img, REMOVED_ATTR, isRemoved ? "" : null);
2833
+ showsRemoved || (showsRemoved = isRemoved);
2810
2834
  }
2835
+ return showsRemoved;
2811
2836
  }
2812
2837
  function untagAssetImages() {
2813
2838
  document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
2839
+ document.querySelectorAll(`img[${REMOVED_ATTR}]`).forEach((img) => img.removeAttribute(REMOVED_ATTR));
2814
2840
  }
2815
2841
 
2816
2842
  // src/core/visual-editor/navigation-guard.ts
@@ -2986,11 +3012,13 @@ var CHROME_SELECTOR = "header, footer, nav, [role=banner], [role=contentinfo], [
2986
3012
  var SECTIONING_SELECTOR = "article, aside, main, nav, section";
2987
3013
  var PLAIN_HEADER_FOOTER = "header:not([role]), footer:not([role])";
2988
3014
  var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
2989
- var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
3015
+ var REMOVED_STYLES = `img[${REMOVED_ATTR}]{opacity:.2!important;transition:opacity .2s ease}@media (prefers-reduced-motion:reduce){img[${REMOVED_ATTR}]{transition:none}}`;
3016
+ var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}${REMOVED_STYLES}`;
2990
3017
  var SUSPENDED_ATTR = "data-prepr-ve-suspended";
2991
3018
  var FIND_RETRY_MS = 150;
2992
3019
  var FIND_RETRIES = 6;
2993
3020
  var MAX_CACHED_KEYS = 2e3;
3021
+ var SAVED_FADE_MS = 1e4;
2994
3022
  var LAYOUT_MUTATIONS = {
2995
3023
  childList: true,
2996
3024
  subtree: true,
@@ -3033,6 +3061,9 @@ function createVisualEditor(opts) {
3033
3061
  let guardEnabled = false;
3034
3062
  let assetIndex = /* @__PURE__ */ new Map();
3035
3063
  const assetKeys = /* @__PURE__ */ new Map();
3064
+ const removedAssets = /* @__PURE__ */ new Map();
3065
+ let saved = false;
3066
+ let savedTimeout = null;
3036
3067
  const guard = createNavigationGuard({
3037
3068
  onBlocked: (href) => opts.onNavigationBlocked?.(href),
3038
3069
  showsEditedContent: () => showsEditedContent()
@@ -3231,15 +3262,32 @@ function createVisualEditor(opts) {
3231
3262
  const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
3232
3263
  select(el, found, block);
3233
3264
  }
3265
+ const hasAssets = () => assetIndex.size > 0 || removedAssets.size > 0;
3234
3266
  function tagAssets() {
3235
3267
  if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
3236
- tagAssetImages(assetIndex, currentItemId, assetKeys, {
3237
- isCurrentItem: (el) => isCurrentItem(el)
3268
+ const showsRemoved = tagAssetImages(assetIndex, currentItemId, assetKeys, {
3269
+ isCurrentItem: (el) => isCurrentItem(el),
3270
+ removed: removedAssets
3238
3271
  });
3272
+ if (saved && !showsRemoved) forgetRemoved();
3273
+ }
3274
+ function forgetRemoved() {
3275
+ saved = false;
3276
+ if (savedTimeout) clearTimeout(savedTimeout);
3277
+ savedTimeout = null;
3278
+ if (!removedAssets.size) return;
3279
+ removedAssets.clear();
3280
+ if (running) tagAssets();
3281
+ }
3282
+ function itemSaved() {
3283
+ if (!removedAssets.size) return;
3284
+ saved = true;
3285
+ if (savedTimeout) clearTimeout(savedTimeout);
3286
+ savedTimeout = setTimeout(forgetRemoved, SAVED_FADE_MS);
3239
3287
  }
3240
3288
  const handleLayoutFrame = () => {
3241
3289
  mutationFrame = null;
3242
- if (assetIndex.size) tagAssets();
3290
+ if (hasAssets()) tagAssets();
3243
3291
  reselect();
3244
3292
  const hovered = overlay.getHoverTarget();
3245
3293
  if (hovered && !hovered.isConnected) {
@@ -3251,7 +3299,7 @@ function createVisualEditor(opts) {
3251
3299
  };
3252
3300
  const handleMutations = () => {
3253
3301
  if (mutationFrame !== null) return;
3254
- if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
3302
+ if (!selection && !overlay.getHoverTarget() && !hasAssets()) return;
3255
3303
  mutationFrame = requestAnimationFrame(handleLayoutFrame);
3256
3304
  };
3257
3305
  function injectCursorStyles() {
@@ -3292,7 +3340,7 @@ function createVisualEditor(opts) {
3292
3340
  });
3293
3341
  mutationObserver = new MutationObserver(handleMutations);
3294
3342
  mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
3295
- if (assetIndex.size) tagAssets();
3343
+ if (hasAssets()) tagAssets();
3296
3344
  syncGuard();
3297
3345
  debug8.log("visual editor started");
3298
3346
  }
@@ -3368,18 +3416,24 @@ function createVisualEditor(opts) {
3368
3416
  });
3369
3417
  }
3370
3418
  function setCurrentItem(itemId, referenceList, componentIds) {
3419
+ if (itemId !== currentItemId) forgetRemoved();
3371
3420
  currentItemId = itemId;
3372
3421
  if (referenceList) setReferences(referenceList, componentIds);
3373
3422
  else if (componentIds) ownComponents = new Set(componentIds);
3374
- if (running && assetIndex.size) tagAssets();
3423
+ if (running && hasAssets()) tagAssets();
3375
3424
  const selected = overlay.getSelectedTarget();
3376
3425
  const owner = selected ? encodedFrom(selected) : null;
3377
3426
  if (owner && !isCurrentItem(owner)) clearSelection();
3378
3427
  }
3379
3428
  function setAssets(assets) {
3380
- const hadAssets = assetIndex.size > 0;
3381
- assetIndex = createAssetIndex(assets);
3382
- if (running && (hadAssets || assetIndex.size)) tagAssets();
3429
+ const hadAssets = hasAssets();
3430
+ const next = createAssetIndex(assets);
3431
+ for (const [key, asset] of assetIndex) {
3432
+ if (!next.has(key)) removedAssets.set(key, asset);
3433
+ }
3434
+ for (const key of next.keys()) removedAssets.delete(key);
3435
+ assetIndex = next;
3436
+ if (running && (hadAssets || hasAssets())) tagAssets();
3383
3437
  }
3384
3438
  function setReferences(referenceList, componentIds) {
3385
3439
  if (componentIds) ownComponents = new Set(componentIds);
@@ -3405,6 +3459,7 @@ function createVisualEditor(opts) {
3405
3459
  setCurrentItem,
3406
3460
  setReferences,
3407
3461
  setAssets,
3462
+ itemSaved,
3408
3463
  setLocale,
3409
3464
  focusField,
3410
3465
  setNavigationGuard,
@@ -3562,6 +3617,7 @@ function createPreprPreview(opts = {}) {
3562
3617
  },
3563
3618
  onAssets: (assets) => visualEditor?.setAssets(assets),
3564
3619
  onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
3620
+ onItemChanged: () => visualEditor?.itemSaved(),
3565
3621
  onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
3566
3622
  // The editor selecting a field scrolls the matching element into view. The
3567
3623
  // lookup relies on the attributes the stega scan writes, so it finds
@@ -3728,8 +3784,32 @@ function shapeLike(template, incoming) {
3728
3784
  }
3729
3785
  return out;
3730
3786
  }
3787
+ function isAsset(value) {
3788
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
3789
+ }
3790
+ function hasId(list, entry) {
3791
+ return entry._id !== void 0 && list.some((c2) => isPlainObject(c2) && c2._id === entry._id);
3792
+ }
3793
+ function isAssetValue(value) {
3794
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
3795
+ }
3796
+ function keepRemovedAssets(current, next) {
3797
+ const listed = /* @__PURE__ */ new Set();
3798
+ for (const entry of next) {
3799
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
3800
+ }
3801
+ let out = next;
3802
+ current.forEach((entry, position) => {
3803
+ if (!isPlainObject(entry) || entry._id === void 0) return;
3804
+ if (listed.has(entry._id)) return;
3805
+ if (out === next) out = [...next];
3806
+ out.splice(Math.min(position, out.length), 0, entry);
3807
+ });
3808
+ return out;
3809
+ }
3731
3810
  function mergeStructured(current, incoming) {
3732
3811
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
3812
+ if (isAssetValue(current)) return current;
3733
3813
  if (Array.isArray(current)) return current.length ? [] : current;
3734
3814
  return null;
3735
3815
  }
@@ -3737,13 +3817,18 @@ function mergeStructured(current, incoming) {
3737
3817
  if (current === null || current === void 0) return incoming;
3738
3818
  if (Array.isArray(current)) {
3739
3819
  const template = current.find(isPlainObject);
3740
- return list.map((entry) => {
3820
+ const next = list.map((entry) => {
3741
3821
  const match = current.find(
3742
3822
  (c2) => isPlainObject(c2) && c2._id !== void 0 && c2._id === entry._id
3743
3823
  );
3744
3824
  if (isPlainObject(match)) return mergeObject(match, entry);
3745
3825
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
3746
3826
  });
3827
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
3828
+ return next;
3829
+ }
3830
+ const kept = keepRemovedAssets(current, next);
3831
+ return kept.length === current.length && kept.every((entry, i2) => entry === current[i2]) ? current : kept;
3747
3832
  }
3748
3833
  if (isPlainObject(current)) return mergeObject(current, list[0]);
3749
3834
  return current;
package/dist/index.d.cts CHANGED
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
5
5
  * Package version; must match package.json. Lives in its own leaf module so
6
6
  * core can import it without cycling through the barrel.
7
7
  */
8
- declare const VERSION = "0.5.0-rc.11";
8
+ declare const VERSION = "0.5.0-rc.12";
9
9
 
10
10
  /**
11
11
  * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
@@ -233,6 +233,11 @@ interface IframeBridgeOptions {
233
233
  * string; its scheme is not checked here.
234
234
  */
235
235
  onAllowNavigation?: (href: string) => void;
236
+ /**
237
+ * The editor saved the open item (`prepr:itemChanged`), with that item's
238
+ * id. The page shows stored content from its next render on.
239
+ */
240
+ onItemChanged?: (itemId: string) => void;
236
241
  }
237
242
 
238
243
  interface PreviewAttrsTarget {
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { g as PreprEnvironment, h as PreprEventType, i as PreprFeatureConfig, j
5
5
  * Package version; must match package.json. Lives in its own leaf module so
6
6
  * core can import it without cycling through the barrel.
7
7
  */
8
- declare const VERSION = "0.5.0-rc.11";
8
+ declare const VERSION = "0.5.0-rc.12";
9
9
 
10
10
  /**
11
11
  * Resolve a `PreprFeatures` bag to plain booleans, defaulting every omitted key
@@ -233,6 +233,11 @@ interface IframeBridgeOptions {
233
233
  * string; its scheme is not checked here.
234
234
  */
235
235
  onAllowNavigation?: (href: string) => void;
236
+ /**
237
+ * The editor saved the open item (`prepr:itemChanged`), with that item's
238
+ * id. The page shows stored content from its next render on.
239
+ */
240
+ onItemChanged?: (itemId: string) => void;
236
241
  }
237
242
 
238
243
  interface PreviewAttrsTarget {
package/dist/index.js CHANGED
@@ -18,7 +18,7 @@ import {
18
18
  setTrackingParam,
19
19
  stegaClean,
20
20
  trackEvent
21
- } from "./chunk-6423Q744.js";
21
+ } from "./chunk-M7FT7XXV.js";
22
22
  import {
23
23
  PreprError,
24
24
  extractAccessToken,
@@ -29,22 +29,22 @@ import {
29
29
  getPreprUUIDFromHeaders,
30
30
  getToolbarPropsFromHeaders,
31
31
  validatePreprToken
32
- } from "./chunk-QSXIHRH4.js";
32
+ } from "./chunk-TJ7VALNA.js";
33
33
  import {
34
34
  VERSION,
35
35
  resolveFeatures
36
- } from "./chunk-AOHA5INA.js";
36
+ } from "./chunk-HVTNUCF7.js";
37
37
  import {
38
38
  createLiveBridge,
39
39
  createLiveStore
40
- } from "./chunk-IPMB32AZ.js";
41
- import "./chunk-URXSDJOU.js";
40
+ } from "./chunk-LWUSRWFE.js";
41
+ import "./chunk-H3AFUPYD.js";
42
42
  import {
43
43
  collectFields,
44
44
  normalizeQuery,
45
45
  parsePatchLog,
46
46
  readQueryDocument
47
- } from "./chunk-KD3GK6TO.js";
47
+ } from "./chunk-CABGJW5X.js";
48
48
  import {
49
49
  createScopedLogger
50
50
  } from "./chunk-S3VT4UWM.js";
@@ -1,13 +1,13 @@
1
1
  import {
2
2
  createLiveBridge,
3
3
  createLiveStore
4
- } from "./chunk-IPMB32AZ.js";
5
- import "./chunk-URXSDJOU.js";
4
+ } from "./chunk-LWUSRWFE.js";
5
+ import "./chunk-H3AFUPYD.js";
6
6
  import {
7
7
  applyPatchLog,
8
8
  normalizeQuery,
9
9
  parsePatchLog
10
- } from "./chunk-KD3GK6TO.js";
10
+ } from "./chunk-CABGJW5X.js";
11
11
  import "./chunk-S3VT4UWM.js";
12
12
  export {
13
13
  applyPatchLog,
@@ -396,6 +396,7 @@ function createIframeBridge(store, options = {}) {
396
396
  const onReferences = options.onReferences;
397
397
  const onAssets = options.onAssets;
398
398
  const onAllowNavigation = options.onAllowNavigation;
399
+ const onItemChanged = options.onItemChanged;
399
400
  let parentOrigin = null;
400
401
  const onKeyDown = (event) => {
401
402
  const key = event.key.toLowerCase();
@@ -486,6 +487,14 @@ function createIframeBridge(store, options = {}) {
486
487
  debug.warn("allowNavigation handler failed", error);
487
488
  }
488
489
  }
490
+ if (data?.event === "prepr:itemChanged") {
491
+ if (typeof data.itemId !== "string" || !data.itemId) return;
492
+ try {
493
+ onItemChanged?.(data.itemId);
494
+ } catch (error) {
495
+ debug.warn("itemChanged handler failed", error);
496
+ }
497
+ }
489
498
  };
490
499
  return {
491
500
  start() {
@@ -2629,6 +2638,7 @@ var ABSOLUTE_HTTP = /^https?:\/\//i;
2629
2638
  var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
2630
2639
  var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
2631
2640
  var ASSET_ATTR = "data-prepr-asset";
2641
+ var REMOVED_ATTR = "data-prepr-removed";
2632
2642
  var ASSET_SOURCE_ATTRIBUTES = [
2633
2643
  ...SOURCE_URL_ATTRIBUTES,
2634
2644
  ...SOURCE_SET_ATTRIBUTES
@@ -2638,7 +2648,8 @@ var TAG_ATTRIBUTES = [
2638
2648
  "data-prepr-id",
2639
2649
  "data-prepr-field",
2640
2650
  "data-prepr-entry-id",
2641
- ASSET_ATTR
2651
+ ASSET_ATTR,
2652
+ REMOVED_ATTR
2642
2653
  ];
2643
2654
  function decodeSegment(segment) {
2644
2655
  try {
@@ -2757,11 +2768,22 @@ function besideOtherItem(img, isCurrentItem) {
2757
2768
  return false;
2758
2769
  }
2759
2770
  function tagAssetImages(index, itemId, keys, options = {}) {
2760
- const { isCurrentItem } = options;
2771
+ const { isCurrentItem, removed } = options;
2772
+ let showsRemoved = false;
2761
2773
  for (const img of document.getElementsByTagName("img")) {
2762
2774
  const ours = img.hasAttribute(ASSET_ATTR);
2763
- if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
2764
- const match = index.size ? matchAsset(img, index, keys) : void 0;
2775
+ let match = index.size ? matchAsset(img, index, keys) : void 0;
2776
+ let isRemoved = false;
2777
+ if (!match && removed?.size) {
2778
+ match = matchAsset(img, removed, keys);
2779
+ isRemoved = match !== void 0;
2780
+ }
2781
+ if (!ours && img.hasAttribute("data-prepr-encoded")) {
2782
+ const mark = isRemoved && (!isCurrentItem || isCurrentItem(img));
2783
+ setIfChanged(img, REMOVED_ATTR, mark ? "" : null);
2784
+ showsRemoved || (showsRemoved = mark);
2785
+ continue;
2786
+ }
2765
2787
  const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
2766
2788
  if (!asset) {
2767
2789
  if (ours) untagAssetImage(img);
@@ -2772,10 +2794,14 @@ function tagAssetImages(index, itemId, keys, options = {}) {
2772
2794
  setIfChanged(img, "data-prepr-id", itemId);
2773
2795
  setIfChanged(img, "data-prepr-field", asset.field);
2774
2796
  setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
2797
+ setIfChanged(img, REMOVED_ATTR, isRemoved ? "" : null);
2798
+ showsRemoved || (showsRemoved = isRemoved);
2775
2799
  }
2800
+ return showsRemoved;
2776
2801
  }
2777
2802
  function untagAssetImages() {
2778
2803
  document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
2804
+ document.querySelectorAll(`img[${REMOVED_ATTR}]`).forEach((img) => img.removeAttribute(REMOVED_ATTR));
2779
2805
  }
2780
2806
 
2781
2807
  // src/core/visual-editor/navigation-guard.ts
@@ -2951,11 +2977,13 @@ var CHROME_SELECTOR = "header, footer, nav, [role=banner], [role=contentinfo], [
2951
2977
  var SECTIONING_SELECTOR = "article, aside, main, nav, section";
2952
2978
  var PLAIN_HEADER_FOOTER = "header:not([role]), footer:not([role])";
2953
2979
  var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
2954
- var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
2980
+ var REMOVED_STYLES = `img[${REMOVED_ATTR}]{opacity:.2!important;transition:opacity .2s ease}@media (prefers-reduced-motion:reduce){img[${REMOVED_ATTR}]{transition:none}}`;
2981
+ var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}${REMOVED_STYLES}`;
2955
2982
  var SUSPENDED_ATTR = "data-prepr-ve-suspended";
2956
2983
  var FIND_RETRY_MS = 150;
2957
2984
  var FIND_RETRIES = 6;
2958
2985
  var MAX_CACHED_KEYS = 2e3;
2986
+ var SAVED_FADE_MS = 1e4;
2959
2987
  var LAYOUT_MUTATIONS = {
2960
2988
  childList: true,
2961
2989
  subtree: true,
@@ -2998,6 +3026,9 @@ function createVisualEditor(opts) {
2998
3026
  let guardEnabled = false;
2999
3027
  let assetIndex = /* @__PURE__ */ new Map();
3000
3028
  const assetKeys = /* @__PURE__ */ new Map();
3029
+ const removedAssets = /* @__PURE__ */ new Map();
3030
+ let saved = false;
3031
+ let savedTimeout = null;
3001
3032
  const guard = createNavigationGuard({
3002
3033
  onBlocked: (href) => opts.onNavigationBlocked?.(href),
3003
3034
  showsEditedContent: () => showsEditedContent()
@@ -3196,15 +3227,32 @@ function createVisualEditor(opts) {
3196
3227
  const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
3197
3228
  select(el, found, block);
3198
3229
  }
3230
+ const hasAssets = () => assetIndex.size > 0 || removedAssets.size > 0;
3199
3231
  function tagAssets() {
3200
3232
  if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
3201
- tagAssetImages(assetIndex, currentItemId, assetKeys, {
3202
- isCurrentItem: (el) => isCurrentItem(el)
3233
+ const showsRemoved = tagAssetImages(assetIndex, currentItemId, assetKeys, {
3234
+ isCurrentItem: (el) => isCurrentItem(el),
3235
+ removed: removedAssets
3203
3236
  });
3237
+ if (saved && !showsRemoved) forgetRemoved();
3238
+ }
3239
+ function forgetRemoved() {
3240
+ saved = false;
3241
+ if (savedTimeout) clearTimeout(savedTimeout);
3242
+ savedTimeout = null;
3243
+ if (!removedAssets.size) return;
3244
+ removedAssets.clear();
3245
+ if (running) tagAssets();
3246
+ }
3247
+ function itemSaved() {
3248
+ if (!removedAssets.size) return;
3249
+ saved = true;
3250
+ if (savedTimeout) clearTimeout(savedTimeout);
3251
+ savedTimeout = setTimeout(forgetRemoved, SAVED_FADE_MS);
3204
3252
  }
3205
3253
  const handleLayoutFrame = () => {
3206
3254
  mutationFrame = null;
3207
- if (assetIndex.size) tagAssets();
3255
+ if (hasAssets()) tagAssets();
3208
3256
  reselect();
3209
3257
  const hovered = overlay.getHoverTarget();
3210
3258
  if (hovered && !hovered.isConnected) {
@@ -3216,7 +3264,7 @@ function createVisualEditor(opts) {
3216
3264
  };
3217
3265
  const handleMutations = () => {
3218
3266
  if (mutationFrame !== null) return;
3219
- if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
3267
+ if (!selection && !overlay.getHoverTarget() && !hasAssets()) return;
3220
3268
  mutationFrame = requestAnimationFrame(handleLayoutFrame);
3221
3269
  };
3222
3270
  function injectCursorStyles() {
@@ -3257,7 +3305,7 @@ function createVisualEditor(opts) {
3257
3305
  });
3258
3306
  mutationObserver = new MutationObserver(handleMutations);
3259
3307
  mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
3260
- if (assetIndex.size) tagAssets();
3308
+ if (hasAssets()) tagAssets();
3261
3309
  syncGuard();
3262
3310
  debug8.log("visual editor started");
3263
3311
  }
@@ -3333,18 +3381,24 @@ function createVisualEditor(opts) {
3333
3381
  });
3334
3382
  }
3335
3383
  function setCurrentItem(itemId, referenceList, componentIds) {
3384
+ if (itemId !== currentItemId) forgetRemoved();
3336
3385
  currentItemId = itemId;
3337
3386
  if (referenceList) setReferences(referenceList, componentIds);
3338
3387
  else if (componentIds) ownComponents = new Set(componentIds);
3339
- if (running && assetIndex.size) tagAssets();
3388
+ if (running && hasAssets()) tagAssets();
3340
3389
  const selected = overlay.getSelectedTarget();
3341
3390
  const owner = selected ? encodedFrom(selected) : null;
3342
3391
  if (owner && !isCurrentItem(owner)) clearSelection();
3343
3392
  }
3344
3393
  function setAssets(assets) {
3345
- const hadAssets = assetIndex.size > 0;
3346
- assetIndex = createAssetIndex(assets);
3347
- if (running && (hadAssets || assetIndex.size)) tagAssets();
3394
+ const hadAssets = hasAssets();
3395
+ const next = createAssetIndex(assets);
3396
+ for (const [key, asset] of assetIndex) {
3397
+ if (!next.has(key)) removedAssets.set(key, asset);
3398
+ }
3399
+ for (const key of next.keys()) removedAssets.delete(key);
3400
+ assetIndex = next;
3401
+ if (running && (hadAssets || hasAssets())) tagAssets();
3348
3402
  }
3349
3403
  function setReferences(referenceList, componentIds) {
3350
3404
  if (componentIds) ownComponents = new Set(componentIds);
@@ -3370,6 +3424,7 @@ function createVisualEditor(opts) {
3370
3424
  setCurrentItem,
3371
3425
  setReferences,
3372
3426
  setAssets,
3427
+ itemSaved,
3373
3428
  setLocale,
3374
3429
  focusField,
3375
3430
  setNavigationGuard,
@@ -3527,6 +3582,7 @@ function createPreprPreview(opts = {}) {
3527
3582
  },
3528
3583
  onAssets: (assets) => visualEditor?.setAssets(assets),
3529
3584
  onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
3585
+ onItemChanged: () => visualEditor?.itemSaved(),
3530
3586
  onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
3531
3587
  // The editor selecting a field scrolls the matching element into view. The
3532
3588
  // lookup relies on the attributes the stega scan writes, so it finds
@@ -3570,7 +3626,7 @@ function createPreprPreview(opts = {}) {
3570
3626
  var import_react = require("react");
3571
3627
 
3572
3628
  // src/version.ts
3573
- var VERSION = "0.5.0-rc.11";
3629
+ var VERSION = "0.5.0-rc.12";
3574
3630
 
3575
3631
  // src/core/pixel.ts
3576
3632
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";
@@ -2,9 +2,9 @@
2
2
  import {
3
3
  createPreprPreview,
4
4
  loadTrackingPixel
5
- } from "../chunk-6423Q744.js";
6
- import "../chunk-AOHA5INA.js";
7
- import "../chunk-URXSDJOU.js";
5
+ } from "../chunk-M7FT7XXV.js";
6
+ import "../chunk-HVTNUCF7.js";
7
+ import "../chunk-H3AFUPYD.js";
8
8
  import "../chunk-S3VT4UWM.js";
9
9
 
10
10
  // src/nextjs/components.ts
@@ -49,7 +49,7 @@ module.exports = __toCommonJS(nextjs_exports);
49
49
  var import_server = require("next/server");
50
50
 
51
51
  // src/version.ts
52
- var VERSION = "0.5.0-rc.11";
52
+ var VERSION = "0.5.0-rc.12";
53
53
 
54
54
  // src/core/constants.ts
55
55
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -405,8 +405,32 @@ function shapeLike(template, incoming) {
405
405
  }
406
406
  return out;
407
407
  }
408
+ function isAsset(value) {
409
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
410
+ }
411
+ function hasId(list, entry) {
412
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
413
+ }
414
+ function isAssetValue(value) {
415
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
416
+ }
417
+ function keepRemovedAssets(current, next) {
418
+ const listed = /* @__PURE__ */ new Set();
419
+ for (const entry of next) {
420
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
421
+ }
422
+ let out = next;
423
+ current.forEach((entry, position) => {
424
+ if (!isPlainObject(entry) || entry._id === void 0) return;
425
+ if (listed.has(entry._id)) return;
426
+ if (out === next) out = [...next];
427
+ out.splice(Math.min(position, out.length), 0, entry);
428
+ });
429
+ return out;
430
+ }
408
431
  function mergeStructured(current, incoming) {
409
432
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
433
+ if (isAssetValue(current)) return current;
410
434
  if (Array.isArray(current)) return current.length ? [] : current;
411
435
  return null;
412
436
  }
@@ -414,13 +438,18 @@ function mergeStructured(current, incoming) {
414
438
  if (current === null || current === void 0) return incoming;
415
439
  if (Array.isArray(current)) {
416
440
  const template = current.find(isPlainObject);
417
- return list.map((entry) => {
441
+ const next = list.map((entry) => {
418
442
  const match = current.find(
419
443
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
420
444
  );
421
445
  if (isPlainObject(match)) return mergeObject(match, entry);
422
446
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
423
447
  });
448
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
449
+ return next;
450
+ }
451
+ const kept = keepRemovedAssets(current, next);
452
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
424
453
  }
425
454
  if (isPlainObject(current)) return mergeObject(current, list[0]);
426
455
  return current;