@preprio/toolkit 0.5.0-rc.10 → 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 +21 -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-F46GJS6Y.js → chunk-HVTNUCF7.js} +1 -1
  7. package/dist/{chunk-IPMB32AZ.js → chunk-LWUSRWFE.js} +2 -2
  8. package/dist/{chunk-IDEC77LZ.js → chunk-M7FT7XXV.js} +93 -20
  9. package/dist/{chunk-TBBU3LDD.js → chunk-TJ7VALNA.js} +1 -1
  10. package/dist/index.cjs +131 -20
  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 +101 -19
  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 +101 -19
  28. package/dist/react/components.js +101 -19
  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/README.md CHANGED
@@ -840,6 +840,13 @@ The rest is about rendering:
840
840
  - Prepr images are click-to-edit inside the editor without any markup: the
841
841
  toolkit matches image URLs (also through image optimizers like
842
842
  `/_next/image`) to the open item's assets.
843
+ - **A removed image stays until the editor picks another or saves.** The
844
+ page keeps rendering it, faded to 20% opacity, so the layout does not
845
+ shift while the editor chooses a replacement. It stays click-to-edit and
846
+ opens its field. A newly picked image replaces it; after a save the page
847
+ renders stored content, so an empty state you render for a missing image
848
+ shows only then. In a field with several images, a removed one keeps its
849
+ place when the query selects the assets' `_id`.
843
850
  - **Set the attributes yourself only where that does not reach:**
844
851
  - Data you fetch in the browser without those helpers: run it through
845
852
  `annotateEditLinks(result, query)` from `@preprio/toolkit`, or mark the
@@ -872,6 +879,20 @@ The rest is about rendering:
872
879
  `data-prepr-reference="<linked item _id>"`. The editor only knows the items
873
880
  the open item links to directly; content inside the wrapper is outlined as
874
881
  that linked item, and clicking it opens the link in the editor.
882
+ - Content of items the open item does not link to (for example news cards
883
+ filled by a query) is outlined muted as "other item". Clicking it does not
884
+ follow its link: the editor offers to open that item, or to go to the
885
+ linked page. Ctrl/Cmd/Shift-click still opens the link in a new tab, and
886
+ Alt-click follows it. Inside the site's header, footer and navigation
887
+ (`header`, `footer`, `nav` and the `banner`, `contentinfo` and
888
+ `navigation` roles) such content stays unmarked and its links behave as
889
+ any other page link (a `header` or `footer` inside an `article`, `section`,
890
+ `aside` or `main` belongs to that content and is not site chrome). Change
891
+ that per part of the page with an attribute on
892
+ an ancestor; the nearest one wins:
893
+ - `data-prepr-other-item="off"` keeps other items inside it unmarked.
894
+ - `data-prepr-other-item="on"` marks them, also inside a header, footer or
895
+ navigation.
875
896
 
876
897
  Typed text and changed fields show immediately. Structural changes, such as
877
898
  adding or reordering components, appear after save.
@@ -31,7 +31,7 @@ __export(astro_exports, {
31
31
  module.exports = __toCommonJS(astro_exports);
32
32
 
33
33
  // src/version.ts
34
- var VERSION = "0.5.0-rc.10";
34
+ var VERSION = "0.5.0-rc.12";
35
35
 
36
36
  // src/core/features.ts
37
37
  function isEnabled(config) {
@@ -490,8 +490,32 @@ function shapeLike(template, incoming) {
490
490
  }
491
491
  return out;
492
492
  }
493
+ function isAsset(value) {
494
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
495
+ }
496
+ function hasId(list, entry) {
497
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
498
+ }
499
+ function isAssetValue(value) {
500
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
501
+ }
502
+ function keepRemovedAssets(current, next) {
503
+ const listed = /* @__PURE__ */ new Set();
504
+ for (const entry of next) {
505
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
506
+ }
507
+ let out = next;
508
+ current.forEach((entry, position) => {
509
+ if (!isPlainObject(entry) || entry._id === void 0) return;
510
+ if (listed.has(entry._id)) return;
511
+ if (out === next) out = [...next];
512
+ out.splice(Math.min(position, out.length), 0, entry);
513
+ });
514
+ return out;
515
+ }
493
516
  function mergeStructured(current, incoming) {
494
517
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
518
+ if (isAssetValue(current)) return current;
495
519
  if (Array.isArray(current)) return current.length ? [] : current;
496
520
  return null;
497
521
  }
@@ -499,13 +523,18 @@ function mergeStructured(current, incoming) {
499
523
  if (current === null || current === void 0) return incoming;
500
524
  if (Array.isArray(current)) {
501
525
  const template = current.find(isPlainObject);
502
- return list.map((entry) => {
526
+ const next = list.map((entry) => {
503
527
  const match = current.find(
504
528
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
505
529
  );
506
530
  if (isPlainObject(match)) return mergeObject(match, entry);
507
531
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
508
532
  });
533
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
534
+ return next;
535
+ }
536
+ const kept = keepRemovedAssets(current, next);
537
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
509
538
  }
510
539
  if (isPlainObject(current)) return mergeObject(current, list[0]);
511
540
  return current;
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.5.0-rc.10";
2
+ var VERSION = "0.5.0-rc.12";
3
3
 
4
4
  // src/core/features.ts
5
5
  function isEnabled(config) {
@@ -462,8 +462,32 @@ function shapeLike(template, incoming) {
462
462
  }
463
463
  return out;
464
464
  }
465
+ function isAsset(value) {
466
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
467
+ }
468
+ function hasId(list, entry) {
469
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
470
+ }
471
+ function isAssetValue(value) {
472
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
473
+ }
474
+ function keepRemovedAssets(current, next) {
475
+ const listed = /* @__PURE__ */ new Set();
476
+ for (const entry of next) {
477
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
478
+ }
479
+ let out = next;
480
+ current.forEach((entry, position) => {
481
+ if (!isPlainObject(entry) || entry._id === void 0) return;
482
+ if (listed.has(entry._id)) return;
483
+ if (out === next) out = [...next];
484
+ out.splice(Math.min(position, out.length), 0, entry);
485
+ });
486
+ return out;
487
+ }
465
488
  function mergeStructured(current, incoming) {
466
489
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
490
+ if (isAssetValue(current)) return current;
467
491
  if (Array.isArray(current)) return current.length ? [] : current;
468
492
  return null;
469
493
  }
@@ -471,13 +495,18 @@ function mergeStructured(current, incoming) {
471
495
  if (current === null || current === void 0) return incoming;
472
496
  if (Array.isArray(current)) {
473
497
  const template = current.find(isPlainObject);
474
- return list.map((entry) => {
498
+ const next = list.map((entry) => {
475
499
  const match = current.find(
476
500
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
477
501
  );
478
502
  if (isPlainObject(match)) return mergeObject(match, entry);
479
503
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
480
504
  });
505
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
506
+ return next;
507
+ }
508
+ const kept = keepRemovedAssets(current, next);
509
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
481
510
  }
482
511
  if (isPlainObject(current)) return mergeObject(current, list[0]);
483
512
  return current;
@@ -101,8 +101,32 @@ function shapeLike(template, incoming) {
101
101
  }
102
102
  return out;
103
103
  }
104
+ function isAsset(value) {
105
+ return isPlainObject(value) && (typeof value.url === "string" || value.__typename === "Asset");
106
+ }
107
+ function hasId(list, entry) {
108
+ return entry._id !== void 0 && list.some((c) => isPlainObject(c) && c._id === entry._id);
109
+ }
110
+ function isAssetValue(value) {
111
+ return Array.isArray(value) ? value.length > 0 && value.every(isAsset) : isAsset(value);
112
+ }
113
+ function keepRemovedAssets(current, next) {
114
+ const listed = /* @__PURE__ */ new Set();
115
+ for (const entry of next) {
116
+ if (isPlainObject(entry) && entry._id !== void 0) listed.add(entry._id);
117
+ }
118
+ let out = next;
119
+ current.forEach((entry, position) => {
120
+ if (!isPlainObject(entry) || entry._id === void 0) return;
121
+ if (listed.has(entry._id)) return;
122
+ if (out === next) out = [...next];
123
+ out.splice(Math.min(position, out.length), 0, entry);
124
+ });
125
+ return out;
126
+ }
104
127
  function mergeStructured(current, incoming) {
105
128
  if (incoming === null || Array.isArray(incoming) && !incoming.length) {
129
+ if (isAssetValue(current)) return current;
106
130
  if (Array.isArray(current)) return current.length ? [] : current;
107
131
  return null;
108
132
  }
@@ -110,13 +134,18 @@ function mergeStructured(current, incoming) {
110
134
  if (current === null || current === void 0) return incoming;
111
135
  if (Array.isArray(current)) {
112
136
  const template = current.find(isPlainObject);
113
- return list.map((entry) => {
137
+ const next = list.map((entry) => {
114
138
  const match = current.find(
115
139
  (c) => isPlainObject(c) && c._id !== void 0 && c._id === entry._id
116
140
  );
117
141
  if (isPlainObject(match)) return mergeObject(match, entry);
118
142
  return isPlainObject(template) ? shapeLike(template, entry) : entry;
119
143
  });
144
+ if (!isAssetValue(current) || list.some((entry) => !hasId(current, entry))) {
145
+ return next;
146
+ }
147
+ const kept = keepRemovedAssets(current, next);
148
+ return kept.length === current.length && kept.every((entry, i) => entry === current[i]) ? current : kept;
120
149
  }
121
150
  if (isPlainObject(current)) return mergeObject(current, list[0]);
122
151
  return current;
@@ -239,6 +239,7 @@ function createIframeBridge(store, options = {}) {
239
239
  const onReferences = options.onReferences;
240
240
  const onAssets = options.onAssets;
241
241
  const onAllowNavigation = options.onAllowNavigation;
242
+ const onItemChanged = options.onItemChanged;
242
243
  let parentOrigin = null;
243
244
  const onKeyDown = (event) => {
244
245
  const key = event.key.toLowerCase();
@@ -329,6 +330,14 @@ function createIframeBridge(store, options = {}) {
329
330
  debug.warn("allowNavigation handler failed", error);
330
331
  }
331
332
  }
333
+ if (data?.event === "prepr:itemChanged") {
334
+ if (typeof data.itemId !== "string" || !data.itemId) return;
335
+ try {
336
+ onItemChanged?.(data.itemId);
337
+ } catch (error) {
338
+ debug.warn("itemChanged handler failed", error);
339
+ }
340
+ }
332
341
  };
333
342
  return {
334
343
  start() {
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "0.5.0-rc.10";
2
+ var VERSION = "0.5.0-rc.12";
3
3
 
4
4
  // src/core/features.ts
5
5
  function isEnabled(config) {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  isAllowedEditorOrigin
3
- } from "./chunk-URXSDJOU.js";
3
+ } from "./chunk-H3AFUPYD.js";
4
4
  import {
5
5
  indexSnapshot,
6
6
  isLiveFieldChange,
@@ -8,7 +8,7 @@ import {
8
8
  parseListEntries,
9
9
  patchSnapshot,
10
10
  syncListSnapshot
11
- } from "./chunk-KD3GK6TO.js";
11
+ } from "./chunk-CABGJW5X.js";
12
12
  import {
13
13
  createScopedLogger,
14
14
  getTrustedParentOrigin,
@@ -2,12 +2,12 @@ import {
2
2
  ALL_FEATURES_ENABLED,
3
3
  VERSION,
4
4
  resolveFeatures
5
- } from "./chunk-F46GJS6Y.js";
5
+ } from "./chunk-HVTNUCF7.js";
6
6
  import {
7
7
  createIframeBridge,
8
8
  isLocale,
9
9
  t
10
- } from "./chunk-URXSDJOU.js";
10
+ } from "./chunk-H3AFUPYD.js";
11
11
  import {
12
12
  createScopedLogger,
13
13
  initDebugLogger,
@@ -2193,6 +2193,7 @@ var ABSOLUTE_HTTP = /^https?:\/\//i;
2193
2193
  var SOURCE_URL_ATTRIBUTES = ["src", "data-src"];
2194
2194
  var SOURCE_SET_ATTRIBUTES = ["srcset", "data-srcset"];
2195
2195
  var ASSET_ATTR = "data-prepr-asset";
2196
+ var REMOVED_ATTR = "data-prepr-removed";
2196
2197
  var ASSET_SOURCE_ATTRIBUTES = [
2197
2198
  ...SOURCE_URL_ATTRIBUTES,
2198
2199
  ...SOURCE_SET_ATTRIBUTES
@@ -2202,7 +2203,8 @@ var TAG_ATTRIBUTES = [
2202
2203
  "data-prepr-id",
2203
2204
  "data-prepr-field",
2204
2205
  "data-prepr-entry-id",
2205
- ASSET_ATTR
2206
+ ASSET_ATTR,
2207
+ REMOVED_ATTR
2206
2208
  ];
2207
2209
  function decodeSegment(segment) {
2208
2210
  try {
@@ -2321,11 +2323,22 @@ function besideOtherItem(img, isCurrentItem) {
2321
2323
  return false;
2322
2324
  }
2323
2325
  function tagAssetImages(index, itemId, keys, options = {}) {
2324
- const { isCurrentItem } = options;
2326
+ const { isCurrentItem, removed } = options;
2327
+ let showsRemoved = false;
2325
2328
  for (const img of document.getElementsByTagName("img")) {
2326
2329
  const ours = img.hasAttribute(ASSET_ATTR);
2327
- if (!ours && img.hasAttribute("data-prepr-encoded")) continue;
2328
- const match = index.size ? matchAsset(img, index, keys) : void 0;
2330
+ let match = index.size ? matchAsset(img, index, keys) : void 0;
2331
+ let isRemoved = false;
2332
+ if (!match && removed?.size) {
2333
+ match = matchAsset(img, removed, keys);
2334
+ isRemoved = match !== void 0;
2335
+ }
2336
+ if (!ours && img.hasAttribute("data-prepr-encoded")) {
2337
+ const mark = isRemoved && (!isCurrentItem || isCurrentItem(img));
2338
+ setIfChanged(img, REMOVED_ATTR, mark ? "" : null);
2339
+ showsRemoved || (showsRemoved = mark);
2340
+ continue;
2341
+ }
2329
2342
  const asset = match && isCurrentItem && besideOtherItem(img, isCurrentItem) ? void 0 : match;
2330
2343
  if (!asset) {
2331
2344
  if (ours) untagAssetImage(img);
@@ -2336,10 +2349,14 @@ function tagAssetImages(index, itemId, keys, options = {}) {
2336
2349
  setIfChanged(img, "data-prepr-id", itemId);
2337
2350
  setIfChanged(img, "data-prepr-field", asset.field);
2338
2351
  setIfChanged(img, "data-prepr-entry-id", asset.entryId ?? null);
2352
+ setIfChanged(img, REMOVED_ATTR, isRemoved ? "" : null);
2353
+ showsRemoved || (showsRemoved = isRemoved);
2339
2354
  }
2355
+ return showsRemoved;
2340
2356
  }
2341
2357
  function untagAssetImages() {
2342
2358
  document.querySelectorAll(`img[${ASSET_ATTR}]`).forEach(untagAssetImage);
2359
+ document.querySelectorAll(`img[${REMOVED_ATTR}]`).forEach((img) => img.removeAttribute(REMOVED_ATTR));
2343
2360
  }
2344
2361
 
2345
2362
  // src/core/visual-editor/navigation-guard.ts
@@ -2509,12 +2526,19 @@ var debug7 = createScopedLogger("visual-editor");
2509
2526
  var ENCODED_SELECTOR2 = "[data-prepr-encoded]";
2510
2527
  var REFERENCE_ATTR = "data-prepr-reference";
2511
2528
  var REFERENCE_SELECTOR = `[${REFERENCE_ATTR}]`;
2529
+ var OTHER_ITEM_ATTR = "data-prepr-other-item";
2530
+ var OTHER_ITEM_SELECTOR = `[${OTHER_ITEM_ATTR}="on"],[${OTHER_ITEM_ATTR}="off"]`;
2531
+ var CHROME_SELECTOR = "header, footer, nav, [role=banner], [role=contentinfo], [role=navigation]";
2532
+ var SECTIONING_SELECTOR = "article, aside, main, nav, section";
2533
+ var PLAIN_HEADER_FOOTER = "header:not([role]), footer:not([role])";
2512
2534
  var CURSOR_STYLE_MARKER = "data-prepr-visual-editor";
2513
- var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}`;
2535
+ var REMOVED_STYLES = `img[${REMOVED_ATTR}]{opacity:.2!important;transition:opacity .2s ease}@media (prefers-reduced-motion:reduce){img[${REMOVED_ATTR}]{transition:none}}`;
2536
+ var CURSOR_STYLES = `${ENCODED_SELECTOR2}{cursor:pointer}html[data-prepr-ve-suspended] ${ENCODED_SELECTOR2}{cursor:auto}${REMOVED_STYLES}`;
2514
2537
  var SUSPENDED_ATTR = "data-prepr-ve-suspended";
2515
2538
  var FIND_RETRY_MS = 150;
2516
2539
  var FIND_RETRIES = 6;
2517
2540
  var MAX_CACHED_KEYS = 2e3;
2541
+ var SAVED_FADE_MS = 1e4;
2518
2542
  var LAYOUT_MUTATIONS = {
2519
2543
  childList: true,
2520
2544
  subtree: true,
@@ -2557,6 +2581,9 @@ function createVisualEditor(opts) {
2557
2581
  let guardEnabled = false;
2558
2582
  let assetIndex = /* @__PURE__ */ new Map();
2559
2583
  const assetKeys = /* @__PURE__ */ new Map();
2584
+ const removedAssets = /* @__PURE__ */ new Map();
2585
+ let saved = false;
2586
+ let savedTimeout = null;
2560
2587
  const guard = createNavigationGuard({
2561
2588
  onBlocked: (href) => opts.onNavigationBlocked?.(href),
2562
2589
  showsEditedContent: () => showsEditedContent()
@@ -2588,10 +2615,26 @@ function createVisualEditor(opts) {
2588
2615
  }
2589
2616
  return null;
2590
2617
  };
2618
+ const inSiteChrome = (el) => {
2619
+ let chrome = el.closest(CHROME_SELECTOR);
2620
+ while (chrome) {
2621
+ if (!chrome.matches(PLAIN_HEADER_FOOTER) || !chrome.parentElement?.closest(SECTIONING_SELECTOR)) {
2622
+ return true;
2623
+ }
2624
+ chrome = chrome.parentElement?.closest(CHROME_SELECTOR) ?? null;
2625
+ }
2626
+ return false;
2627
+ };
2628
+ const showsOtherItem = (el) => {
2629
+ const choice = el.closest(OTHER_ITEM_SELECTOR);
2630
+ if (choice) return choice.getAttribute(OTHER_ITEM_ATTR) === "on";
2631
+ return !inSiteChrome(el);
2632
+ };
2591
2633
  const itemKind = (el) => {
2592
2634
  if (isCurrentItem(el)) return "current";
2593
2635
  if (!references) return "other";
2594
- return linkedIdOf(el) ? "referenced" : null;
2636
+ if (linkedIdOf(el)) return "referenced";
2637
+ return showsOtherItem(el) ? "other" : null;
2595
2638
  };
2596
2639
  const referenceFieldOf = (el) => {
2597
2640
  const linked = linkedIdOf(el);
@@ -2664,6 +2707,7 @@ function createVisualEditor(opts) {
2664
2707
  }
2665
2708
  return;
2666
2709
  }
2710
+ if (kind === "other" && (e2.ctrlKey || e2.metaKey || e2.shiftKey)) return;
2667
2711
  e2.preventDefault();
2668
2712
  e2.stopPropagation();
2669
2713
  const item = listItemIn(encoded, e2.target);
@@ -2671,11 +2715,13 @@ function createVisualEditor(opts) {
2671
2715
  select(item?.element ?? encoded, encoded, item?.index);
2672
2716
  const referenceField = kind === "referenced" ? referenceFieldOf(encoded) : void 0;
2673
2717
  const linkedId = kind === "referenced" ? linkedIdOf(encoded) : null;
2718
+ const linkHref = kind === "other" ? leavingLinkHref(e2.target) : null;
2674
2719
  opts.onEdit({
2675
2720
  ...readEditPayload(encoded),
2676
2721
  ...linkedId && { id: linkedId },
2677
2722
  ...item && { block: item.index },
2678
- ...referenceField && { referenceField }
2723
+ ...referenceField && { referenceField },
2724
+ ...linkHref && { linkHref }
2679
2725
  });
2680
2726
  };
2681
2727
  const handleKey = (e2) => {
@@ -2736,15 +2782,32 @@ function createVisualEditor(opts) {
2736
2782
  const el = block !== void 0 ? nthListItem(found, block) ?? found : found;
2737
2783
  select(el, found, block);
2738
2784
  }
2785
+ const hasAssets = () => assetIndex.size > 0 || removedAssets.size > 0;
2739
2786
  function tagAssets() {
2740
2787
  if (assetKeys.size > MAX_CACHED_KEYS) assetKeys.clear();
2741
- tagAssetImages(assetIndex, currentItemId, assetKeys, {
2742
- isCurrentItem: (el) => isCurrentItem(el)
2788
+ const showsRemoved = tagAssetImages(assetIndex, currentItemId, assetKeys, {
2789
+ isCurrentItem: (el) => isCurrentItem(el),
2790
+ removed: removedAssets
2743
2791
  });
2792
+ if (saved && !showsRemoved) forgetRemoved();
2793
+ }
2794
+ function forgetRemoved() {
2795
+ saved = false;
2796
+ if (savedTimeout) clearTimeout(savedTimeout);
2797
+ savedTimeout = null;
2798
+ if (!removedAssets.size) return;
2799
+ removedAssets.clear();
2800
+ if (running) tagAssets();
2801
+ }
2802
+ function itemSaved() {
2803
+ if (!removedAssets.size) return;
2804
+ saved = true;
2805
+ if (savedTimeout) clearTimeout(savedTimeout);
2806
+ savedTimeout = setTimeout(forgetRemoved, SAVED_FADE_MS);
2744
2807
  }
2745
2808
  const handleLayoutFrame = () => {
2746
2809
  mutationFrame = null;
2747
- if (assetIndex.size) tagAssets();
2810
+ if (hasAssets()) tagAssets();
2748
2811
  reselect();
2749
2812
  const hovered = overlay.getHoverTarget();
2750
2813
  if (hovered && !hovered.isConnected) {
@@ -2756,7 +2819,7 @@ function createVisualEditor(opts) {
2756
2819
  };
2757
2820
  const handleMutations = () => {
2758
2821
  if (mutationFrame !== null) return;
2759
- if (!selection && !overlay.getHoverTarget() && !assetIndex.size) return;
2822
+ if (!selection && !overlay.getHoverTarget() && !hasAssets()) return;
2760
2823
  mutationFrame = requestAnimationFrame(handleLayoutFrame);
2761
2824
  };
2762
2825
  function injectCursorStyles() {
@@ -2797,7 +2860,7 @@ function createVisualEditor(opts) {
2797
2860
  });
2798
2861
  mutationObserver = new MutationObserver(handleMutations);
2799
2862
  mutationObserver.observe(document.body, LAYOUT_MUTATIONS);
2800
- if (assetIndex.size) tagAssets();
2863
+ if (hasAssets()) tagAssets();
2801
2864
  syncGuard();
2802
2865
  debug7.log("visual editor started");
2803
2866
  }
@@ -2873,18 +2936,24 @@ function createVisualEditor(opts) {
2873
2936
  });
2874
2937
  }
2875
2938
  function setCurrentItem(itemId, referenceList, componentIds) {
2939
+ if (itemId !== currentItemId) forgetRemoved();
2876
2940
  currentItemId = itemId;
2877
2941
  if (referenceList) setReferences(referenceList, componentIds);
2878
2942
  else if (componentIds) ownComponents = new Set(componentIds);
2879
- if (running && assetIndex.size) tagAssets();
2943
+ if (running && hasAssets()) tagAssets();
2880
2944
  const selected = overlay.getSelectedTarget();
2881
2945
  const owner = selected ? encodedFrom(selected) : null;
2882
2946
  if (owner && !isCurrentItem(owner)) clearSelection();
2883
2947
  }
2884
2948
  function setAssets(assets) {
2885
- const hadAssets = assetIndex.size > 0;
2886
- assetIndex = createAssetIndex(assets);
2887
- if (running && (hadAssets || assetIndex.size)) tagAssets();
2949
+ const hadAssets = hasAssets();
2950
+ const next = createAssetIndex(assets);
2951
+ for (const [key, asset] of assetIndex) {
2952
+ if (!next.has(key)) removedAssets.set(key, asset);
2953
+ }
2954
+ for (const key of next.keys()) removedAssets.delete(key);
2955
+ assetIndex = next;
2956
+ if (running && (hadAssets || hasAssets())) tagAssets();
2888
2957
  }
2889
2958
  function setReferences(referenceList, componentIds) {
2890
2959
  if (componentIds) ownComponents = new Set(componentIds);
@@ -2910,6 +2979,7 @@ function createVisualEditor(opts) {
2910
2979
  setCurrentItem,
2911
2980
  setReferences,
2912
2981
  setAssets,
2982
+ itemSaved,
2913
2983
  setLocale,
2914
2984
  focusField,
2915
2985
  setNavigationGuard,
@@ -2996,7 +3066,8 @@ function createPreprPreview(opts = {}) {
2996
3066
  locale: contentLocale,
2997
3067
  block,
2998
3068
  entryId,
2999
- referenceField
3069
+ referenceField,
3070
+ linkHref
3000
3071
  }) => {
3001
3072
  const safeHref = href ? safeEditUrl(href) : null;
3002
3073
  if (href && !safeHref) {
@@ -3010,7 +3081,8 @@ function createPreprPreview(opts = {}) {
3010
3081
  locale: contentLocale,
3011
3082
  ...block !== void 0 && { block },
3012
3083
  ...entryId && { entryId },
3013
- ...referenceField && { referenceField }
3084
+ ...referenceField && { referenceField },
3085
+ ...linkHref && { linkHref }
3014
3086
  });
3015
3087
  } else if (safeHref) {
3016
3088
  window.open(safeHref, "_blank", "noopener,noreferrer");
@@ -3065,6 +3137,7 @@ function createPreprPreview(opts = {}) {
3065
3137
  },
3066
3138
  onAssets: (assets) => visualEditor?.setAssets(assets),
3067
3139
  onAllowNavigation: (href) => visualEditor?.allowNavigation(href),
3140
+ onItemChanged: () => visualEditor?.itemSaved(),
3068
3141
  onReferences: (referencedIds, componentIds) => visualEditor?.setReferences(referencedIds, componentIds),
3069
3142
  // The editor selecting a field scrolls the matching element into view. The
3070
3143
  // lookup relies on the attributes the stega scan writes, so it finds
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  VERSION,
3
3
  resolveFeatures
4
- } from "./chunk-F46GJS6Y.js";
4
+ } from "./chunk-HVTNUCF7.js";
5
5
 
6
6
  // src/core/server.ts
7
7
  var PreprError = class extends Error {