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

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,85 @@ 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
+ maxWidth: "360px",
831
+ borderRadius: "0.375rem",
832
+ overflow: "hidden",
833
+ margin: "0.5rem 0"
834
+ };
835
+ var cellStyle = {
836
+ position: "relative",
837
+ display: "block",
838
+ // Landscape cells keep the grid short when a run wraps onto 2+ rows.
839
+ aspectRatio: "3 / 2",
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
+
771
901
  // src/components/controls/view/CommentRendererClient.tsx
772
- import { jsx as jsx7 } from "react/jsx-runtime";
902
+ import { jsx as jsx8 } from "react/jsx-runtime";
773
903
  var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) => {
774
904
  const nodes = React4.useMemo(() => parseSerializedContent(value), [value]);
775
905
  const resolvedAssetBaseUrl = assetBaseUrl ?? apiBaseUrl;
@@ -777,17 +907,42 @@ var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) =>
777
907
  () => collectCommentImages(nodes, resolvedAssetBaseUrl),
778
908
  [nodes, resolvedAssetBaseUrl]
779
909
  );
910
+ const groups = React4.useMemo(() => groupConsecutiveImages(nodes), [nodes]);
780
911
  if (!nodes || nodes.length === 0) {
781
912
  return null;
782
913
  }
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
- )) }) });
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
+ }) }) });
791
946
  };
792
947
  var CommentRendererClient_default = CommentRendererClient;
793
948
  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,85 @@ 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
+ maxWidth: "360px",
833
+ borderRadius: "0.375rem",
834
+ overflow: "hidden",
835
+ margin: "0.5rem 0"
836
+ };
837
+ var cellStyle = {
838
+ position: "relative",
839
+ display: "block",
840
+ // Landscape cells keep the grid short when a run wraps onto 2+ rows.
841
+ aspectRatio: "3 / 2",
842
+ width: "100%",
843
+ padding: 0,
844
+ border: "none",
845
+ background: "none",
846
+ cursor: "zoom-in",
847
+ overflow: "hidden"
848
+ };
849
+ var cellImgStyle = {
850
+ display: "block",
851
+ width: "100%",
852
+ height: "100%",
853
+ objectFit: "cover"
854
+ };
855
+ var moreOverlayStyle = {
856
+ position: "absolute",
857
+ inset: 0,
858
+ display: "flex",
859
+ alignItems: "center",
860
+ justifyContent: "center",
861
+ background: "rgba(0, 0, 0, 0.5)",
862
+ color: "#fff",
863
+ fontSize: "1.25rem",
864
+ fontWeight: 600
865
+ };
866
+ var CommentImageGallery = ({ nodes, assetBaseUrl }) => {
867
+ const preview = useCommentImagePreview();
868
+ const cells = nodes.map((node) => ({
869
+ id: typeof node.id === "string" && node.id || void 0,
870
+ src: resolveCommentImageUrl(node, assetBaseUrl),
871
+ alt: typeof node.fileName === "string" && node.fileName || typeof node.alt === "string" && node.alt || ""
872
+ })).filter((cell) => !!cell.src);
873
+ if (cells.length === 0) {
874
+ return null;
875
+ }
876
+ const visibleCount = Math.min(cells.length, MAX_VISIBLE_GALLERY_IMAGES);
877
+ const remaining = cells.length - MAX_VISIBLE_GALLERY_IMAGES;
878
+ return /* @__PURE__ */ jsx7("div", { className: "comment-image-gallery", style: gridStyle, children: cells.slice(0, visibleCount).map((cell, i) => {
879
+ const isLastVisible = i === visibleCount - 1;
880
+ const showMoreOverlay = isLastVisible && remaining > 0;
881
+ return /* @__PURE__ */ jsxs6(
882
+ "button",
883
+ {
884
+ type: "button",
885
+ className: "comment-image-gallery-cell",
886
+ style: cellStyle,
887
+ "aria-label": showMoreOverlay ? `Show ${remaining} more photo${remaining === 1 ? "" : "s"}` : cell.alt || "View image",
888
+ onClick: () => preview?.openPreview(cell.src),
889
+ children: [
890
+ /* @__PURE__ */ jsx7("img", { src: cell.src, alt: cell.alt, loading: "lazy", style: cellImgStyle }),
891
+ showMoreOverlay ? /* @__PURE__ */ jsxs6("span", { className: "comment-image-gallery-more-overlay", style: moreOverlayStyle, children: [
892
+ "+",
893
+ remaining
894
+ ] }) : null
895
+ ]
896
+ },
897
+ cell.id ?? `comment-gallery-${i}`
898
+ );
899
+ }) });
900
+ };
901
+ var CommentImageGallery_default = CommentImageGallery;
902
+
773
903
  // src/components/controls/view/CommentRendererClient.tsx
774
- import { jsx as jsx7 } from "react/jsx-runtime";
904
+ import { jsx as jsx8 } from "react/jsx-runtime";
775
905
  var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) => {
776
906
  const nodes = React4.useMemo(() => parseSerializedContent(value), [value]);
777
907
  const resolvedAssetBaseUrl = assetBaseUrl ?? apiBaseUrl;
@@ -779,17 +909,42 @@ var CommentRendererClient = ({ value, className, assetBaseUrl, apiBaseUrl }) =>
779
909
  () => collectCommentImages(nodes, resolvedAssetBaseUrl),
780
910
  [nodes, resolvedAssetBaseUrl]
781
911
  );
912
+ const groups = React4.useMemo(() => groupConsecutiveImages(nodes), [nodes]);
782
913
  if (!nodes || nodes.length === 0) {
783
914
  return null;
784
915
  }
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
- )) }) });
916
+ return /* @__PURE__ */ jsx8(CommentImagePreviewProvider, { images, children: /* @__PURE__ */ jsx8("div", { className: className ?? "comment-renderer", children: groups.map((group, groupIndex) => {
917
+ if (group.kind === "images") {
918
+ const key = group.nodes[0]?.id ?? `comment-group-${groupIndex}`;
919
+ if (group.nodes.length <= INDIVIDUAL_IMAGE_THRESHOLD) {
920
+ return group.nodes.map((node2, i) => /* @__PURE__ */ jsx8(
921
+ CommentNodeRenderer_default,
922
+ {
923
+ node: node2,
924
+ assetBaseUrl: resolvedAssetBaseUrl
925
+ },
926
+ node2.id ?? `${key}-${i}`
927
+ ));
928
+ }
929
+ return /* @__PURE__ */ jsx8(
930
+ CommentImageGallery_default,
931
+ {
932
+ nodes: group.nodes,
933
+ assetBaseUrl: resolvedAssetBaseUrl
934
+ },
935
+ key
936
+ );
937
+ }
938
+ const node = group.node;
939
+ return /* @__PURE__ */ jsx8(
940
+ CommentNodeRenderer_default,
941
+ {
942
+ node,
943
+ assetBaseUrl: resolvedAssetBaseUrl
944
+ },
945
+ node.id ?? `comment-group-${groupIndex}`
946
+ );
947
+ }) }) });
793
948
  };
794
949
  var CommentRendererClient_default = CommentRendererClient;
795
950
  export {