@clyrex-digital/clyrex-controls-dev 0.8.1-dev.20260827101843 → 0.8.1-dev.20260829044749

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.
@@ -12,6 +12,56 @@ import {
12
12
  import React4 from "react";
13
13
 
14
14
  // src/components/controls/view/commentImages.ts
15
+ var INDIVIDUAL_IMAGE_THRESHOLD = 1;
16
+ var MAX_VISIBLE_GALLERY_IMAGES = 4;
17
+ var isImageNode = (node) => !!node && !isTextNode(node) && asElementNode(node).type === "image";
18
+ var flattenText = (node) => {
19
+ if (!node) return "";
20
+ if (isTextNode(node)) return node.text ?? "";
21
+ return (asElementNode(node).children ?? []).map(flattenText).join("");
22
+ };
23
+ var isBlankFiller = (node) => {
24
+ if (!node || isTextNode(node)) return false;
25
+ const element = asElementNode(node);
26
+ if (element.type === "linebreak" || element.type === "line-break") return true;
27
+ if (element.type === "paragraph") return flattenText(node).trim() === "";
28
+ return false;
29
+ };
30
+ var groupConsecutiveImages = (nodes) => {
31
+ const groups = [];
32
+ let i = 0;
33
+ while (i < nodes.length) {
34
+ const node = nodes[i];
35
+ if (!isImageNode(node)) {
36
+ groups.push({ kind: "other", node });
37
+ i += 1;
38
+ continue;
39
+ }
40
+ const runImages = [asElementNode(node)];
41
+ let cursor = i + 1;
42
+ while (cursor < nodes.length) {
43
+ if (isImageNode(nodes[cursor])) {
44
+ runImages.push(asElementNode(nodes[cursor]));
45
+ cursor += 1;
46
+ continue;
47
+ }
48
+ if (isBlankFiller(nodes[cursor])) {
49
+ let peek = cursor + 1;
50
+ while (peek < nodes.length && isBlankFiller(nodes[peek])) {
51
+ peek += 1;
52
+ }
53
+ if (peek < nodes.length && isImageNode(nodes[peek])) {
54
+ cursor = peek;
55
+ continue;
56
+ }
57
+ }
58
+ break;
59
+ }
60
+ groups.push({ kind: "images", nodes: runImages });
61
+ i = cursor;
62
+ }
63
+ return groups;
64
+ };
15
65
  var pickUrl = (node) => typeof node.url === "string" && node.url || typeof node.src === "string" && node.src || typeof node.assetUrl === "string" && node.assetUrl || "";
16
66
  var pickAlt = (node) => typeof node.fileName === "string" && node.fileName || typeof node.altText === "string" && node.altText || typeof node.alt === "string" && node.alt || "";
17
67
  var resolveCommentImageUrl = (node, assetBaseUrl) => {
@@ -279,9 +329,12 @@ var CommentImageNode = ({ node, assetBaseUrl }) => {
279
329
  loading: "lazy",
280
330
  onClick: clickable ? () => preview?.openPreview(resolvedUrl) : void 0,
281
331
  style: {
282
- maxWidth: "100%",
283
- height: "auto",
284
332
  display: "block",
333
+ width: "auto",
334
+ height: "auto",
335
+ objectFit: "cover",
336
+ borderRadius: "0.375rem",
337
+ margin: "0.5rem 0",
285
338
  cursor: clickable ? "zoom-in" : "default"
286
339
  }
287
340
  }
@@ -768,8 +821,83 @@ var CommentNodeRenderer = ({ node, assetBaseUrl }) => {
768
821
  };
769
822
  var CommentNodeRenderer_default = CommentNodeRenderer;
770
823
 
824
+ // src/components/controls/view/CommentImageGallery.tsx
825
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
826
+ var gridStyle = {
827
+ display: "grid",
828
+ gridTemplateColumns: "repeat(2, 1fr)",
829
+ gap: "2px",
830
+ borderRadius: "0.375rem",
831
+ overflow: "hidden",
832
+ margin: "0.5rem 0"
833
+ };
834
+ var cellStyle = {
835
+ position: "relative",
836
+ display: "block",
837
+ aspectRatio: "1",
838
+ width: "100%",
839
+ padding: 0,
840
+ border: "none",
841
+ background: "none",
842
+ cursor: "zoom-in",
843
+ overflow: "hidden"
844
+ };
845
+ var cellImgStyle = {
846
+ display: "block",
847
+ width: "100%",
848
+ height: "100%",
849
+ objectFit: "cover"
850
+ };
851
+ var moreOverlayStyle = {
852
+ position: "absolute",
853
+ inset: 0,
854
+ display: "flex",
855
+ alignItems: "center",
856
+ justifyContent: "center",
857
+ background: "rgba(0, 0, 0, 0.5)",
858
+ color: "#fff",
859
+ fontSize: "1.25rem",
860
+ fontWeight: 600
861
+ };
862
+ var CommentImageGallery = ({ nodes, assetBaseUrl }) => {
863
+ const preview = useCommentImagePreview();
864
+ const cells = nodes.map((node) => ({
865
+ id: typeof node.id === "string" && node.id || void 0,
866
+ src: resolveCommentImageUrl(node, assetBaseUrl),
867
+ alt: typeof node.fileName === "string" && node.fileName || typeof node.alt === "string" && node.alt || ""
868
+ })).filter((cell) => !!cell.src);
869
+ if (cells.length === 0) {
870
+ return null;
871
+ }
872
+ const visibleCount = Math.min(cells.length, MAX_VISIBLE_GALLERY_IMAGES);
873
+ const remaining = cells.length - MAX_VISIBLE_GALLERY_IMAGES;
874
+ return /* @__PURE__ */ jsx7("div", { className: "comment-image-gallery", style: gridStyle, children: cells.slice(0, visibleCount).map((cell, i) => {
875
+ const isLastVisible = i === visibleCount - 1;
876
+ const showMoreOverlay = isLastVisible && remaining > 0;
877
+ return /* @__PURE__ */ jsxs6(
878
+ "button",
879
+ {
880
+ type: "button",
881
+ className: "comment-image-gallery-cell",
882
+ style: cellStyle,
883
+ "aria-label": showMoreOverlay ? `Show ${remaining} more photo${remaining === 1 ? "" : "s"}` : cell.alt || "View image",
884
+ onClick: () => preview?.openPreview(cell.src),
885
+ children: [
886
+ /* @__PURE__ */ jsx7("img", { src: cell.src, alt: cell.alt, loading: "lazy", style: cellImgStyle }),
887
+ showMoreOverlay ? /* @__PURE__ */ jsxs6("span", { className: "comment-image-gallery-more-overlay", style: moreOverlayStyle, children: [
888
+ "+",
889
+ remaining
890
+ ] }) : null
891
+ ]
892
+ },
893
+ cell.id ?? `comment-gallery-${i}`
894
+ );
895
+ }) });
896
+ };
897
+ var CommentImageGallery_default = CommentImageGallery;
898
+
771
899
  // src/components/controls/view/CommentRendererClient.tsx
772
- import { jsx as jsx7 } from "react/jsx-runtime";
900
+ import { jsx as jsx8 } from "react/jsx-runtime";
773
901
  var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) => {
774
902
  const nodes = React4.useMemo(() => parseSerializedContent(value), [value]);
775
903
  const resolvedAssetBaseUrl = assetBaseUrl ?? apiBaseUrl;
@@ -777,17 +905,42 @@ var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) =>
777
905
  () => collectCommentImages(nodes, resolvedAssetBaseUrl),
778
906
  [nodes, resolvedAssetBaseUrl]
779
907
  );
908
+ const groups = React4.useMemo(() => groupConsecutiveImages(nodes), [nodes]);
780
909
  if (!nodes || nodes.length === 0) {
781
910
  return null;
782
911
  }
783
- return /* @__PURE__ */ jsx7(CommentImagePreviewProvider, { images, children: /* @__PURE__ */ jsx7("div", { className: className ?? "comment-renderer", children: nodes.map((node, index) => /* @__PURE__ */ jsx7(
784
- CommentNodeRenderer_default,
785
- {
786
- node,
787
- assetBaseUrl: resolvedAssetBaseUrl
788
- },
789
- node.id ?? `comment-node-${index}`
790
- )) }) });
912
+ return /* @__PURE__ */ jsx8(CommentImagePreviewProvider, { images, children: /* @__PURE__ */ jsx8("div", { className: className ?? "comment-renderer", children: groups.map((group, groupIndex) => {
913
+ if (group.kind === "images") {
914
+ const key = group.nodes[0]?.id ?? `comment-group-${groupIndex}`;
915
+ if (group.nodes.length <= INDIVIDUAL_IMAGE_THRESHOLD) {
916
+ return group.nodes.map((node2, i) => /* @__PURE__ */ jsx8(
917
+ CommentNodeRenderer_default,
918
+ {
919
+ node: node2,
920
+ assetBaseUrl: resolvedAssetBaseUrl
921
+ },
922
+ node2.id ?? `${key}-${i}`
923
+ ));
924
+ }
925
+ return /* @__PURE__ */ jsx8(
926
+ CommentImageGallery_default,
927
+ {
928
+ nodes: group.nodes,
929
+ assetBaseUrl: resolvedAssetBaseUrl
930
+ },
931
+ key
932
+ );
933
+ }
934
+ const node = group.node;
935
+ return /* @__PURE__ */ jsx8(
936
+ CommentNodeRenderer_default,
937
+ {
938
+ node,
939
+ assetBaseUrl: resolvedAssetBaseUrl
940
+ },
941
+ node.id ?? `comment-group-${groupIndex}`
942
+ );
943
+ }) }) });
791
944
  };
792
945
  var CommentRendererClient_default = CommentRendererClient;
793
946
  export {
@@ -14,6 +14,56 @@ import {
14
14
  import React4 from "react";
15
15
 
16
16
  // src/components/controls/view/commentImages.ts
17
+ var INDIVIDUAL_IMAGE_THRESHOLD = 1;
18
+ var MAX_VISIBLE_GALLERY_IMAGES = 4;
19
+ var isImageNode = (node) => !!node && !isTextNode(node) && asElementNode(node).type === "image";
20
+ var flattenText = (node) => {
21
+ if (!node) return "";
22
+ if (isTextNode(node)) return node.text ?? "";
23
+ return (asElementNode(node).children ?? []).map(flattenText).join("");
24
+ };
25
+ var isBlankFiller = (node) => {
26
+ if (!node || isTextNode(node)) return false;
27
+ const element = asElementNode(node);
28
+ if (element.type === "linebreak" || element.type === "line-break") return true;
29
+ if (element.type === "paragraph") return flattenText(node).trim() === "";
30
+ return false;
31
+ };
32
+ var groupConsecutiveImages = (nodes) => {
33
+ const groups = [];
34
+ let i = 0;
35
+ while (i < nodes.length) {
36
+ const node = nodes[i];
37
+ if (!isImageNode(node)) {
38
+ groups.push({ kind: "other", node });
39
+ i += 1;
40
+ continue;
41
+ }
42
+ const runImages = [asElementNode(node)];
43
+ let cursor = i + 1;
44
+ while (cursor < nodes.length) {
45
+ if (isImageNode(nodes[cursor])) {
46
+ runImages.push(asElementNode(nodes[cursor]));
47
+ cursor += 1;
48
+ continue;
49
+ }
50
+ if (isBlankFiller(nodes[cursor])) {
51
+ let peek = cursor + 1;
52
+ while (peek < nodes.length && isBlankFiller(nodes[peek])) {
53
+ peek += 1;
54
+ }
55
+ if (peek < nodes.length && isImageNode(nodes[peek])) {
56
+ cursor = peek;
57
+ continue;
58
+ }
59
+ }
60
+ break;
61
+ }
62
+ groups.push({ kind: "images", nodes: runImages });
63
+ i = cursor;
64
+ }
65
+ return groups;
66
+ };
17
67
  var pickUrl = (node) => typeof node.url === "string" && node.url || typeof node.src === "string" && node.src || typeof node.assetUrl === "string" && node.assetUrl || "";
18
68
  var pickAlt = (node) => typeof node.fileName === "string" && node.fileName || typeof node.altText === "string" && node.altText || typeof node.alt === "string" && node.alt || "";
19
69
  var resolveCommentImageUrl = (node, assetBaseUrl) => {
@@ -281,9 +331,12 @@ var CommentImageNode = ({ node, assetBaseUrl }) => {
281
331
  loading: "lazy",
282
332
  onClick: clickable ? () => preview?.openPreview(resolvedUrl) : void 0,
283
333
  style: {
284
- maxWidth: "100%",
285
- height: "auto",
286
334
  display: "block",
335
+ width: "auto",
336
+ height: "auto",
337
+ objectFit: "cover",
338
+ borderRadius: "0.375rem",
339
+ margin: "0.5rem 0",
287
340
  cursor: clickable ? "zoom-in" : "default"
288
341
  }
289
342
  }
@@ -770,8 +823,83 @@ var CommentNodeRenderer = ({ node, assetBaseUrl }) => {
770
823
  };
771
824
  var CommentNodeRenderer_default = CommentNodeRenderer;
772
825
 
826
+ // src/components/controls/view/CommentImageGallery.tsx
827
+ import { jsx as jsx7, jsxs as jsxs6 } from "react/jsx-runtime";
828
+ var gridStyle = {
829
+ display: "grid",
830
+ gridTemplateColumns: "repeat(2, 1fr)",
831
+ gap: "2px",
832
+ borderRadius: "0.375rem",
833
+ overflow: "hidden",
834
+ margin: "0.5rem 0"
835
+ };
836
+ var cellStyle = {
837
+ position: "relative",
838
+ display: "block",
839
+ aspectRatio: "1",
840
+ width: "100%",
841
+ padding: 0,
842
+ border: "none",
843
+ background: "none",
844
+ cursor: "zoom-in",
845
+ overflow: "hidden"
846
+ };
847
+ var cellImgStyle = {
848
+ display: "block",
849
+ width: "100%",
850
+ height: "100%",
851
+ objectFit: "cover"
852
+ };
853
+ var moreOverlayStyle = {
854
+ position: "absolute",
855
+ inset: 0,
856
+ display: "flex",
857
+ alignItems: "center",
858
+ justifyContent: "center",
859
+ background: "rgba(0, 0, 0, 0.5)",
860
+ color: "#fff",
861
+ fontSize: "1.25rem",
862
+ fontWeight: 600
863
+ };
864
+ var CommentImageGallery = ({ nodes, assetBaseUrl }) => {
865
+ const preview = useCommentImagePreview();
866
+ const cells = nodes.map((node) => ({
867
+ id: typeof node.id === "string" && node.id || void 0,
868
+ src: resolveCommentImageUrl(node, assetBaseUrl),
869
+ alt: typeof node.fileName === "string" && node.fileName || typeof node.alt === "string" && node.alt || ""
870
+ })).filter((cell) => !!cell.src);
871
+ if (cells.length === 0) {
872
+ return null;
873
+ }
874
+ const visibleCount = Math.min(cells.length, MAX_VISIBLE_GALLERY_IMAGES);
875
+ const remaining = cells.length - MAX_VISIBLE_GALLERY_IMAGES;
876
+ return /* @__PURE__ */ jsx7("div", { className: "comment-image-gallery", style: gridStyle, children: cells.slice(0, visibleCount).map((cell, i) => {
877
+ const isLastVisible = i === visibleCount - 1;
878
+ const showMoreOverlay = isLastVisible && remaining > 0;
879
+ return /* @__PURE__ */ jsxs6(
880
+ "button",
881
+ {
882
+ type: "button",
883
+ className: "comment-image-gallery-cell",
884
+ style: cellStyle,
885
+ "aria-label": showMoreOverlay ? `Show ${remaining} more photo${remaining === 1 ? "" : "s"}` : cell.alt || "View image",
886
+ onClick: () => preview?.openPreview(cell.src),
887
+ children: [
888
+ /* @__PURE__ */ jsx7("img", { src: cell.src, alt: cell.alt, loading: "lazy", style: cellImgStyle }),
889
+ showMoreOverlay ? /* @__PURE__ */ jsxs6("span", { className: "comment-image-gallery-more-overlay", style: moreOverlayStyle, children: [
890
+ "+",
891
+ remaining
892
+ ] }) : null
893
+ ]
894
+ },
895
+ cell.id ?? `comment-gallery-${i}`
896
+ );
897
+ }) });
898
+ };
899
+ var CommentImageGallery_default = CommentImageGallery;
900
+
773
901
  // src/components/controls/view/CommentRendererClient.tsx
774
- import { jsx as jsx7 } from "react/jsx-runtime";
902
+ import { jsx as jsx8 } from "react/jsx-runtime";
775
903
  var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) => {
776
904
  const nodes = React4.useMemo(() => parseSerializedContent(value), [value]);
777
905
  const resolvedAssetBaseUrl = assetBaseUrl ?? apiBaseUrl;
@@ -779,17 +907,42 @@ var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) =>
779
907
  () => collectCommentImages(nodes, resolvedAssetBaseUrl),
780
908
  [nodes, resolvedAssetBaseUrl]
781
909
  );
910
+ const groups = React4.useMemo(() => groupConsecutiveImages(nodes), [nodes]);
782
911
  if (!nodes || nodes.length === 0) {
783
912
  return null;
784
913
  }
785
- return /* @__PURE__ */ jsx7(CommentImagePreviewProvider, { images, children: /* @__PURE__ */ jsx7("div", { className: className ?? "comment-renderer", children: nodes.map((node, index) => /* @__PURE__ */ jsx7(
786
- CommentNodeRenderer_default,
787
- {
788
- node,
789
- assetBaseUrl: resolvedAssetBaseUrl
790
- },
791
- node.id ?? `comment-node-${index}`
792
- )) }) });
914
+ return /* @__PURE__ */ jsx8(CommentImagePreviewProvider, { images, children: /* @__PURE__ */ jsx8("div", { className: className ?? "comment-renderer", children: groups.map((group, groupIndex) => {
915
+ if (group.kind === "images") {
916
+ const key = group.nodes[0]?.id ?? `comment-group-${groupIndex}`;
917
+ if (group.nodes.length <= INDIVIDUAL_IMAGE_THRESHOLD) {
918
+ return group.nodes.map((node2, i) => /* @__PURE__ */ jsx8(
919
+ CommentNodeRenderer_default,
920
+ {
921
+ node: node2,
922
+ assetBaseUrl: resolvedAssetBaseUrl
923
+ },
924
+ node2.id ?? `${key}-${i}`
925
+ ));
926
+ }
927
+ return /* @__PURE__ */ jsx8(
928
+ CommentImageGallery_default,
929
+ {
930
+ nodes: group.nodes,
931
+ assetBaseUrl: resolvedAssetBaseUrl
932
+ },
933
+ key
934
+ );
935
+ }
936
+ const node = group.node;
937
+ return /* @__PURE__ */ jsx8(
938
+ CommentNodeRenderer_default,
939
+ {
940
+ node,
941
+ assetBaseUrl: resolvedAssetBaseUrl
942
+ },
943
+ node.id ?? `comment-group-${groupIndex}`
944
+ );
945
+ }) }) });
793
946
  };
794
947
  var CommentRendererClient_default = CommentRendererClient;
795
948
  export {