@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.mjs CHANGED
@@ -3019,7 +3019,7 @@ PropertyTour.displayName = "PropertyTour";
3019
3019
  var BREAKING_WINDOW_HOURS = 6;
3020
3020
  var BREAKING_MEMORY_CAP = 50;
3021
3021
  var BREAKING_MEMORY_STORAGE_KEY = "loaf:dismissed-breaking";
3022
- var emptyBreakingMemory = () => ({ dismissed: {}, pinned: {}, watermark: null });
3022
+ var emptyBreakingMemory = () => ({ dismissed: {}, blocked: {} });
3023
3023
  var isTimestampMap = (v) => typeof v === "object" && v !== null && !Array.isArray(v) && Object.values(v).every((t) => typeof t === "number" && Number.isFinite(t));
3024
3024
  var pruneMap = (map, cutoff) => {
3025
3025
  const fresh = Object.entries(map).filter(([, t]) => t >= cutoff);
@@ -3030,44 +3030,30 @@ var prune = (mem, now) => {
3030
3030
  const cutoff = now - BREAKING_WINDOW_HOURS * 36e5;
3031
3031
  return {
3032
3032
  dismissed: pruneMap(mem.dismissed, cutoff),
3033
- pinned: pruneMap(mem.pinned, cutoff),
3034
- // An expired watermark can't block anything (every in-window story is newer).
3035
- watermark: mem.watermark !== null && mem.watermark >= cutoff ? mem.watermark : null
3033
+ blocked: pruneMap(mem.blocked, cutoff)
3036
3034
  };
3037
3035
  };
3038
3036
  function parseBreakingMemory(raw, now) {
3039
3037
  if (!raw) return emptyBreakingMemory();
3040
3038
  try {
3041
3039
  const v = JSON.parse(raw);
3042
- 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))) {
3040
+ if (typeof v !== "object" || v === null || Array.isArray(v) || !isTimestampMap(v.dismissed)) {
3043
3041
  return emptyBreakingMemory();
3044
3042
  }
3045
- return prune({ dismissed: v.dismissed, pinned: v.pinned, watermark: v.watermark ?? null }, now);
3043
+ const blocked = isTimestampMap(v.blocked) ? v.blocked : {};
3044
+ return prune({ dismissed: v.dismissed, blocked }, now);
3046
3045
  } catch {
3047
3046
  return emptyBreakingMemory();
3048
3047
  }
3049
3048
  }
3050
3049
  var serializeBreakingMemory = (mem) => JSON.stringify(mem);
3051
- function recordBreakingDismissal(mem, key, publishedAtMs, now) {
3052
- return prune(
3053
- {
3054
- dismissed: { ...mem.dismissed, [key]: publishedAtMs },
3055
- pinned: mem.pinned,
3056
- watermark: Math.max(mem.watermark ?? -Infinity, publishedAtMs)
3057
- },
3058
- now
3059
- );
3050
+ function recordBreakingDismissal(mem, key, publishedAtMs, now, waiting = []) {
3051
+ const blocked = { ...mem.blocked };
3052
+ for (const w of waiting) blocked[w.key] = w.publishedAtMs;
3053
+ return prune({ dismissed: { ...mem.dismissed, [key]: publishedAtMs }, blocked }, now);
3060
3054
  }
3061
- function recordBreakingPins(mem, pins, now) {
3062
- if (pins.every((p) => mem.pinned[p.key] !== void 0)) return mem;
3063
- const pinned = { ...mem.pinned };
3064
- for (const p of pins) pinned[p.key] = p.publishedAtMs;
3065
- return prune({ dismissed: mem.dismissed, pinned, watermark: mem.watermark }, now);
3066
- }
3067
- function mayPinBreaking(mem, key, publishedAtMs) {
3068
- if (mem.dismissed[key] !== void 0) return false;
3069
- if (mem.watermark === null) return true;
3070
- return publishedAtMs > mem.watermark || mem.pinned[key] !== void 0;
3055
+ function mayPinBreaking(mem, key) {
3056
+ return mem.dismissed[key] === void 0 && mem.blocked[key] === void 0;
3071
3057
  }
3072
3058
  function loadBreakingMemory(now) {
3073
3059
  try {
@@ -3652,18 +3638,8 @@ var PropertyNewsUpdates = React9.forwardRef(
3652
3638
  };
3653
3639
  const pinnedBreaking = React9.useMemo(() => {
3654
3640
  const mem = getBreakingMemory();
3655
- return breakingEligible.filter((it) => mayPinBreaking(mem, breakingKey(it), breakingPublishedMs(it))).sort((a, b) => breakingImpact(b) - breakingImpact(a)).slice(0, MAX_PINNED_BREAKING);
3641
+ return breakingEligible.filter((it) => mayPinBreaking(mem, breakingKey(it))).sort((a, b) => breakingImpact(b) - breakingImpact(a)).slice(0, MAX_PINNED_BREAKING);
3656
3642
  }, [breakingEligible, dismissedVersion]);
3657
- React9.useEffect(() => {
3658
- if (pinnedBreaking.length === 0) return;
3659
- const mem = getBreakingMemory();
3660
- const next = recordBreakingPins(
3661
- mem,
3662
- pinnedBreaking.map((it) => ({ key: breakingKey(it), publishedAtMs: breakingPublishedMs(it) })),
3663
- Date.now()
3664
- );
3665
- if (next !== mem) commitBreakingMemory(next);
3666
- }, [pinnedBreaking]);
3667
3643
  const breakingRowKeys = React9.useMemo(
3668
3644
  () => new Set(breakingEligible.map(breakingKey)),
3669
3645
  [breakingEligible]
@@ -3769,8 +3745,10 @@ var PropertyNewsUpdates = React9.forwardRef(
3769
3745
  const k = breakingKey(item);
3770
3746
  const dx = breakingSwipe?.key === k ? breakingSwipe.dx : 0;
3771
3747
  const persistDismissal = () => {
3748
+ const holding = new Set(pinnedBreaking);
3749
+ const waiting = breakingEligible.filter((c) => !holding.has(c)).map((c) => ({ key: breakingKey(c), publishedAtMs: breakingPublishedMs(c) }));
3772
3750
  commitBreakingMemory(
3773
- recordBreakingDismissal(getBreakingMemory(), k, breakingPublishedMs(item), Date.now())
3751
+ recordBreakingDismissal(getBreakingMemory(), k, breakingPublishedMs(item), Date.now(), waiting)
3774
3752
  );
3775
3753
  };
3776
3754
  const finishDismiss = () => {
@@ -10797,6 +10775,7 @@ function GalleryMapSection({
10797
10775
  const [showVideo, setShowVideo] = useState(false);
10798
10776
  const [autoPlaying, setAutoPlaying] = useState(autoPlay);
10799
10777
  const [showFullMap, setShowFullMap] = useState(false);
10778
+ const [fullMapMountedFor, setFullMapMountedFor] = useState(null);
10800
10779
  const [showFullVideo, setShowFullVideo] = useState(false);
10801
10780
  const isMobile = useMaxWidth(768);
10802
10781
  const resolvedMapUrl = mapUrl ?? (tokenName ? `/map/${tokenName}?embed=true&zoom=14&hideOthers=true` : "about:blank");
@@ -10914,7 +10893,10 @@ function GalleryMapSection({
10914
10893
  ] }),
10915
10894
  /* @__PURE__ */ jsxs(MapHeaderRight, { children: [
10916
10895
  /* @__PURE__ */ jsx("span", { children: propertyLocation }),
10917
- /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => setShowFullMap(true), children: [
10896
+ /* @__PURE__ */ jsxs("button", { type: "button", onClick: () => {
10897
+ setFullMapMountedFor(resolvedMapUrl);
10898
+ setShowFullMap(true);
10899
+ }, children: [
10918
10900
  /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", style: { width: 14, height: 14 }, children: /* @__PURE__ */ 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" }) }),
10919
10901
  "Full Screen"
10920
10902
  ] })
@@ -10963,7 +10945,7 @@ function GalleryMapSection({
10963
10945
  ] }),
10964
10946
  /* @__PURE__ */ 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." })
10965
10947
  ] }),
10966
- showFullMap && !hideMap && /* @__PURE__ */ jsx(FullMapPopup, { onClose: () => setShowFullMap(false), mapUrl: resolvedMapUrl }),
10948
+ fullMapMountedFor === resolvedMapUrl && !hideMap && /* @__PURE__ */ jsx(FullMapPopup, { hidden: !showFullMap, onClose: () => setShowFullMap(false), mapUrl: resolvedMapUrl }),
10967
10949
  showFullVideo && videoUrl && /* @__PURE__ */ jsxs(FullVideoOverlay, { onClick: () => setShowFullVideo(false), children: [
10968
10950
  /* @__PURE__ */ jsx(FullVideoClose, { onClick: () => setShowFullVideo(false), "aria-label": "Close video", children: "\u2715" }),
10969
10951
  /* @__PURE__ */ jsx(
@@ -11093,21 +11075,23 @@ function StackedGallery({ images, onPhotoClick }) {
11093
11075
  }) })
11094
11076
  ] });
11095
11077
  }
11096
- function FullMapPopup({ onClose, mapUrl }) {
11078
+ function FullMapPopup({ onClose, mapUrl, hidden = false }) {
11097
11079
  useEffect(() => {
11080
+ if (hidden) return;
11098
11081
  const handleEsc = (e) => {
11099
11082
  if (e.key === "Escape") onClose();
11100
11083
  };
11101
11084
  window.addEventListener("keydown", handleEsc);
11102
11085
  return () => window.removeEventListener("keydown", handleEsc);
11103
- }, [onClose]);
11086
+ }, [onClose, hidden]);
11104
11087
  useEffect(() => {
11088
+ if (hidden) return;
11105
11089
  const handleMessage = (e) => {
11106
11090
  if (e.data === "loaf:close-embed") onClose();
11107
11091
  };
11108
11092
  window.addEventListener("message", handleMessage);
11109
11093
  return () => window.removeEventListener("message", handleMessage);
11110
- }, [onClose]);
11094
+ }, [onClose, hidden]);
11111
11095
  const iframeRef = useCallback((node) => {
11112
11096
  if (!node) return;
11113
11097
  const handleLoad = () => {
@@ -11123,7 +11107,7 @@ function FullMapPopup({ onClose, mapUrl }) {
11123
11107
  };
11124
11108
  node.addEventListener("load", handleLoad);
11125
11109
  }, [onClose]);
11126
- return /* @__PURE__ */ jsx(FullMapOverlay, { onClick: onClose, children: /* @__PURE__ */ jsxs(FullMapPanel, { onClick: (e) => e.stopPropagation(), children: [
11110
+ return /* @__PURE__ */ jsx(FullMapOverlay, { onClick: onClose, hidden, children: /* @__PURE__ */ jsxs(FullMapPanel, { onClick: (e) => e.stopPropagation(), children: [
11127
11111
  /* @__PURE__ */ jsxs(FullMapHeader, { children: [
11128
11112
  /* @__PURE__ */ jsxs(FullMapTitle, { children: [
11129
11113
  /* @__PURE__ */ jsx(BiMap, {}),
@@ -11419,6 +11403,12 @@ var FullMapOverlay = styled10.div`
11419
11403
  align-items: center;
11420
11404
  justify-content: center;
11421
11405
  backdrop-filter: blur(4px);
11406
+
11407
+ /* Kept mounted between opens (see fullMapMountedFor); \`display: flex\` above would
11408
+ otherwise beat the UA's [hidden] rule. */
11409
+ &[hidden] {
11410
+ display: none;
11411
+ }
11422
11412
  `;
11423
11413
  var FullVideoOverlay = styled10.div`
11424
11414
  position: fixed;