@sesionario/blocks 1.41.0-rc.7 → 1.41.0-rc.9

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.cjs CHANGED
@@ -1174,31 +1174,54 @@ function useZoomPanGestures(options = {}) {
1174
1174
  clearSnapBack
1175
1175
  };
1176
1176
  }
1177
+ var STATIC_BLUR_FILTER = "blur(20px)";
1177
1178
  function isBrokenImage(img) {
1178
1179
  return img.complete && img.naturalWidth === 0;
1179
1180
  }
1181
+ function resolveBlurDataUrl(value) {
1182
+ return typeof value === "string" && value.length > 0 ? value : null;
1183
+ }
1180
1184
  function SafeImage({
1181
1185
  src,
1182
1186
  fallback,
1183
1187
  alt = "",
1184
1188
  onError,
1189
+ onLoad,
1190
+ blurDataUrl,
1191
+ style,
1185
1192
  ...imgProps
1186
1193
  }) {
1187
1194
  const [failed, setFailed] = React2.useState(false);
1195
+ const [loaded, setLoaded] = React2.useState(false);
1188
1196
  const imgRef = React2.useRef(null);
1189
1197
  const resolvedSrc = typeof src === "string" && src.length > 0 ? src : null;
1198
+ const resolvedBlur = resolveBlurDataUrl(blurDataUrl);
1190
1199
  React2.useLayoutEffect(() => {
1191
1200
  setFailed(false);
1201
+ setLoaded(false);
1192
1202
  }, [resolvedSrc]);
1193
1203
  React2.useLayoutEffect(() => {
1194
1204
  const img = imgRef.current;
1195
- if (img && isBrokenImage(img)) {
1205
+ if (!img) return;
1206
+ if (isBrokenImage(img)) {
1196
1207
  setFailed(true);
1208
+ return;
1197
1209
  }
1198
- }, [resolvedSrc, failed]);
1210
+ if (resolvedBlur && img.complete && img.naturalWidth > 0) {
1211
+ setLoaded(true);
1212
+ }
1213
+ }, [resolvedSrc, failed, resolvedBlur]);
1199
1214
  if (!resolvedSrc || failed) {
1200
1215
  return /* @__PURE__ */ jsxRuntime.jsx(jsxRuntime.Fragment, { children: fallback });
1201
1216
  }
1217
+ const paintedStyle = resolvedBlur === null ? style : {
1218
+ ...style,
1219
+ backgroundImage: `url(${JSON.stringify(resolvedBlur)})`,
1220
+ backgroundSize: "cover",
1221
+ backgroundPosition: "center",
1222
+ backgroundRepeat: "no-repeat",
1223
+ ...loaded || prefersReducedMotion() ? {} : { filter: STATIC_BLUR_FILTER }
1224
+ };
1202
1225
  return /* @__PURE__ */ jsxRuntime.jsx(
1203
1226
  "img",
1204
1227
  {
@@ -1206,6 +1229,11 @@ function SafeImage({
1206
1229
  ref: imgRef,
1207
1230
  src: resolvedSrc,
1208
1231
  alt,
1232
+ style: paintedStyle,
1233
+ onLoad: (event) => {
1234
+ if (resolvedBlur) setLoaded(true);
1235
+ onLoad?.(event);
1236
+ },
1209
1237
  onError: (event) => {
1210
1238
  setFailed(true);
1211
1239
  onError?.(event);
@@ -1215,6 +1243,7 @@ function SafeImage({
1215
1243
  }
1216
1244
  function ImageLightbox({
1217
1245
  images,
1246
+ blurDataUrls,
1218
1247
  index,
1219
1248
  onIndexChange,
1220
1249
  onClose,
@@ -1340,6 +1369,7 @@ function ImageLightbox({
1340
1369
  {
1341
1370
  src: images[index],
1342
1371
  alt: "",
1372
+ blurDataUrl: blurDataUrls?.[index] ?? null,
1343
1373
  draggable: false,
1344
1374
  className: "max-h-[calc(100dvh-2rem)] max-w-full select-none rounded-lg object-contain",
1345
1375
  style: {
@@ -1472,6 +1502,7 @@ function MediaCarousel({
1472
1502
  {
1473
1503
  src: item.src,
1474
1504
  alt: item.alt ?? "",
1505
+ blurDataUrl: item.blurDataUrl,
1475
1506
  draggable: false,
1476
1507
  className: "max-h-full max-w-full select-none object-contain",
1477
1508
  style: {
@@ -1707,6 +1738,79 @@ function MediaViewerDialog({
1707
1738
  }
1708
1739
  ) });
1709
1740
  }
1741
+
1742
+ // src/components/media/blurDataUrl.ts
1743
+ var BLUR_LONG_EDGE_PX = 16;
1744
+ var BLUR_DATA_URL_MAX_CHARS = 1024;
1745
+ var JPEG_DATA_URL_PREFIX = "data:image/jpeg;base64,";
1746
+ var JPEG_QUALITIES = [0.7, 0.5, 0.35, 0.2];
1747
+ function longEdgeSteps(start) {
1748
+ const steps = [];
1749
+ let edge = start;
1750
+ while (edge >= 2) {
1751
+ steps.push(edge);
1752
+ edge = Math.floor(edge / 2);
1753
+ }
1754
+ return steps;
1755
+ }
1756
+ function sizeForLongEdge(width, height, longEdge) {
1757
+ const longest = Math.max(width, height);
1758
+ const scale = longEdge / longest;
1759
+ return {
1760
+ width: Math.max(1, Math.round(width * scale)),
1761
+ height: Math.max(1, Math.round(height * scale))
1762
+ };
1763
+ }
1764
+ function acceptableBlurDataUrl(value) {
1765
+ return value.startsWith(JPEG_DATA_URL_PREFIX) && value.length > JPEG_DATA_URL_PREFIX.length && value.length <= BLUR_DATA_URL_MAX_CHARS;
1766
+ }
1767
+ async function decodeBlob(blob) {
1768
+ if (typeof createImageBitmap !== "function") {
1769
+ throw new Error("createImageBitmap unavailable");
1770
+ }
1771
+ const bitmap = await createImageBitmap(blob);
1772
+ return {
1773
+ source: bitmap,
1774
+ width: bitmap.width,
1775
+ height: bitmap.height,
1776
+ release() {
1777
+ bitmap.close();
1778
+ }
1779
+ };
1780
+ }
1781
+ async function blurDataUrlFromBlob(blob) {
1782
+ if (typeof document === "undefined") return null;
1783
+ let decoded;
1784
+ try {
1785
+ decoded = await decodeBlob(blob);
1786
+ } catch {
1787
+ return null;
1788
+ }
1789
+ try {
1790
+ if (Math.min(decoded.width, decoded.height) <= 0) return null;
1791
+ const canvas = document.createElement("canvas");
1792
+ for (const edge of longEdgeSteps(BLUR_LONG_EDGE_PX)) {
1793
+ const size = sizeForLongEdge(decoded.width, decoded.height, edge);
1794
+ canvas.width = size.width;
1795
+ canvas.height = size.height;
1796
+ const context = canvas.getContext("2d");
1797
+ if (!context) return null;
1798
+ context.drawImage(decoded.source, 0, 0, size.width, size.height);
1799
+ for (const quality of JPEG_QUALITIES) {
1800
+ let url;
1801
+ try {
1802
+ url = canvas.toDataURL("image/jpeg", quality);
1803
+ } catch {
1804
+ return null;
1805
+ }
1806
+ if (acceptableBlurDataUrl(url)) return url;
1807
+ }
1808
+ }
1809
+ return null;
1810
+ } finally {
1811
+ decoded.release();
1812
+ }
1813
+ }
1710
1814
  var miniMenuContentClassName = "max-h-none overflow-x-visible overflow-y-visible";
1711
1815
  var miniMenuContentDefaults = {
1712
1816
  sideOffset: 8,
@@ -9104,6 +9208,7 @@ exports.ZOOM_PAN_MIN_SCALE = ZOOM_PAN_MIN_SCALE;
9104
9208
  exports.ZOOM_PAN_SWIPE_COMMIT_PX = ZOOM_PAN_SWIPE_COMMIT_PX;
9105
9209
  exports.ZOOM_PAN_SWIPE_SNAP_MS = ZOOM_PAN_SWIPE_SNAP_MS;
9106
9210
  exports.avatarFallbackStyle = avatarFallbackStyle;
9211
+ exports.blurDataUrlFromBlob = blurDataUrlFromBlob;
9107
9212
  exports.bubbleVariants = bubbleVariants;
9108
9213
  exports.cn = cn;
9109
9214
  exports.createSupportChatClient = createSupportChatClient;