@loafmarkets/ui 0.1.426 → 0.1.428

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.
package/dist/index.js CHANGED
@@ -3044,7 +3044,7 @@ PropertyTour.displayName = "PropertyTour";
3044
3044
  var BREAKING_WINDOW_HOURS = 6;
3045
3045
  var BREAKING_MEMORY_CAP = 50;
3046
3046
  var BREAKING_MEMORY_STORAGE_KEY = "loaf:dismissed-breaking";
3047
- var emptyBreakingMemory = () => ({ dismissed: {}, pinned: {}, watermark: null });
3047
+ var emptyBreakingMemory = () => ({ dismissed: {}, blocked: {} });
3048
3048
  var isTimestampMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && Object.values(v).every((t) => typeof t === "number" && Number.isFinite(t));
3049
3049
  var pruneMap = (map, cutoff) => {
3050
3050
  const fresh = Object.entries(map).filter(([, t]) => t >= cutoff);
@@ -3055,44 +3055,30 @@ var prune = (mem, now) => {
3055
3055
  const cutoff = now - BREAKING_WINDOW_HOURS * 36e5;
3056
3056
  return {
3057
3057
  dismissed: pruneMap(mem.dismissed, cutoff),
3058
- pinned: pruneMap(mem.pinned, cutoff),
3059
- // An expired watermark can't block anything (every in-window story is newer).
3060
- watermark: mem.watermark !== null && mem.watermark >= cutoff ? mem.watermark : null
3058
+ blocked: pruneMap(mem.blocked, cutoff)
3061
3059
  };
3062
3060
  };
3063
3061
  function parseBreakingMemory(raw, now) {
3064
3062
  if (!raw) return emptyBreakingMemory();
3065
3063
  try {
3066
3064
  const v = JSON.parse(raw);
3067
- if (typeof v !== "object" || v === null || Array.isArray(v) || !isTimestampMap(v.dismissed) || !isTimestampMap(v.pinned) || !(v.watermark === null || typeof v.watermark === "number" && Number.isFinite(v.watermark))) {
3065
+ if (typeof v !== "object" || v === null || Array.isArray(v) || !isTimestampMap(v.dismissed)) {
3068
3066
  return emptyBreakingMemory();
3069
3067
  }
3070
- return prune({ dismissed: v.dismissed, pinned: v.pinned, watermark: v.watermark ?? null }, now);
3068
+ const blocked = isTimestampMap(v.blocked) ? v.blocked : {};
3069
+ return prune({ dismissed: v.dismissed, blocked }, now);
3071
3070
  } catch {
3072
3071
  return emptyBreakingMemory();
3073
3072
  }
3074
3073
  }
3075
3074
  var serializeBreakingMemory = (mem) => JSON.stringify(mem);
3076
- function recordBreakingDismissal(mem, key, publishedAtMs, now) {
3077
- return prune(
3078
- {
3079
- dismissed: { ...mem.dismissed, [key]: publishedAtMs },
3080
- pinned: mem.pinned,
3081
- watermark: Math.max(mem.watermark ?? -Infinity, publishedAtMs)
3082
- },
3083
- now
3084
- );
3075
+ function recordBreakingDismissal(mem, key, publishedAtMs, now, waiting = []) {
3076
+ const blocked = { ...mem.blocked };
3077
+ for (const w of waiting) blocked[w.key] = w.publishedAtMs;
3078
+ return prune({ dismissed: { ...mem.dismissed, [key]: publishedAtMs }, blocked }, now);
3085
3079
  }
3086
- function recordBreakingPins(mem, pins, now) {
3087
- if (pins.every((p) => mem.pinned[p.key] !== void 0)) return mem;
3088
- const pinned = { ...mem.pinned };
3089
- for (const p of pins) pinned[p.key] = p.publishedAtMs;
3090
- return prune({ dismissed: mem.dismissed, pinned, watermark: mem.watermark }, now);
3091
- }
3092
- function mayPinBreaking(mem, key, publishedAtMs) {
3093
- if (mem.dismissed[key] !== void 0) return false;
3094
- if (mem.watermark === null) return true;
3095
- return publishedAtMs > mem.watermark || mem.pinned[key] !== void 0;
3080
+ function mayPinBreaking(mem, key) {
3081
+ return mem.dismissed[key] === void 0 && mem.blocked[key] === void 0;
3096
3082
  }
3097
3083
  function loadBreakingMemory(now) {
3098
3084
  try {
@@ -3677,18 +3663,8 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
3677
3663
  };
3678
3664
  const pinnedBreaking = React9__namespace.useMemo(() => {
3679
3665
  const mem = getBreakingMemory();
3680
- return breakingEligible.filter((it) => mayPinBreaking(mem, breakingKey(it), breakingPublishedMs(it))).sort((a, b) => breakingImpact(b) - breakingImpact(a)).slice(0, MAX_PINNED_BREAKING);
3666
+ return breakingEligible.filter((it) => mayPinBreaking(mem, breakingKey(it))).sort((a, b) => breakingImpact(b) - breakingImpact(a)).slice(0, MAX_PINNED_BREAKING);
3681
3667
  }, [breakingEligible, dismissedVersion]);
3682
- React9__namespace.useEffect(() => {
3683
- if (pinnedBreaking.length === 0) return;
3684
- const mem = getBreakingMemory();
3685
- const next = recordBreakingPins(
3686
- mem,
3687
- pinnedBreaking.map((it) => ({ key: breakingKey(it), publishedAtMs: breakingPublishedMs(it) })),
3688
- Date.now()
3689
- );
3690
- if (next !== mem) commitBreakingMemory(next);
3691
- }, [pinnedBreaking]);
3692
3668
  const breakingRowKeys = React9__namespace.useMemo(
3693
3669
  () => new Set(breakingEligible.map(breakingKey)),
3694
3670
  [breakingEligible]
@@ -3794,8 +3770,10 @@ var PropertyNewsUpdates = React9__namespace.forwardRef(
3794
3770
  const k = breakingKey(item);
3795
3771
  const dx = breakingSwipe?.key === k ? breakingSwipe.dx : 0;
3796
3772
  const persistDismissal = () => {
3773
+ const holding = new Set(pinnedBreaking);
3774
+ const waiting = breakingEligible.filter((c) => !holding.has(c)).map((c) => ({ key: breakingKey(c), publishedAtMs: breakingPublishedMs(c) }));
3797
3775
  commitBreakingMemory(
3798
- recordBreakingDismissal(getBreakingMemory(), k, breakingPublishedMs(item), Date.now())
3776
+ recordBreakingDismissal(getBreakingMemory(), k, breakingPublishedMs(item), Date.now(), waiting)
3799
3777
  );
3800
3778
  };
3801
3779
  const finishDismiss = () => {
@@ -10822,6 +10800,7 @@ function GalleryMapSection({
10822
10800
  const [showVideo, setShowVideo] = React9.useState(false);
10823
10801
  const [autoPlaying, setAutoPlaying] = React9.useState(autoPlay);
10824
10802
  const [showFullMap, setShowFullMap] = React9.useState(false);
10803
+ const [fullMapMountedFor, setFullMapMountedFor] = React9.useState(null);
10825
10804
  const [showFullVideo, setShowFullVideo] = React9.useState(false);
10826
10805
  const isMobile = useMaxWidth(768);
10827
10806
  const resolvedMapUrl = mapUrl ?? (tokenName ? `/map/${tokenName}?embed=true&zoom=14&hideOthers=true` : "about:blank");
@@ -10939,7 +10918,10 @@ function GalleryMapSection({
10939
10918
  ] }),
10940
10919
  /* @__PURE__ */ jsxRuntime.jsxs(MapHeaderRight, { children: [
10941
10920
  /* @__PURE__ */ jsxRuntime.jsx("span", { children: propertyLocation }),
10942
- /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => setShowFullMap(true), children: [
10921
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { type: "button", onClick: () => {
10922
+ setFullMapMountedFor(resolvedMapUrl);
10923
+ setShowFullMap(true);
10924
+ }, children: [
10943
10925
  /* @__PURE__ */ jsxRuntime.jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { width: 14, height: 14 }, children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" }) }),
10944
10926
  "Full Screen"
10945
10927
  ] })
@@ -10988,7 +10970,7 @@ function GalleryMapSection({
10988
10970
  ] }),
10989
10971
  /* @__PURE__ */ jsxRuntime.jsx(SuburbDesc, { children: "One of Sydney's most prestigious harbourside suburbs, consistently ranked among Australia's highest-value residential markets. Strong capital growth driven by limited supply, heritage conservation overlays, and international buyer demand." })
10990
10972
  ] }),
10991
- showFullMap && !hideMap && /* @__PURE__ */ jsxRuntime.jsx(FullMapPopup, { onClose: () => setShowFullMap(false), mapUrl: resolvedMapUrl }),
10973
+ fullMapMountedFor === resolvedMapUrl && !hideMap && /* @__PURE__ */ jsxRuntime.jsx(FullMapPopup, { hidden: !showFullMap, onClose: () => setShowFullMap(false), mapUrl: resolvedMapUrl }),
10992
10974
  showFullVideo && videoUrl && /* @__PURE__ */ jsxRuntime.jsxs(FullVideoOverlay, { onClick: () => setShowFullVideo(false), children: [
10993
10975
  /* @__PURE__ */ jsxRuntime.jsx(FullVideoClose, { onClick: () => setShowFullVideo(false), "aria-label": "Close video", children: "\u2715" }),
10994
10976
  /* @__PURE__ */ jsxRuntime.jsx(
@@ -11118,21 +11100,23 @@ function StackedGallery({ images, onPhotoClick }) {
11118
11100
  }) })
11119
11101
  ] });
11120
11102
  }
11121
- function FullMapPopup({ onClose, mapUrl }) {
11103
+ function FullMapPopup({ onClose, mapUrl, hidden = false }) {
11122
11104
  React9.useEffect(() => {
11105
+ if (hidden) return;
11123
11106
  const handleEsc = (e) => {
11124
11107
  if (e.key === "Escape") onClose();
11125
11108
  };
11126
11109
  window.addEventListener("keydown", handleEsc);
11127
11110
  return () => window.removeEventListener("keydown", handleEsc);
11128
- }, [onClose]);
11111
+ }, [onClose, hidden]);
11129
11112
  React9.useEffect(() => {
11113
+ if (hidden) return;
11130
11114
  const handleMessage = (e) => {
11131
11115
  if (e.data === "loaf:close-embed") onClose();
11132
11116
  };
11133
11117
  window.addEventListener("message", handleMessage);
11134
11118
  return () => window.removeEventListener("message", handleMessage);
11135
- }, [onClose]);
11119
+ }, [onClose, hidden]);
11136
11120
  const iframeRef = React9.useCallback((node) => {
11137
11121
  if (!node) return;
11138
11122
  const handleLoad = () => {
@@ -11148,7 +11132,7 @@ function FullMapPopup({ onClose, mapUrl }) {
11148
11132
  };
11149
11133
  node.addEventListener("load", handleLoad);
11150
11134
  }, [onClose]);
11151
- return /* @__PURE__ */ jsxRuntime.jsx(FullMapOverlay, { onClick: onClose, children: /* @__PURE__ */ jsxRuntime.jsxs(FullMapPanel, { onClick: (e) => e.stopPropagation(), children: [
11135
+ return /* @__PURE__ */ jsxRuntime.jsx(FullMapOverlay, { onClick: onClose, hidden, children: /* @__PURE__ */ jsxRuntime.jsxs(FullMapPanel, { onClick: (e) => e.stopPropagation(), children: [
11152
11136
  /* @__PURE__ */ jsxRuntime.jsxs(FullMapHeader, { children: [
11153
11137
  /* @__PURE__ */ jsxRuntime.jsxs(FullMapTitle, { children: [
11154
11138
  /* @__PURE__ */ jsxRuntime.jsx(bi.BiMap, {}),
@@ -11444,6 +11428,12 @@ var FullMapOverlay = styled10__default.default.div`
11444
11428
  align-items: center;
11445
11429
  justify-content: center;
11446
11430
  backdrop-filter: blur(4px);
11431
+
11432
+ /* Kept mounted between opens (see fullMapMountedFor); \`display: flex\` above would
11433
+ otherwise beat the UA's [hidden] rule. */
11434
+ &[hidden] {
11435
+ display: none;
11436
+ }
11447
11437
  `;
11448
11438
  var FullVideoOverlay = styled10__default.default.div`
11449
11439
  position: fixed;