design-zystem 1.0.213 → 1.0.215

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.d.mts CHANGED
@@ -687,8 +687,10 @@ interface MetricCardProps {
687
687
  iconBackgroundColor?: string;
688
688
  iconColor?: string;
689
689
  contentLayout?: 'col' | 'row';
690
+ onClick?: () => void;
691
+ checked?: boolean;
690
692
  }
691
- declare const MetricCard: ({ icon, title, value, valueColor, iconBackgroundColor, iconColor, contentLayout, }: MetricCardProps) => react_jsx_runtime.JSX.Element;
693
+ declare const MetricCard: ({ icon, title, value, valueColor, iconBackgroundColor, iconColor, contentLayout, onClick, checked, }: MetricCardProps) => react_jsx_runtime.JSX.Element;
692
694
 
693
695
  interface TooltipProps {
694
696
  children?: ReactNode;
@@ -776,6 +778,33 @@ declare const formatDuration: (minutes: number | null | undefined, language?: st
776
778
  declare const truncateText: (text: string | null | undefined, maxLength?: number) => string;
777
779
  declare const truncateFileName: (fileName: string | null | undefined, maxLength?: number) => string;
778
780
 
781
+ /**
782
+ * Optimiser image:
783
+ * - Converti en webp (ou png/jpeg selon support navigateur)
784
+ * - Resize l'image pour ne pas dépasser maxDimension
785
+ * - Compression à la taille cible (meilleur effort)
786
+ *
787
+ * Retourne { file, error } — jamais de throw.
788
+ * Si error !== null, l'appelant peut afficher un message à l'utilisateur.
789
+ * Si l'environnement ne supporte pas les APIs nécessaires, passthrough silencieux.
790
+ */
791
+ interface OptimizeImageResult {
792
+ file: File | null | undefined;
793
+ error: string | null;
794
+ }
795
+ interface OptimizeImageOptions {
796
+ maxDimension?: number;
797
+ targetSizeKB?: number;
798
+ qualityStart?: number;
799
+ qualityMin?: number;
800
+ qualityStep?: number;
801
+ /** Timeout en ms pour le chargement de l'image (défaut : 15 000) */
802
+ imageLoadTimeoutMs?: number;
803
+ /** Timeout en ms pour toBlob (défaut : 15 000) */
804
+ toBlobTimeoutMs?: number;
805
+ }
806
+ declare const optimizeImage: (file: File | null | undefined, desiredBaseName: string, opts?: OptimizeImageOptions) => Promise<OptimizeImageResult>;
807
+
779
808
  declare const colors: {
780
809
  readonly primary: "#52bdc4";
781
810
  readonly secondary: "#f8af2d";
@@ -851,4 +880,4 @@ declare const colors: {
851
880
  readonly light_blue: "#F1F5F9";
852
881
  };
853
882
 
854
- export { Accordion, type AccordionItem, Box, Bubble, Bulk, Button, CardSkeleton, Checkbox, Col, ColorPicker, DatePicker, Divider, Drawer, FileUploadZone, Grid, Icon, IconTabs, Image, Input, Link, List, ListItem, MetricCard, Modal, ModalConfirmation, MultiSelect, NewModal, Options, PageContainer, Pagination, Popover, Radio, Row, Select, type SelectOption, type SelectProps, SkeletonRow, SliderInput, Spinner, Stepper, Switch, Table, TableBody, TableCell, TableFooter, TableFooterCell, TableFooterRow, TableHeader, TableHeaderCell, TableHeaderRow, TableRow, Tabs, TagBubble, Text, Tooltip, colors, formatDate, formatDistance, formatDuration, truncateFileName, truncateText };
883
+ export { Accordion, type AccordionItem, Box, Bubble, Bulk, Button, CardSkeleton, Checkbox, Col, ColorPicker, DatePicker, Divider, Drawer, FileUploadZone, Grid, Icon, IconTabs, Image, Input, Link, List, ListItem, MetricCard, Modal, ModalConfirmation, MultiSelect, NewModal, Options, PageContainer, Pagination, Popover, Radio, Row, Select, type SelectOption, type SelectProps, SkeletonRow, SliderInput, Spinner, Stepper, Switch, Table, TableBody, TableCell, TableFooter, TableFooterCell, TableFooterRow, TableHeader, TableHeaderCell, TableHeaderRow, TableRow, Tabs, TagBubble, Text, Tooltip, colors, formatDate, formatDistance, formatDuration, optimizeImage, truncateFileName, truncateText };
package/dist/index.d.ts CHANGED
@@ -687,8 +687,10 @@ interface MetricCardProps {
687
687
  iconBackgroundColor?: string;
688
688
  iconColor?: string;
689
689
  contentLayout?: 'col' | 'row';
690
+ onClick?: () => void;
691
+ checked?: boolean;
690
692
  }
691
- declare const MetricCard: ({ icon, title, value, valueColor, iconBackgroundColor, iconColor, contentLayout, }: MetricCardProps) => react_jsx_runtime.JSX.Element;
693
+ declare const MetricCard: ({ icon, title, value, valueColor, iconBackgroundColor, iconColor, contentLayout, onClick, checked, }: MetricCardProps) => react_jsx_runtime.JSX.Element;
692
694
 
693
695
  interface TooltipProps {
694
696
  children?: ReactNode;
@@ -776,6 +778,33 @@ declare const formatDuration: (minutes: number | null | undefined, language?: st
776
778
  declare const truncateText: (text: string | null | undefined, maxLength?: number) => string;
777
779
  declare const truncateFileName: (fileName: string | null | undefined, maxLength?: number) => string;
778
780
 
781
+ /**
782
+ * Optimiser image:
783
+ * - Converti en webp (ou png/jpeg selon support navigateur)
784
+ * - Resize l'image pour ne pas dépasser maxDimension
785
+ * - Compression à la taille cible (meilleur effort)
786
+ *
787
+ * Retourne { file, error } — jamais de throw.
788
+ * Si error !== null, l'appelant peut afficher un message à l'utilisateur.
789
+ * Si l'environnement ne supporte pas les APIs nécessaires, passthrough silencieux.
790
+ */
791
+ interface OptimizeImageResult {
792
+ file: File | null | undefined;
793
+ error: string | null;
794
+ }
795
+ interface OptimizeImageOptions {
796
+ maxDimension?: number;
797
+ targetSizeKB?: number;
798
+ qualityStart?: number;
799
+ qualityMin?: number;
800
+ qualityStep?: number;
801
+ /** Timeout en ms pour le chargement de l'image (défaut : 15 000) */
802
+ imageLoadTimeoutMs?: number;
803
+ /** Timeout en ms pour toBlob (défaut : 15 000) */
804
+ toBlobTimeoutMs?: number;
805
+ }
806
+ declare const optimizeImage: (file: File | null | undefined, desiredBaseName: string, opts?: OptimizeImageOptions) => Promise<OptimizeImageResult>;
807
+
779
808
  declare const colors: {
780
809
  readonly primary: "#52bdc4";
781
810
  readonly secondary: "#f8af2d";
@@ -851,4 +880,4 @@ declare const colors: {
851
880
  readonly light_blue: "#F1F5F9";
852
881
  };
853
882
 
854
- export { Accordion, type AccordionItem, Box, Bubble, Bulk, Button, CardSkeleton, Checkbox, Col, ColorPicker, DatePicker, Divider, Drawer, FileUploadZone, Grid, Icon, IconTabs, Image, Input, Link, List, ListItem, MetricCard, Modal, ModalConfirmation, MultiSelect, NewModal, Options, PageContainer, Pagination, Popover, Radio, Row, Select, type SelectOption, type SelectProps, SkeletonRow, SliderInput, Spinner, Stepper, Switch, Table, TableBody, TableCell, TableFooter, TableFooterCell, TableFooterRow, TableHeader, TableHeaderCell, TableHeaderRow, TableRow, Tabs, TagBubble, Text, Tooltip, colors, formatDate, formatDistance, formatDuration, truncateFileName, truncateText };
883
+ export { Accordion, type AccordionItem, Box, Bubble, Bulk, Button, CardSkeleton, Checkbox, Col, ColorPicker, DatePicker, Divider, Drawer, FileUploadZone, Grid, Icon, IconTabs, Image, Input, Link, List, ListItem, MetricCard, Modal, ModalConfirmation, MultiSelect, NewModal, Options, PageContainer, Pagination, Popover, Radio, Row, Select, type SelectOption, type SelectProps, SkeletonRow, SliderInput, Spinner, Stepper, Switch, Table, TableBody, TableCell, TableFooter, TableFooterCell, TableFooterRow, TableHeader, TableHeaderCell, TableHeaderRow, TableRow, Tabs, TagBubble, Text, Tooltip, colors, formatDate, formatDistance, formatDuration, optimizeImage, truncateFileName, truncateText };
package/dist/index.js CHANGED
@@ -75,7 +75,7 @@ __export(index_exports, {
75
75
  Grid: () => Grid,
76
76
  Icon: () => Icon,
77
77
  IconTabs: () => IconTabs,
78
- Image: () => Image,
78
+ Image: () => Image2,
79
79
  Input: () => Input,
80
80
  Link: () => Link,
81
81
  List: () => List,
@@ -115,6 +115,7 @@ __export(index_exports, {
115
115
  formatDate: () => formatDate,
116
116
  formatDistance: () => formatDistance,
117
117
  formatDuration: () => formatDuration,
118
+ optimizeImage: () => optimizeImage_default,
118
119
  truncateFileName: () => truncateFileName,
119
120
  truncateText: () => truncateText
120
121
  });
@@ -3799,7 +3800,7 @@ var StyledImage = import_styled_components36.default.img`
3799
3800
  height: ${(props) => props.height || "auto"};
3800
3801
  display: block;
3801
3802
  `;
3802
- var Image = (_a) => {
3803
+ var Image2 = (_a) => {
3803
3804
  var _b = _a, {
3804
3805
  src,
3805
3806
  alt = "",
@@ -4305,15 +4306,25 @@ var Popover = ({ items = [], children }) => {
4305
4306
  const menuRef = (0, import_react21.useRef)(null);
4306
4307
  const [menuPosition, setMenuPosition] = (0, import_react21.useState)({ top: 0, left: 0 });
4307
4308
  const updatePosition = (0, import_react21.useCallback)(() => {
4309
+ var _a;
4308
4310
  if (!ref.current) {
4309
4311
  return;
4310
4312
  }
4311
4313
  const rect = ref.current.getBoundingClientRect();
4314
+ const estimatedMenuHeight = items.length * 42 + 8;
4315
+ const menuHeight = ((_a = menuRef.current) == null ? void 0 : _a.getBoundingClientRect().height) ? menuRef.current.getBoundingClientRect().height : estimatedMenuHeight;
4316
+ const viewportPadding = 8;
4317
+ const spaceBelow = window.innerHeight - rect.bottom;
4318
+ const shouldOpenAbove = spaceBelow < menuHeight + 6;
4319
+ const top = shouldOpenAbove ? rect.top - menuHeight - 6 : rect.bottom + 6;
4312
4320
  setMenuPosition({
4313
- top: rect.bottom + 6,
4321
+ top: Math.max(
4322
+ viewportPadding,
4323
+ Math.min(top, window.innerHeight - menuHeight - viewportPadding)
4324
+ ),
4314
4325
  left: rect.right
4315
4326
  });
4316
- }, []);
4327
+ }, [items.length]);
4317
4328
  (0, import_react21.useEffect)(() => {
4318
4329
  const handleClickOutside = (event) => {
4319
4330
  var _a, _b;
@@ -4324,7 +4335,7 @@ var Popover = ({ items = [], children }) => {
4324
4335
  }
4325
4336
  };
4326
4337
  if (open) {
4327
- updatePosition();
4338
+ requestAnimationFrame(updatePosition);
4328
4339
  window.addEventListener("scroll", updatePosition, true);
4329
4340
  window.addEventListener("resize", updatePosition);
4330
4341
  document.addEventListener("mousedown", handleClickOutside);
@@ -4376,7 +4387,9 @@ var MetricCard = ({
4376
4387
  valueColor,
4377
4388
  iconBackgroundColor = "blue_50",
4378
4389
  iconColor = "blue_950",
4379
- contentLayout = "col"
4390
+ contentLayout = "col",
4391
+ onClick,
4392
+ checked = false
4380
4393
  }) => {
4381
4394
  return /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4382
4395
  Box,
@@ -4384,8 +4397,11 @@ var MetricCard = ({
4384
4397
  padding: "16px",
4385
4398
  borderRadius: "6px",
4386
4399
  style: {
4387
- flex: 1
4400
+ flex: 1,
4401
+ border: checked ? `1px solid ${colors.blue_950}` : void 0,
4402
+ cursor: onClick ? "pointer" : "default"
4388
4403
  },
4404
+ onClick,
4389
4405
  children: /* @__PURE__ */ (0, import_jsx_runtime48.jsxs)(Row, { alignItems: "center", gap: "12", fullWidth: true, children: [
4390
4406
  /* @__PURE__ */ (0, import_jsx_runtime48.jsx)(
4391
4407
  Box,
@@ -5035,6 +5051,140 @@ var truncateFileName = (fileName, maxLength = 20) => {
5035
5051
  }
5036
5052
  return fileName;
5037
5053
  };
5054
+
5055
+ // src/services/optimizeImage/optimizeImage.ts
5056
+ var hasDocument = () => typeof document !== "undefined";
5057
+ var hasObjectURL = () => typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function";
5058
+ var readArrayBuffer = (file) => {
5059
+ if (typeof file.arrayBuffer === "function") {
5060
+ return file.arrayBuffer();
5061
+ }
5062
+ return new Promise((resolve, reject) => {
5063
+ const reader = new FileReader();
5064
+ reader.onload = () => resolve(reader.result);
5065
+ reader.onerror = () => {
5066
+ var _a;
5067
+ return reject((_a = reader.error) != null ? _a : new Error("FileReader error"));
5068
+ };
5069
+ reader.readAsArrayBuffer(file);
5070
+ });
5071
+ };
5072
+ var loadImage = (blob, timeoutMs) => new Promise((resolve, reject) => {
5073
+ const url = URL.createObjectURL(blob);
5074
+ let settled = false;
5075
+ const cleanup = () => {
5076
+ URL.revokeObjectURL(url);
5077
+ };
5078
+ const timer = setTimeout(() => {
5079
+ if (settled) return;
5080
+ settled = true;
5081
+ cleanup();
5082
+ reject(new Error("Image load timeout"));
5083
+ }, timeoutMs);
5084
+ const img = new Image();
5085
+ img.onload = () => {
5086
+ if (settled) return;
5087
+ settled = true;
5088
+ clearTimeout(timer);
5089
+ cleanup();
5090
+ resolve(img);
5091
+ };
5092
+ img.onerror = (e) => {
5093
+ if (settled) return;
5094
+ settled = true;
5095
+ clearTimeout(timer);
5096
+ cleanup();
5097
+ reject(e instanceof Error ? e : new Error("Image load error"));
5098
+ };
5099
+ img.src = url;
5100
+ });
5101
+ var canvasToBlob = (canvas, mime, quality, timeoutMs) => new Promise((resolve, reject) => {
5102
+ const timer = setTimeout(
5103
+ () => reject(new Error("toBlob timeout")),
5104
+ timeoutMs
5105
+ );
5106
+ canvas.toBlob(
5107
+ (blob) => {
5108
+ clearTimeout(timer);
5109
+ if (blob) {
5110
+ resolve(blob);
5111
+ } else {
5112
+ reject(new Error("toBlob returned null"));
5113
+ }
5114
+ },
5115
+ mime,
5116
+ quality
5117
+ );
5118
+ });
5119
+ var optimizeImage = async (file, desiredBaseName, opts = {}) => {
5120
+ var _a;
5121
+ if (!file || !((_a = file.type) == null ? void 0 : _a.startsWith("image/"))) {
5122
+ return { file, error: null };
5123
+ }
5124
+ if (!hasDocument() || !hasObjectURL()) {
5125
+ return { file, error: null };
5126
+ }
5127
+ const {
5128
+ maxDimension = 1920,
5129
+ targetSizeKB = 1024,
5130
+ qualityStart = 0.86,
5131
+ qualityMin = 0.5,
5132
+ qualityStep = 0.08,
5133
+ imageLoadTimeoutMs = 15e3,
5134
+ toBlobTimeoutMs = 15e3
5135
+ } = opts;
5136
+ try {
5137
+ const arrayBuf = await readArrayBuffer(file);
5138
+ const blob = new Blob([arrayBuf], { type: file.type });
5139
+ const img = await loadImage(blob, imageLoadTimeoutMs);
5140
+ const scale = Math.min(1, maxDimension / Math.max(img.width, img.height));
5141
+ const outW = Math.max(1, Math.round(img.width * scale));
5142
+ const outH = Math.max(1, Math.round(img.height * scale));
5143
+ const canvas = document.createElement("canvas");
5144
+ canvas.width = outW;
5145
+ canvas.height = outH;
5146
+ const ctx = canvas.getContext("2d");
5147
+ if (!ctx) throw new Error("Canvas 2D context unavailable");
5148
+ if ("imageSmoothingQuality" in ctx) {
5149
+ ctx.imageSmoothingQuality = "high";
5150
+ }
5151
+ ctx.drawImage(img, 0, 0, outW, outH);
5152
+ const supportsWebP = (() => {
5153
+ try {
5154
+ const probe = document.createElement("canvas");
5155
+ return probe.toDataURL("image/webp").startsWith("data:image/webp");
5156
+ } catch (e) {
5157
+ return false;
5158
+ }
5159
+ })();
5160
+ const sourceHasAlphaHint = /png|webp|gif|avif/i.test(file.type);
5161
+ const preferPNG = !supportsWebP && sourceHasAlphaHint;
5162
+ const targetMime = supportsWebP ? "image/webp" : preferPNG ? "image/png" : "image/jpeg";
5163
+ let q = qualityStart;
5164
+ let outBlob = await canvasToBlob(canvas, targetMime, q, toBlobTimeoutMs);
5165
+ let actualMime = outBlob.type || targetMime;
5166
+ if (/webp|jpeg/.test(actualMime)) {
5167
+ while (outBlob.size / 1024 > targetSizeKB && q > qualityMin) {
5168
+ q = Math.max(qualityMin, q - qualityStep);
5169
+ outBlob = await canvasToBlob(canvas, actualMime, q, toBlobTimeoutMs);
5170
+ actualMime = outBlob.type || actualMime;
5171
+ }
5172
+ }
5173
+ const ext = actualMime.includes("webp") ? "webp" : actualMime.includes("png") ? "png" : "jpg";
5174
+ const optimizedFile = new File([outBlob], `${desiredBaseName}.${ext}`, {
5175
+ type: actualMime,
5176
+ lastModified: Date.now()
5177
+ });
5178
+ return { file: optimizedFile, error: null };
5179
+ } catch (error) {
5180
+ console.error("Image optimization error:", error);
5181
+ return {
5182
+ file,
5183
+ error: "Une erreur est survenue lors de l'optimisation de l'image."
5184
+ };
5185
+ }
5186
+ };
5187
+ var optimizeImage_default = optimizeImage;
5038
5188
  // Annotate the CommonJS export names for ESM import in node:
5039
5189
  0 && (module.exports = {
5040
5190
  Accordion,
@@ -5093,6 +5243,7 @@ var truncateFileName = (fileName, maxLength = 20) => {
5093
5243
  formatDate,
5094
5244
  formatDistance,
5095
5245
  formatDuration,
5246
+ optimizeImage,
5096
5247
  truncateFileName,
5097
5248
  truncateText
5098
5249
  });
package/dist/index.mjs CHANGED
@@ -3729,7 +3729,7 @@ var StyledImage = styled36.img`
3729
3729
  height: ${(props) => props.height || "auto"};
3730
3730
  display: block;
3731
3731
  `;
3732
- var Image = (_a) => {
3732
+ var Image2 = (_a) => {
3733
3733
  var _b = _a, {
3734
3734
  src,
3735
3735
  alt = "",
@@ -4235,15 +4235,25 @@ var Popover = ({ items = [], children }) => {
4235
4235
  const menuRef = useRef12(null);
4236
4236
  const [menuPosition, setMenuPosition] = useState12({ top: 0, left: 0 });
4237
4237
  const updatePosition = useCallback3(() => {
4238
+ var _a;
4238
4239
  if (!ref.current) {
4239
4240
  return;
4240
4241
  }
4241
4242
  const rect = ref.current.getBoundingClientRect();
4243
+ const estimatedMenuHeight = items.length * 42 + 8;
4244
+ const menuHeight = ((_a = menuRef.current) == null ? void 0 : _a.getBoundingClientRect().height) ? menuRef.current.getBoundingClientRect().height : estimatedMenuHeight;
4245
+ const viewportPadding = 8;
4246
+ const spaceBelow = window.innerHeight - rect.bottom;
4247
+ const shouldOpenAbove = spaceBelow < menuHeight + 6;
4248
+ const top = shouldOpenAbove ? rect.top - menuHeight - 6 : rect.bottom + 6;
4242
4249
  setMenuPosition({
4243
- top: rect.bottom + 6,
4250
+ top: Math.max(
4251
+ viewportPadding,
4252
+ Math.min(top, window.innerHeight - menuHeight - viewportPadding)
4253
+ ),
4244
4254
  left: rect.right
4245
4255
  });
4246
- }, []);
4256
+ }, [items.length]);
4247
4257
  useEffect13(() => {
4248
4258
  const handleClickOutside = (event) => {
4249
4259
  var _a, _b;
@@ -4254,7 +4264,7 @@ var Popover = ({ items = [], children }) => {
4254
4264
  }
4255
4265
  };
4256
4266
  if (open) {
4257
- updatePosition();
4267
+ requestAnimationFrame(updatePosition);
4258
4268
  window.addEventListener("scroll", updatePosition, true);
4259
4269
  window.addEventListener("resize", updatePosition);
4260
4270
  document.addEventListener("mousedown", handleClickOutside);
@@ -4306,7 +4316,9 @@ var MetricCard = ({
4306
4316
  valueColor,
4307
4317
  iconBackgroundColor = "blue_50",
4308
4318
  iconColor = "blue_950",
4309
- contentLayout = "col"
4319
+ contentLayout = "col",
4320
+ onClick,
4321
+ checked = false
4310
4322
  }) => {
4311
4323
  return /* @__PURE__ */ jsx48(
4312
4324
  Box,
@@ -4314,8 +4326,11 @@ var MetricCard = ({
4314
4326
  padding: "16px",
4315
4327
  borderRadius: "6px",
4316
4328
  style: {
4317
- flex: 1
4329
+ flex: 1,
4330
+ border: checked ? `1px solid ${colors.blue_950}` : void 0,
4331
+ cursor: onClick ? "pointer" : "default"
4318
4332
  },
4333
+ onClick,
4319
4334
  children: /* @__PURE__ */ jsxs24(Row, { alignItems: "center", gap: "12", fullWidth: true, children: [
4320
4335
  /* @__PURE__ */ jsx48(
4321
4336
  Box,
@@ -4965,6 +4980,140 @@ var truncateFileName = (fileName, maxLength = 20) => {
4965
4980
  }
4966
4981
  return fileName;
4967
4982
  };
4983
+
4984
+ // src/services/optimizeImage/optimizeImage.ts
4985
+ var hasDocument = () => typeof document !== "undefined";
4986
+ var hasObjectURL = () => typeof URL !== "undefined" && typeof URL.createObjectURL === "function" && typeof URL.revokeObjectURL === "function";
4987
+ var readArrayBuffer = (file) => {
4988
+ if (typeof file.arrayBuffer === "function") {
4989
+ return file.arrayBuffer();
4990
+ }
4991
+ return new Promise((resolve, reject) => {
4992
+ const reader = new FileReader();
4993
+ reader.onload = () => resolve(reader.result);
4994
+ reader.onerror = () => {
4995
+ var _a;
4996
+ return reject((_a = reader.error) != null ? _a : new Error("FileReader error"));
4997
+ };
4998
+ reader.readAsArrayBuffer(file);
4999
+ });
5000
+ };
5001
+ var loadImage = (blob, timeoutMs) => new Promise((resolve, reject) => {
5002
+ const url = URL.createObjectURL(blob);
5003
+ let settled = false;
5004
+ const cleanup = () => {
5005
+ URL.revokeObjectURL(url);
5006
+ };
5007
+ const timer = setTimeout(() => {
5008
+ if (settled) return;
5009
+ settled = true;
5010
+ cleanup();
5011
+ reject(new Error("Image load timeout"));
5012
+ }, timeoutMs);
5013
+ const img = new Image();
5014
+ img.onload = () => {
5015
+ if (settled) return;
5016
+ settled = true;
5017
+ clearTimeout(timer);
5018
+ cleanup();
5019
+ resolve(img);
5020
+ };
5021
+ img.onerror = (e) => {
5022
+ if (settled) return;
5023
+ settled = true;
5024
+ clearTimeout(timer);
5025
+ cleanup();
5026
+ reject(e instanceof Error ? e : new Error("Image load error"));
5027
+ };
5028
+ img.src = url;
5029
+ });
5030
+ var canvasToBlob = (canvas, mime, quality, timeoutMs) => new Promise((resolve, reject) => {
5031
+ const timer = setTimeout(
5032
+ () => reject(new Error("toBlob timeout")),
5033
+ timeoutMs
5034
+ );
5035
+ canvas.toBlob(
5036
+ (blob) => {
5037
+ clearTimeout(timer);
5038
+ if (blob) {
5039
+ resolve(blob);
5040
+ } else {
5041
+ reject(new Error("toBlob returned null"));
5042
+ }
5043
+ },
5044
+ mime,
5045
+ quality
5046
+ );
5047
+ });
5048
+ var optimizeImage = async (file, desiredBaseName, opts = {}) => {
5049
+ var _a;
5050
+ if (!file || !((_a = file.type) == null ? void 0 : _a.startsWith("image/"))) {
5051
+ return { file, error: null };
5052
+ }
5053
+ if (!hasDocument() || !hasObjectURL()) {
5054
+ return { file, error: null };
5055
+ }
5056
+ const {
5057
+ maxDimension = 1920,
5058
+ targetSizeKB = 1024,
5059
+ qualityStart = 0.86,
5060
+ qualityMin = 0.5,
5061
+ qualityStep = 0.08,
5062
+ imageLoadTimeoutMs = 15e3,
5063
+ toBlobTimeoutMs = 15e3
5064
+ } = opts;
5065
+ try {
5066
+ const arrayBuf = await readArrayBuffer(file);
5067
+ const blob = new Blob([arrayBuf], { type: file.type });
5068
+ const img = await loadImage(blob, imageLoadTimeoutMs);
5069
+ const scale = Math.min(1, maxDimension / Math.max(img.width, img.height));
5070
+ const outW = Math.max(1, Math.round(img.width * scale));
5071
+ const outH = Math.max(1, Math.round(img.height * scale));
5072
+ const canvas = document.createElement("canvas");
5073
+ canvas.width = outW;
5074
+ canvas.height = outH;
5075
+ const ctx = canvas.getContext("2d");
5076
+ if (!ctx) throw new Error("Canvas 2D context unavailable");
5077
+ if ("imageSmoothingQuality" in ctx) {
5078
+ ctx.imageSmoothingQuality = "high";
5079
+ }
5080
+ ctx.drawImage(img, 0, 0, outW, outH);
5081
+ const supportsWebP = (() => {
5082
+ try {
5083
+ const probe = document.createElement("canvas");
5084
+ return probe.toDataURL("image/webp").startsWith("data:image/webp");
5085
+ } catch (e) {
5086
+ return false;
5087
+ }
5088
+ })();
5089
+ const sourceHasAlphaHint = /png|webp|gif|avif/i.test(file.type);
5090
+ const preferPNG = !supportsWebP && sourceHasAlphaHint;
5091
+ const targetMime = supportsWebP ? "image/webp" : preferPNG ? "image/png" : "image/jpeg";
5092
+ let q = qualityStart;
5093
+ let outBlob = await canvasToBlob(canvas, targetMime, q, toBlobTimeoutMs);
5094
+ let actualMime = outBlob.type || targetMime;
5095
+ if (/webp|jpeg/.test(actualMime)) {
5096
+ while (outBlob.size / 1024 > targetSizeKB && q > qualityMin) {
5097
+ q = Math.max(qualityMin, q - qualityStep);
5098
+ outBlob = await canvasToBlob(canvas, actualMime, q, toBlobTimeoutMs);
5099
+ actualMime = outBlob.type || actualMime;
5100
+ }
5101
+ }
5102
+ const ext = actualMime.includes("webp") ? "webp" : actualMime.includes("png") ? "png" : "jpg";
5103
+ const optimizedFile = new File([outBlob], `${desiredBaseName}.${ext}`, {
5104
+ type: actualMime,
5105
+ lastModified: Date.now()
5106
+ });
5107
+ return { file: optimizedFile, error: null };
5108
+ } catch (error) {
5109
+ console.error("Image optimization error:", error);
5110
+ return {
5111
+ file,
5112
+ error: "Une erreur est survenue lors de l'optimisation de l'image."
5113
+ };
5114
+ }
5115
+ };
5116
+ var optimizeImage_default = optimizeImage;
4968
5117
  export {
4969
5118
  Accordion,
4970
5119
  Box,
@@ -4982,7 +5131,7 @@ export {
4982
5131
  Grid,
4983
5132
  Icon,
4984
5133
  IconTabs,
4985
- Image,
5134
+ Image2 as Image,
4986
5135
  Input,
4987
5136
  Link,
4988
5137
  List,
@@ -5022,6 +5171,7 @@ export {
5022
5171
  formatDate,
5023
5172
  formatDistance,
5024
5173
  formatDuration,
5174
+ optimizeImage_default as optimizeImage,
5025
5175
  truncateFileName,
5026
5176
  truncateText
5027
5177
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "design-zystem",
3
- "version": "1.0.213",
3
+ "version": "1.0.215",
4
4
  "description": "A React design system of importable components",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",