@sesionario/blocks 1.41.0-rc.6 → 1.41.0-rc.8

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;
1209
+ }
1210
+ if (resolvedBlur && img.complete && img.naturalWidth > 0) {
1211
+ setLoaded(true);
1197
1212
  }
1198
- }, [resolvedSrc, failed]);
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,
@@ -5618,6 +5722,7 @@ function matchesQuery(entry, query, getOptionKeywords) {
5618
5722
  function SearchablePicker(props) {
5619
5723
  const {
5620
5724
  options,
5725
+ selectedItems: selectedItemsProp,
5621
5726
  fetchOptions,
5622
5727
  getOptionValue,
5623
5728
  getOptionLabel,
@@ -5676,11 +5781,16 @@ function SearchablePicker(props) {
5676
5781
  },
5677
5782
  [getOptionValue]
5678
5783
  );
5784
+ if (options?.length) rememberItems(options);
5785
+ if (selectedItemsProp?.length) rememberItems(selectedItemsProp);
5786
+ if (pinned?.recommended?.length) rememberItems(pinned.recommended);
5787
+ if (pinned?.recent?.length) rememberItems(pinned.recent);
5679
5788
  React2.useEffect(() => {
5680
5789
  if (options) rememberItems(options);
5790
+ if (selectedItemsProp) rememberItems(selectedItemsProp);
5681
5791
  if (pinned?.recommended) rememberItems(pinned.recommended);
5682
5792
  if (pinned?.recent) rememberItems(pinned.recent);
5683
- }, [options, pinned, rememberItems]);
5793
+ }, [options, selectedItemsProp, pinned, rememberItems]);
5684
5794
  React2.useEffect(() => {
5685
5795
  if (!open) {
5686
5796
  setQuery("");
@@ -5728,7 +5838,7 @@ function SearchablePicker(props) {
5728
5838
  const selectedItems = React2.useMemo(() => {
5729
5839
  const values = mode === "multiple" ? selectedValueKey ? selectedValueKey.split("\0") : [] : selectedValueKey ? [selectedValueKey] : [];
5730
5840
  return values.map((value) => knownItemsRef.current.get(value)).filter((item) => item !== void 0);
5731
- }, [mode, selectedValueKey, options, pinned, remoteItems]);
5841
+ }, [mode, selectedValueKey, options, selectedItemsProp, pinned, remoteItems]);
5732
5842
  const queryForFilter = query.trim();
5733
5843
  const recommended = React2.useMemo(() => {
5734
5844
  if (queryForFilter) return [];
@@ -5832,13 +5942,27 @@ function SearchablePicker(props) {
5832
5942
  );
5833
5943
  const handleLoadMore = React2.useCallback(async () => {
5834
5944
  if (!onLoadMore || isLoadingMore) return;
5945
+ const requestId = requestIdRef.current;
5835
5946
  setIsLoadingMore(true);
5836
5947
  try {
5837
- await onLoadMore();
5948
+ const more = await onLoadMore();
5949
+ if (requestIdRef.current !== requestId || !Array.isArray(more)) return;
5950
+ setRemoteItems((prev) => {
5951
+ const seen = new Set(prev.map((item) => getOptionValue(item)));
5952
+ const next = [...prev];
5953
+ for (const item of more) {
5954
+ const value = getOptionValue(item);
5955
+ if (seen.has(value)) continue;
5956
+ seen.add(value);
5957
+ next.push(item);
5958
+ }
5959
+ return next;
5960
+ });
5961
+ rememberItems(more);
5838
5962
  } finally {
5839
- setIsLoadingMore(false);
5963
+ if (requestIdRef.current === requestId) setIsLoadingMore(false);
5840
5964
  }
5841
- }, [onLoadMore, isLoadingMore]);
5965
+ }, [onLoadMore, isLoadingMore, getOptionValue, rememberItems]);
5842
5966
  const triggerLabel = React2.useMemo(() => {
5843
5967
  if (renderValue) {
5844
5968
  if (mode === "multiple") {
@@ -5972,7 +6096,7 @@ function SearchablePicker(props) {
5972
6096
  ] });
5973
6097
  }
5974
6098
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex w-full items-center gap-1", className), children: [
5975
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, modal: true, children: [
6099
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxRuntime.jsxs(Popover, { open, onOpenChange: setOpen, modal: false, children: [
5976
6100
  /* @__PURE__ */ jsxRuntime.jsx(PopoverTrigger, { asChild: true, children: triggerButton }),
5977
6101
  /* @__PURE__ */ jsxRuntime.jsx(
5978
6102
  PopoverContent,
@@ -9084,6 +9208,7 @@ exports.ZOOM_PAN_MIN_SCALE = ZOOM_PAN_MIN_SCALE;
9084
9208
  exports.ZOOM_PAN_SWIPE_COMMIT_PX = ZOOM_PAN_SWIPE_COMMIT_PX;
9085
9209
  exports.ZOOM_PAN_SWIPE_SNAP_MS = ZOOM_PAN_SWIPE_SNAP_MS;
9086
9210
  exports.avatarFallbackStyle = avatarFallbackStyle;
9211
+ exports.blurDataUrlFromBlob = blurDataUrlFromBlob;
9087
9212
  exports.bubbleVariants = bubbleVariants;
9088
9213
  exports.cn = cn;
9089
9214
  exports.createSupportChatClient = createSupportChatClient;