@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
@@ -2,7 +2,7 @@
2
2
  import { NextResponse } from "next/server";
3
3
 
4
4
  // src/version.ts
5
- var VERSION = "0.5.0-rc.11";
5
+ var VERSION = "0.5.0-rc.12";
6
6
 
7
7
  // src/core/constants.ts
8
8
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -362,8 +362,32 @@ function shapeLike(template, incoming) {
362
362
  }
363
363
  return out;
364
364
  }
365
+ function isAsset(value) {
366
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
367
+ }
368
+ function hasId(list, entry) {
369
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
370
+ }
371
+ function isAssetValue(value) {
372
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
373
+ }
374
+ function keepRemovedAssets(current, next) {
375
+ const listed = /* @__PURE__ */ new Set();
376
+ for (const entry of next) {
377
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
378
+ }
379
+ let out = next;
380
+ current.forEach((entry, position) => {
381
+ if (!isPlainObject(entry) || entry._id === void 0) return;
382
+ if (listed.has(entry._id)) return;
383
+ if (out === next) out = [...next];
384
+ out.splice(Math.min(position, out.length), 0, entry);
385
+ });
386
+ return out;
387
+ }
365
388
  function mergeStructured(current, incoming) {
366
389
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
390
+ if (isAssetValue(current)) return current;
367
391
  if (Array.isArray(current)) return current.length ? [] : current;
368
392
  return null;
369
393
  }
@@ -371,13 +395,18 @@ function mergeStructured(current, incoming) {
371
395
  if (current === null || current === void 0) return incoming;
372
396
  if (Array.isArray(current)) {
373
397
  const template = current.find(isPlainObject);
374
- return list.map((entry) => {
398
+ const next = list.map((entry) => {
375
399
  const match = current.find(
376
400
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
377
401
  );
378
402
  if (isPlainObject(match)) return mergeObject(match, entry);
379
403
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
380
404
  });
405
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
406
+ return next;
407
+ }
408
+ const kept = keepRemovedAssets(current, next);
409
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
381
410
  }
382
411
  if (isPlainObject(current)) return mergeObject(current, list[0]);
383
412
  return current;
@@ -1,11 +1,11 @@
1
1
  "use server";
2
2
  import {
3
3
  isPreviewFromHeaders
4
- } from "../chunk-QSXIHRH4.js";
5
- import "../chunk-AOHA5INA.js";
4
+ } from "../chunk-TJ7VALNA.js";
5
+ import "../chunk-HVTNUCF7.js";
6
6
  import {
7
7
  parsePatchLog
8
- } from "../chunk-KD3GK6TO.js";
8
+ } from "../chunk-CABGJW5X.js";
9
9
  import "../chunk-S3VT4UWM.js";
10
10
 
11
11
  // src/nextjs/live-action.ts
@@ -196,8 +196,32 @@ function shapeLike(template, incoming) {
196
196
  }
197
197
  return out;
198
198
  }
199
+ function isAsset(value) {
200
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
201
+ }
202
+ function hasId(list, entry) {
203
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
204
+ }
205
+ function isAssetValue(value) {
206
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
207
+ }
208
+ function keepRemovedAssets(current, next) {
209
+ const listed = /* @__PURE__ */ new Set();
210
+ for (const entry of next) {
211
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
212
+ }
213
+ let out = next;
214
+ current.forEach((entry, position) => {
215
+ if (!isPlainObject(entry) || entry._id === void 0) return;
216
+ if (listed.has(entry._id)) return;
217
+ if (out === next) out = [...next];
218
+ out.splice(Math.min(position, out.length), 0, entry);
219
+ });
220
+ return out;
221
+ }
199
222
  function mergeStructured(current, incoming) {
200
223
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
224
+ if (isAssetValue(current)) return current;
201
225
  if (Array.isArray(current)) return current.length ? [] : current;
202
226
  return null;
203
227
  }
@@ -205,13 +229,18 @@ function mergeStructured(current, incoming) {
205
229
  if (current === null || current === void 0) return incoming;
206
230
  if (Array.isArray(current)) {
207
231
  const template = current.find(isPlainObject);
208
- return list.map((entry) => {
232
+ const next = list.map((entry) => {
209
233
  const match = current.find(
210
234
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
211
235
  );
212
236
  if (isPlainObject(match)) return mergeObject(match, entry);
213
237
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
214
238
  });
239
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
240
+ return next;
241
+ }
242
+ const kept = keepRemovedAssets(current, next);
243
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
215
244
  }
216
245
  if (isPlainObject(current)) return mergeObject(current, list[0]);
217
246
  return current;
@@ -27,7 +27,7 @@ function isFramed() {
27
27
  return typeof window !== "undefined" && window.self !== window.top;
28
28
  }
29
29
  function startLive() {
30
- starting ?? (starting = import("../live-preview-DLXGXRE5.js").then(
30
+ starting ?? (starting = import("../live-preview-SFBGATWC.js").then(
31
31
  ({ createLiveBridge, createLiveStore }) => {
32
32
  const skipped = new Set(quarantined());
33
33
  const started = createLiveStore({
@@ -35,7 +35,7 @@ __export(nuxt_exports, {
35
35
  module.exports = __toCommonJS(nuxt_exports);
36
36
 
37
37
  // src/version.ts
38
- var VERSION = "0.5.0-rc.11";
38
+ var VERSION = "0.5.0-rc.12";
39
39
 
40
40
  // src/core/constants.ts
41
41
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.5.0-rc.11";
2
+ var VERSION = "0.5.0-rc.12";
3
3
 
4
4
  // src/core/constants.ts
5
5
  var COOKIE_PREVIEW_MODE = "Prepr-Preview-Mode";
@@ -160,8 +160,32 @@ function shapeLike(template, incoming) {
160
160
  }
161
161
  return out;
162
162
  }
163
+ function isAsset(value) {
164
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
165
+ }
166
+ function hasId(list, entry) {
167
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
168
+ }
169
+ function isAssetValue(value) {
170
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
171
+ }
172
+ function keepRemovedAssets(current, next) {
173
+ const listed = /* @__PURE__ */ new Set();
174
+ for (const entry of next) {
175
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
176
+ }
177
+ let out = next;
178
+ current.forEach((entry, position) => {
179
+ if (!isPlainObject(entry) || entry._id === void 0) return;
180
+ if (listed.has(entry._id)) return;
181
+ if (out === next) out = [...next];
182
+ out.splice(Math.min(position, out.length), 0, entry);
183
+ });
184
+ return out;
185
+ }
163
186
  function mergeStructured(current, incoming) {
164
187
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
188
+ if (isAssetValue(current)) return current;
165
189
  if (Array.isArray(current)) return current.length ? [] : current;
166
190
  return null;
167
191
  }
@@ -169,13 +193,18 @@ function mergeStructured(current, incoming) {
169
193
  if (current === null || current === void 0) return incoming;
170
194
  if (Array.isArray(current)) {
171
195
  const template = current.find(isPlainObject);
172
- return list.map((entry) => {
196
+ const next = list.map((entry) => {
173
197
  const match = current.find(
174
198
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
175
199
  );
176
200
  if (isPlainObject(match)) return mergeObject(match, entry);
177
201
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
178
202
  });
203
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
204
+ return next;
205
+ }
206
+ const kept = keepRemovedAssets(current, next);
207
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
179
208
  }
180
209
  if (isPlainObject(current)) return mergeObject(current, list[0]);
181
210
  return current;
@@ -536,8 +536,32 @@ function shapeLike(template, incoming) {
536
536
  }
537
537
  return out;
538
538
  }
539
+ function isAsset(value) {
540
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
541
+ }
542
+ function hasId(list, entry) {
543
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
544
+ }
545
+ function isAssetValue(value) {
546
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
547
+ }
548
+ function keepRemovedAssets(current, next) {
549
+ const listed = /* @__PURE__ */ new Set();
550
+ for (const entry of next) {
551
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
552
+ }
553
+ let out = next;
554
+ current.forEach((entry, position) => {
555
+ if (!isPlainObject(entry) || entry._id === void 0) return;
556
+ if (listed.has(entry._id)) return;
557
+ if (out === next) out = [...next];
558
+ out.splice(Math.min(position, out.length), 0, entry);
559
+ });
560
+ return out;
561
+ }
539
562
  function mergeStructured(current, incoming) {
540
563
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
564
+ if (isAssetValue(current)) return current;
541
565
  if (Array.isArray(current)) return current.length ? [] : current;
542
566
  return null;
543
567
  }
@@ -545,13 +569,18 @@ function mergeStructured(current, incoming) {
545
569
  if (current === null || current === void 0) return incoming;
546
570
  if (Array.isArray(current)) {
547
571
  const template = current.find(isPlainObject);
548
- return list.map((entry) => {
572
+ const next = list.map((entry) => {
549
573
  const match = current.find(
550
574
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
551
575
  );
552
576
  if (isPlainObject(match)) return mergeObject(match, entry);
553
577
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
554
578
  });
579
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
580
+ return next;
581
+ }
582
+ const kept = keepRemovedAssets(current, next);
583
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
555
584
  }
556
585
  if (isPlainObject(current)) return mergeObject(current, list[0]);
557
586
  return current;
package/dist/nuxt/live.js CHANGED
@@ -130,7 +130,7 @@ function isFramed() {
130
130
  return typeof window !== "undefined" && window.self !== window.top;
131
131
  }
132
132
  function startLive() {
133
- starting ?? (starting = import("./live-preview-2RCU35W5.js").then(
133
+ starting ?? (starting = import("./live-preview-XMQQZHMZ.js").then(
134
134
  ({ applyPatchLog, createLiveBridge, createLiveStore }) => {
135
135
  applyLog = applyPatchLog;
136
136
  const started = createLiveStore();
@@ -395,6 +395,7 @@ function createIframeBridge(store, options = {}) {
395
395
  const onReferences = options.onReferences;
396
396
  const onAssets = options.onAssets;
397
397
  const onAllowNavigation = options.onAllowNavigation;
398
+ const onItemChanged = options.onItemChanged;
398
399
  let parentOrigin = null;
399
400
  const onKeyDown = (event) => {
400
401
  const key = event.key.toLowerCase();
@@ -485,6 +486,14 @@ function createIframeBridge(store, options = {}) {
485
486
  debug.warn("allowNavigation handler failed", error);
486
487
  }
487
488
  }
489
+ if (data?.event === "prepr:itemChanged") {
490
+ if (typeof data.itemId !== "string" || !data.itemId) return;
491
+ try {
492
+ onItemChanged?.(data.itemId);
493
+ } catch (error) {
494
+ debug.warn("itemChanged handler failed", error);
495
+ }
496
+ }
488
497
  };
489
498
  return {
490
499
  start() {
@@ -2628,6 +2637,7 @@ var ABSOLUTE_HTTP = /^https?:\/\//i;
2628
2637
  var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
2629
2638
  var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
2630
2639
  var ASSET_ATTR = "data-prepr-asset";
2640
+ var REMOVED_ATTR = "data-prepr-removed";
2631
2641
  var ASSET_SOURCE_ATTRIBUTES = [
2632
2642
  ...SOURCE_URL_ATTRIBUTES,
2633
2643
  ...SOURCE_SET_ATTRIBUTES
@@ -2637,7 +2647,8 @@ var TAG_ATTRIBUTES = [
2637
2647
  "data-prepr-id",
2638
2648
  "data-prepr-field",
2639
2649
  "data-prepr-entry-id",
2640
- ASSET_ATTR
2650
+ ASSET_ATTR,
2651
+ REMOVED_ATTR
2641
2652
  ];
2642
2653
  function decodeSegment(segment) {
2643
2654
  try {
@@ -2756,11 +2767,22 @@ function besideOtherItem(img, isCurrentItem) {
2756
2767
  return false;
2757
2768
  }
2758
2769
  function tagAssetImages(index, itemId, keys, options = {}) {
2759
- const { isCurrentItem } = options;
2770
+ const { isCurrentItem, removed } = options;
2771
+ let showsRemoved = false;
2760
2772
  for (const img of document.getElementsByTagName("img")) {
2761
2773
  const ours = img.hasAttribute(ASSET_ATTR);
2762
- if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
2763
- const match = index.size ? matchAsset(img, index, keys) : void 0;
2774
+ let match = index.size ? matchAsset(img, index, keys) : void 0;
2775
+ let isRemoved = false;
2776
+ if (!match && removed?.size) {
2777
+ match = matchAsset(img, removed, keys);
2778
+ isRemoved = match !== void 0;
2779
+ }
2780
+ if (!ours && img.hasAttribute("data-prepr-encoded")) {
2781
+ const mark = isRemoved && (!isCurrentItem || isCurrentItem(img));
2782
+ setIfChanged(img, REMOVED_ATTR, mark ? "" : null);
2783
+ showsRemoved || (showsRemoved = mark);
2784
+ continue;
2785
+ }
2764
2786
  const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
2765
2787
  if (!asset) {
2766
2788
  if (ours) untagAssetImage(img);
@@ -2771,10 +2793,14 @@ function tagAssetImages(index, itemId, keys, options = {}) {
2771
2793
  setIfChanged(img, "data-prepr-id", itemId);
2772
2794
  setIfChanged(img, "data-prepr-field", asset.field);
2773
2795
  setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
2796
+ setIfChanged(img, REMOVED_ATTR, isRemoved ? "" : null);
2797
+ showsRemoved || (showsRemoved = isRemoved);
2774
2798
  }
2799
+ return showsRemoved;
2775
2800
  }
2776
2801
  function untagAssetImages() {
2777
2802
  document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
2803
+ document.querySelectorAll(`img[${REMOVED_ATTR}]`).forEach((img) => img.removeAttribute(REMOVED_ATTR));
2778
2804
  }
2779
2805
 
2780
2806
  // src/core/visual-editor/navigation-guard.ts
@@ -2950,11 +2976,13 @@ var CHROME_SELECTOR = "header, footer, nav, [role=banner], [role=contentinfo], [
2950
2976
  var SECTIONING_SELECTOR = "article, aside, main, nav, section";
2951
2977
  var PLAIN_HEADER_FOOTER = "header:not([role]), footer:not([role])";
2952
2978
  var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
2953
- var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
2979
+ var REMOVED_STYLES = `img[${REMOVED_ATTR}]{opacity:.2!important;transition:opacity .2s ease}@media (prefers-reduced-motion:reduce){img[${REMOVED_ATTR}]{transition:none}}`;
2980
+ var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}${REMOVED_STYLES}`;
2954
2981
  var SUSPENDED_ATTR = "data-prepr-ve-suspended";
2955
2982
  var FIND_RETRY_MS = 150;
2956
2983
  var FIND_RETRIES = 6;
2957
2984
  var MAX_CACHED_KEYS = 2e3;
2985
+ var SAVED_FADE_MS = 1e4;
2958
2986
  var LAYOUT_MUTATIONS = {
2959
2987
  childList: true,
2960
2988
  subtree: true,
@@ -2997,6 +3025,9 @@ function createVisualEditor(opts) {
2997
3025
  let guardEnabled = false;
2998
3026
  let assetIndex = /* @__PURE__ */ new Map();
2999
3027
  const assetKeys = /* @__PURE__ */ new Map();
3028
+ const removedAssets = /* @__PURE__ */ new Map();
3029
+ let saved = false;
3030
+ let savedTimeout = null;
3000
3031
  const guard = createNavigationGuard({
3001
3032
  onBlocked: (href) => opts.onNavigationBlocked?.(href),
3002
3033
  showsEditedContent: () => showsEditedContent()
@@ -3195,15 +3226,32 @@ function createVisualEditor(opts) {
3195
3226
  const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
3196
3227
  select(el, found, block);
3197
3228
  }
3229
+ const hasAssets = () => assetIndex.size > 0 || removedAssets.size > 0;
3198
3230
  function tagAssets() {
3199
3231
  if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
3200
- tagAssetImages(assetIndex, currentItemId, assetKeys, {
3201
- isCurrentItem: (el) => isCurrentItem(el)
3232
+ const showsRemoved = tagAssetImages(assetIndex, currentItemId, assetKeys, {
3233
+ isCurrentItem: (el) => isCurrentItem(el),
3234
+ removed: removedAssets
3202
3235
  });
3236
+ if (saved && !showsRemoved) forgetRemoved();
3237
+ }
3238
+ function forgetRemoved() {
3239
+ saved = false;
3240
+ if (savedTimeout) clearTimeout(savedTimeout);
3241
+ savedTimeout = null;
3242
+ if (!removedAssets.size) return;
3243
+ removedAssets.clear();
3244
+ if (running) tagAssets();
3245
+ }
3246
+ function itemSaved() {
3247
+ if (!removedAssets.size) return;
3248
+ saved = true;
3249
+ if (savedTimeout) clearTimeout(savedTimeout);
3250
+ savedTimeout = setTimeout(forgetRemoved, SAVED_FADE_MS);
3203
3251
  }
3204
3252
  const handleLayoutFrame = () => {
3205
3253
  mutationFrame = null;
3206
- if (assetIndex.size) tagAssets();
3254
+ if (hasAssets()) tagAssets();
3207
3255
  reselect();
3208
3256
  const hovered = overlay.getHoverTarget();
3209
3257
  if (hovered && !hovered.isConnected) {
@@ -3215,7 +3263,7 @@ function createVisualEditor(opts) {
3215
3263
  };
3216
3264
  const handleMutations = () => {
3217
3265
  if (mutationFrame !== null) return;
3218
- if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
3266
+ if (!selection && !overlay.getHoverTarget() && !hasAssets()) return;
3219
3267
  mutationFrame = requestAnimationFrame(handleLayoutFrame);
3220
3268
  };
3221
3269
  function injectCursorStyles() {
@@ -3256,7 +3304,7 @@ function createVisualEditor(opts) {
3256
3304
  });
3257
3305
  mutationObserver = new MutationObserver(handleMutations);
3258
3306
  mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
3259
- if (assetIndex.size) tagAssets();
3307
+ if (hasAssets()) tagAssets();
3260
3308
  syncGuard();
3261
3309
  debug8.log("visual editor started");
3262
3310
  }
@@ -3332,18 +3380,24 @@ function createVisualEditor(opts) {
3332
3380
  });
3333
3381
  }
3334
3382
  function setCurrentItem(itemId, referenceList, componentIds) {
3383
+ if (itemId !== currentItemId) forgetRemoved();
3335
3384
  currentItemId = itemId;
3336
3385
  if (referenceList) setReferences(referenceList, componentIds);
3337
3386
  else if (componentIds) ownComponents = new Set(componentIds);
3338
- if (running && assetIndex.size) tagAssets();
3387
+ if (running && hasAssets()) tagAssets();
3339
3388
  const selected = overlay.getSelectedTarget();
3340
3389
  const owner = selected ? encodedFrom(selected) : null;
3341
3390
  if (owner && !isCurrentItem(owner)) clearSelection();
3342
3391
  }
3343
3392
  function setAssets(assets) {
3344
- const hadAssets = assetIndex.size > 0;
3345
- assetIndex = createAssetIndex(assets);
3346
- if (running && (hadAssets || assetIndex.size)) tagAssets();
3393
+ const hadAssets = hasAssets();
3394
+ const next = createAssetIndex(assets);
3395
+ for (const [key, asset] of assetIndex) {
3396
+ if (!next.has(key)) removedAssets.set(key, asset);
3397
+ }
3398
+ for (const key of next.keys()) removedAssets.delete(key);
3399
+ assetIndex = next;
3400
+ if (running && (hadAssets || hasAssets())) tagAssets();
3347
3401
  }
3348
3402
  function setReferences(referenceList, componentIds) {
3349
3403
  if (componentIds) ownComponents = new Set(componentIds);
@@ -3369,6 +3423,7 @@ function createVisualEditor(opts) {
3369
3423
  setCurrentItem,
3370
3424
  setReferences,
3371
3425
  setAssets,
3426
+ itemSaved,
3372
3427
  setLocale,
3373
3428
  focusField,
3374
3429
  setNavigationGuard,
@@ -3526,6 +3581,7 @@ function createPreprPreview(opts = {}) {
3526
3581
  },
3527
3582
  onAssets: (assets) => visualEditor?.setAssets(assets),
3528
3583
  onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
3584
+ onItemChanged: () => visualEditor?.itemSaved(),
3529
3585
  onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
3530
3586
  // The editor selecting a field scrolls the matching element into view. The
3531
3587
  // lookup relies on the attributes the stega scan writes, so it finds
@@ -3566,7 +3622,7 @@ function createPreprPreview(opts = {}) {
3566
3622
  }
3567
3623
 
3568
3624
  // src/version.ts
3569
- var VERSION = "0.5.0-rc.11";
3625
+ var VERSION = "0.5.0-rc.12";
3570
3626
 
3571
3627
  // src/core/pixel.ts
3572
3628
  var PIXEL_SCRIPT_URL = "https://cdn.tracking.prepr.io/js/prepr.min.js";