@etsoo/materialui 1.6.76 → 1.6.77

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.
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import SignaturePad, { Options } from "signature_pad";
3
+ /**
4
+ * Extended SignaturePad class with trim method
5
+ */
6
+ export declare class SignaturePadEx extends SignaturePad {
7
+ private readonly canvasElement;
8
+ constructor(canvasElement: HTMLCanvasElement, options?: Options);
9
+ /**
10
+ * Trim the canvas to remove empty space around the signature
11
+ * @returns Trimmed canvas
12
+ */
13
+ trim(): HTMLCanvasElement;
14
+ }
15
+ /**
16
+ * Props for SignaturePadComponent
17
+ */
18
+ export type SignaturePadComponentProps = {
19
+ /**
20
+ * Width of the canvas
21
+ */
22
+ width: number;
23
+ /**
24
+ * Height of the canvas
25
+ */
26
+ height: number;
27
+ /**
28
+ * Options for SignaturePad
29
+ */
30
+ options?: Options;
31
+ /**
32
+ * Ref for the SignaturePadEx instance
33
+ */
34
+ ref: React.RefObject<SignaturePadEx | null>;
35
+ };
36
+ /**
37
+ * React component for SignaturePad
38
+ * @param props Props
39
+ * @returns Component
40
+ */
41
+ export declare function SignaturePadComponent(props: SignaturePadComponentProps): React.JSX.Element;
42
+ type Placement = "top" | "right" | "bottom" | "left";
43
+ type Alignment = "flex-start" | "center" | "flex-end";
44
+ /**
45
+ * Methods for SignaturePadFull component
46
+ */
47
+ export interface SignaturePadFullMethods {
48
+ getInstance(): SignaturePadEx | null;
49
+ }
50
+ /**
51
+ * Props for SignaturePadFull component
52
+ */
53
+ export type SignaturePadFullProps = Partial<Omit<SignaturePadComponentProps, "ref">> & {
54
+ /**
55
+ * Label for the clear button
56
+ * @default "Clear"
57
+ */
58
+ clearLabel?: string;
59
+ /**
60
+ * Label for the save button
61
+ * @default "Save"
62
+ */
63
+ saveLabel?: string;
64
+ /**
65
+ * Ref for the SignaturePadFull instance
66
+ */
67
+ mRef?: React.RefObject<SignaturePadFullMethods | null>;
68
+ /**
69
+ * Placement of the buttons relative to the canvas
70
+ * @default ["right", "flex-start"]
71
+ */
72
+ placement?: [Placement?, alignment?: Alignment];
73
+ /**
74
+ * Callback when the signature is saved
75
+ * @param signaturePad SignaturePadEx instance
76
+ * @returns void or Promise<void>
77
+ */
78
+ onSave?: (signaturePad: SignaturePadEx) => void | Promise<void>;
79
+ /**
80
+ * Spacing between the canvas and buttons
81
+ * @default 0.5
82
+ */
83
+ spacing?: number;
84
+ };
85
+ /**
86
+ * Full SignaturePad component with clear and save buttons
87
+ *
88
+ * @param props Props
89
+ * @returns Component
90
+ */
91
+ export declare function SignaturePadFull(props: SignaturePadFullProps): React.JSX.Element;
92
+ export {};
@@ -0,0 +1,93 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.SignaturePadEx = void 0;
7
+ exports.SignaturePadComponent = SignaturePadComponent;
8
+ exports.SignaturePadFull = SignaturePadFull;
9
+ const jsx_runtime_1 = require("react/jsx-runtime");
10
+ const Button_1 = __importDefault(require("@mui/material/Button"));
11
+ const Paper_1 = __importDefault(require("@mui/material/Paper"));
12
+ const react_1 = __importDefault(require("react"));
13
+ const signature_pad_1 = __importDefault(require("signature_pad"));
14
+ const Clear_1 = __importDefault(require("@mui/icons-material/Clear"));
15
+ const Save_1 = __importDefault(require("@mui/icons-material/Save"));
16
+ const CanvasUtils_1 = require("./utils/CanvasUtils");
17
+ const Stack_1 = __importDefault(require("@mui/material/Stack"));
18
+ const ButtonGroup_1 = __importDefault(require("@mui/material/ButtonGroup"));
19
+ /**
20
+ * Extended SignaturePad class with trim method
21
+ */
22
+ class SignaturePadEx extends signature_pad_1.default {
23
+ canvasElement;
24
+ constructor(canvasElement, options) {
25
+ super(canvasElement, options);
26
+ this.canvasElement = canvasElement;
27
+ }
28
+ /**
29
+ * Trim the canvas to remove empty space around the signature
30
+ * @returns Trimmed canvas
31
+ */
32
+ trim() {
33
+ return CanvasUtils_1.CanvasUtils.trim(this.canvasElement);
34
+ }
35
+ }
36
+ exports.SignaturePadEx = SignaturePadEx;
37
+ /**
38
+ * React component for SignaturePad
39
+ * @param props Props
40
+ * @returns Component
41
+ */
42
+ function SignaturePadComponent(props) {
43
+ // Destruct
44
+ const { width, height, options, ref } = props;
45
+ // Refs
46
+ const canvasRef = react_1.default.useRef(null);
47
+ react_1.default.useEffect(() => {
48
+ const canvas = canvasRef.current;
49
+ if (!canvas)
50
+ return;
51
+ ref.current = new SignaturePadEx(canvas, options);
52
+ return () => {
53
+ ref.current?.off();
54
+ };
55
+ }, [options, ref]);
56
+ return (0, jsx_runtime_1.jsx)("canvas", { ref: canvasRef, width: width, height: height });
57
+ }
58
+ /**
59
+ * Full SignaturePad component with clear and save buttons
60
+ *
61
+ * @param props Props
62
+ * @returns Component
63
+ */
64
+ function SignaturePadFull(props) {
65
+ // Destruct
66
+ const { clearLabel = "Clear", saveLabel = "Save", width = 600, height = 300, mRef, onSave, options, placement = ["right", "flex-start"], spacing = 0.5 } = props;
67
+ // Ref
68
+ const signaturePadRef = react_1.default.useRef(null);
69
+ react_1.default.useImperativeHandle(mRef, () => ({
70
+ getInstance: () => signaturePadRef.current
71
+ }));
72
+ function handleClear() {
73
+ signaturePadRef.current?.clear();
74
+ }
75
+ function handleSave() {
76
+ const sp = signaturePadRef.current;
77
+ if (!sp || sp.isEmpty())
78
+ return;
79
+ onSave?.(sp);
80
+ }
81
+ const [p = "right", a = "flex-start"] = placement;
82
+ let stackDirection;
83
+ let buttonGroupOrientation;
84
+ if (p === "top" || p === "bottom") {
85
+ stackDirection = p === "top" ? "column-reverse" : "column";
86
+ buttonGroupOrientation = "horizontal";
87
+ }
88
+ else {
89
+ stackDirection = p === "left" ? "row-reverse" : "row";
90
+ buttonGroupOrientation = "vertical";
91
+ }
92
+ return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { direction: stackDirection, spacing: spacing, sx: { alignItems: a }, children: [(0, jsx_runtime_1.jsx)(Paper_1.default, { sx: { width, height }, children: (0, jsx_runtime_1.jsx)(SignaturePadComponent, { width: width, height: height, options: options, ref: signaturePadRef }) }), (0, jsx_runtime_1.jsxs)(ButtonGroup_1.default, { orientation: buttonGroupOrientation, children: [(0, jsx_runtime_1.jsx)(Button_1.default, { onClick: handleClear, startIcon: (0, jsx_runtime_1.jsx)(Clear_1.default, {}), children: clearLabel }), onSave && ((0, jsx_runtime_1.jsx)(Button_1.default, { variant: "contained", onClick: handleSave, startIcon: (0, jsx_runtime_1.jsx)(Save_1.default, {}), children: saveLabel }))] })] }));
93
+ }
@@ -175,6 +175,6 @@ function UserAvatarEditor(props) {
175
175
  }
176
176
  }
177
177
  };
178
- return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { direction: "column", spacing: 0.5, sx: { width: containerWidth }, children: [(0, jsx_runtime_1.jsx)(FileUploadButton_1.FileUploadButton, { variant: "outlined", size: "medium", startIcon: (0, jsx_runtime_1.jsx)(Image_1.default, {}), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { accept: "image/png, image/jpeg" }, children: selectFileLabel }), (0, jsx_runtime_1.jsxs)(Stack_1.default, { direction: "row", spacing: 0.5, children: [(0, jsx_runtime_1.jsx)(react_1.default.Suspense, { fallback: (0, jsx_runtime_1.jsx)(Skeleton_1.default, { variant: "rounded", width: width, height: localHeight }), children: (0, jsx_runtime_1.jsx)(react_avatar_editor_1.default, { ref: ref, border: border, width: width, height: localHeight, onLoadSuccess: handleLoad, image: previewImage ??
178
+ return ((0, jsx_runtime_1.jsxs)(Stack_1.default, { direction: "column", spacing: 0.5, sx: { width: containerWidth }, children: [(0, jsx_runtime_1.jsx)(FileUploadButton_1.FileUploadButton, { variant: "outlined", size: "medium", startIcon: (0, jsx_runtime_1.jsx)(Image_1.default, {}), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { accept: "image/png, image/jpeg, image/webp" }, children: selectFileLabel }), (0, jsx_runtime_1.jsxs)(Stack_1.default, { direction: "row", spacing: 0.5, children: [(0, jsx_runtime_1.jsx)(react_1.default.Suspense, { fallback: (0, jsx_runtime_1.jsx)(Skeleton_1.default, { variant: "rounded", width: width, height: localHeight }), children: (0, jsx_runtime_1.jsx)(react_avatar_editor_1.default, { ref: ref, border: border, width: width, height: localHeight, onLoadSuccess: handleLoad, image: previewImage ??
179
179
  "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=", scale: editorState.scale, rotate: editorState.rotate }) }), (0, jsx_runtime_1.jsxs)(ButtonGroup_1.default, { size: "small", orientation: "vertical", disabled: !ready, children: [(0, jsx_runtime_1.jsx)(Button_1.default, { onClick: () => handleRotate(90), title: labels.rotateRight, children: (0, jsx_runtime_1.jsx)(RotateRight_1.default, {}) }), (0, jsx_runtime_1.jsx)(Button_1.default, { onClick: () => handleRotate(-90), title: labels.rotateLeft, children: (0, jsx_runtime_1.jsx)(RotateLeft_1.default, {}) }), (0, jsx_runtime_1.jsx)(Button_1.default, { onClick: handleReset, title: labels.reset, children: (0, jsx_runtime_1.jsx)(ClearAll_1.default, {}) })] })] }), (0, jsx_runtime_1.jsxs)(Stack_1.default, { spacing: 0.5, direction: "row", sx: { alignItems: "center", paddingBottom: 2 }, children: [(0, jsx_runtime_1.jsx)(IconButton_1.default, { size: "small", disabled: !ready || editorState.scale <= min, onClick: () => adjustScale(false), children: (0, jsx_runtime_1.jsx)(Remove_1.default, {}) }), (0, jsx_runtime_1.jsx)(Slider_1.default, { title: labels.zoom, disabled: !ready, min: min, max: max, step: step, value: editorState.scale, valueLabelDisplay: "auto", valueLabelFormat: (value) => `${Math.round(100 * value) / 100}`, marks: marks, onChange: handleZoom }), (0, jsx_runtime_1.jsx)(IconButton_1.default, { size: "small", disabled: !ready || editorState.scale >= max, onClick: () => adjustScale(true), children: (0, jsx_runtime_1.jsx)(Add_1.default, {}) })] }), (0, jsx_runtime_1.jsx)(Button_1.default, { ref: buttonRef, variant: "contained", startIcon: (0, jsx_runtime_1.jsx)(Done_1.default, {}), disabled: !ready, onClick: handleDone, children: labels.done })] }));
180
180
  }
@@ -35,6 +35,7 @@ export * from "./pages/ViewPage";
35
35
  export * from "./texts/DateText";
36
36
  export * from "./texts/MoneyText";
37
37
  export * from "./texts/NumberText";
38
+ export * from "./utils/CanvasUtils";
38
39
  export * from "./AddresSelector";
39
40
  export * from "./AuditDisplay";
40
41
  export * from "./AutocompleteExtendedProps";
@@ -122,6 +123,7 @@ export * from "./SearchOptionGroup";
122
123
  export * from "./SelectBool";
123
124
  export * from "./SelectEx";
124
125
  export * from "./ShowDataComparison";
126
+ export * from "./SignaturePadComponent";
125
127
  export * from "./Switch";
126
128
  export * from "./SwitchAnt";
127
129
  export * from "./SwitchField";
package/lib/cjs/index.js CHANGED
@@ -55,6 +55,7 @@ __exportStar(require("./pages/ViewPage"), exports);
55
55
  __exportStar(require("./texts/DateText"), exports);
56
56
  __exportStar(require("./texts/MoneyText"), exports);
57
57
  __exportStar(require("./texts/NumberText"), exports);
58
+ __exportStar(require("./utils/CanvasUtils"), exports);
58
59
  __exportStar(require("./AddresSelector"), exports);
59
60
  __exportStar(require("./AuditDisplay"), exports);
60
61
  __exportStar(require("./AutocompleteExtendedProps"), exports);
@@ -142,6 +143,7 @@ __exportStar(require("./SearchOptionGroup"), exports);
142
143
  __exportStar(require("./SelectBool"), exports);
143
144
  __exportStar(require("./SelectEx"), exports);
144
145
  __exportStar(require("./ShowDataComparison"), exports);
146
+ __exportStar(require("./SignaturePadComponent"), exports);
145
147
  __exportStar(require("./Switch"), exports);
146
148
  __exportStar(require("./SwitchAnt"), exports);
147
149
  __exportStar(require("./SwitchField"), exports);
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Utility functions for canvas operations
3
+ */
4
+ export declare namespace CanvasUtils {
5
+ /**
6
+ * JPEG content type
7
+ */
8
+ const JPG = "image/jpeg";
9
+ /**
10
+ * PNG content type
11
+ */
12
+ const PNG = "image/png";
13
+ /**
14
+ * WEBP content type
15
+ */
16
+ const WEBP = "image/webp";
17
+ /**
18
+ * SVG content type
19
+ */
20
+ const SVG = "image/svg+xml";
21
+ /**
22
+ * Convert canvas to Blob
23
+ * @param canvas Canvas element
24
+ * @param type MIME type of the image
25
+ * @param quality Image quality (0 to 1)
26
+ * @returns Promise that resolves to a Blob
27
+ */
28
+ function toBlob(canvas: HTMLCanvasElement, type?: string, quality?: number): Promise<Blob>;
29
+ /**
30
+ * Trim the canvas to remove empty space around the signature
31
+ * @param canvas Canvas element
32
+ * @returns Trimmed canvas
33
+ */
34
+ function trim(canvas: HTMLCanvasElement): HTMLCanvasElement;
35
+ }
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.CanvasUtils = void 0;
4
+ /**
5
+ * Utility functions for canvas operations
6
+ */
7
+ var CanvasUtils;
8
+ (function (CanvasUtils) {
9
+ /**
10
+ * JPEG content type
11
+ */
12
+ CanvasUtils.JPG = "image/jpeg";
13
+ /**
14
+ * PNG content type
15
+ */
16
+ CanvasUtils.PNG = "image/png";
17
+ /**
18
+ * WEBP content type
19
+ */
20
+ CanvasUtils.WEBP = "image/webp";
21
+ /**
22
+ * SVG content type
23
+ */
24
+ CanvasUtils.SVG = "image/svg+xml";
25
+ /**
26
+ * Convert canvas to Blob
27
+ * @param canvas Canvas element
28
+ * @param type MIME type of the image
29
+ * @param quality Image quality (0 to 1)
30
+ * @returns Promise that resolves to a Blob
31
+ */
32
+ function toBlob(canvas, type = "image/png", quality = 1) {
33
+ return new Promise((resolve, reject) => {
34
+ canvas.toBlob((blob) => {
35
+ if (blob) {
36
+ resolve(blob);
37
+ }
38
+ else {
39
+ reject(new Error("Failed to create Blob."));
40
+ }
41
+ }, type, quality);
42
+ });
43
+ }
44
+ CanvasUtils.toBlob = toBlob;
45
+ /**
46
+ * Trim the canvas to remove empty space around the signature
47
+ * @param canvas Canvas element
48
+ * @returns Trimmed canvas
49
+ */
50
+ function trim(canvas) {
51
+ const ctx = canvas.getContext("2d");
52
+ const { width, height } = canvas;
53
+ const { data } = ctx.getImageData(0, 0, width, height);
54
+ let top = height;
55
+ let left = width;
56
+ let right = 0;
57
+ let bottom = 0;
58
+ for (let y = 0; y < height; y++) {
59
+ for (let x = 0; x < width; x++) {
60
+ if (data[(y * width + x) * 4 + 3] === 0)
61
+ continue;
62
+ if (x < left)
63
+ left = x;
64
+ if (x > right)
65
+ right = x;
66
+ if (y < top)
67
+ top = y;
68
+ if (y > bottom)
69
+ bottom = y;
70
+ }
71
+ }
72
+ const trimmed = document.createElement("canvas");
73
+ // Empty canvas
74
+ if (right < left || bottom < top)
75
+ return trimmed;
76
+ trimmed.width = right - left + 1;
77
+ trimmed.height = bottom - top + 1;
78
+ trimmed
79
+ .getContext("2d")
80
+ .drawImage(canvas, left, top, trimmed.width, trimmed.height, 0, 0, trimmed.width, trimmed.height);
81
+ return trimmed;
82
+ }
83
+ CanvasUtils.trim = trim;
84
+ })(CanvasUtils || (exports.CanvasUtils = CanvasUtils = {}));
@@ -0,0 +1,92 @@
1
+ import React from "react";
2
+ import SignaturePad, { Options } from "signature_pad";
3
+ /**
4
+ * Extended SignaturePad class with trim method
5
+ */
6
+ export declare class SignaturePadEx extends SignaturePad {
7
+ private readonly canvasElement;
8
+ constructor(canvasElement: HTMLCanvasElement, options?: Options);
9
+ /**
10
+ * Trim the canvas to remove empty space around the signature
11
+ * @returns Trimmed canvas
12
+ */
13
+ trim(): HTMLCanvasElement;
14
+ }
15
+ /**
16
+ * Props for SignaturePadComponent
17
+ */
18
+ export type SignaturePadComponentProps = {
19
+ /**
20
+ * Width of the canvas
21
+ */
22
+ width: number;
23
+ /**
24
+ * Height of the canvas
25
+ */
26
+ height: number;
27
+ /**
28
+ * Options for SignaturePad
29
+ */
30
+ options?: Options;
31
+ /**
32
+ * Ref for the SignaturePadEx instance
33
+ */
34
+ ref: React.RefObject<SignaturePadEx | null>;
35
+ };
36
+ /**
37
+ * React component for SignaturePad
38
+ * @param props Props
39
+ * @returns Component
40
+ */
41
+ export declare function SignaturePadComponent(props: SignaturePadComponentProps): React.JSX.Element;
42
+ type Placement = "top" | "right" | "bottom" | "left";
43
+ type Alignment = "flex-start" | "center" | "flex-end";
44
+ /**
45
+ * Methods for SignaturePadFull component
46
+ */
47
+ export interface SignaturePadFullMethods {
48
+ getInstance(): SignaturePadEx | null;
49
+ }
50
+ /**
51
+ * Props for SignaturePadFull component
52
+ */
53
+ export type SignaturePadFullProps = Partial<Omit<SignaturePadComponentProps, "ref">> & {
54
+ /**
55
+ * Label for the clear button
56
+ * @default "Clear"
57
+ */
58
+ clearLabel?: string;
59
+ /**
60
+ * Label for the save button
61
+ * @default "Save"
62
+ */
63
+ saveLabel?: string;
64
+ /**
65
+ * Ref for the SignaturePadFull instance
66
+ */
67
+ mRef?: React.RefObject<SignaturePadFullMethods | null>;
68
+ /**
69
+ * Placement of the buttons relative to the canvas
70
+ * @default ["right", "flex-start"]
71
+ */
72
+ placement?: [Placement?, alignment?: Alignment];
73
+ /**
74
+ * Callback when the signature is saved
75
+ * @param signaturePad SignaturePadEx instance
76
+ * @returns void or Promise<void>
77
+ */
78
+ onSave?: (signaturePad: SignaturePadEx) => void | Promise<void>;
79
+ /**
80
+ * Spacing between the canvas and buttons
81
+ * @default 0.5
82
+ */
83
+ spacing?: number;
84
+ };
85
+ /**
86
+ * Full SignaturePad component with clear and save buttons
87
+ *
88
+ * @param props Props
89
+ * @returns Component
90
+ */
91
+ export declare function SignaturePadFull(props: SignaturePadFullProps): React.JSX.Element;
92
+ export {};
@@ -0,0 +1,84 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import Button from "@mui/material/Button";
3
+ import Paper from "@mui/material/Paper";
4
+ import React from "react";
5
+ import SignaturePad from "signature_pad";
6
+ import ClearIcon from "@mui/icons-material/Clear";
7
+ import SaveIcon from "@mui/icons-material/Save";
8
+ import { CanvasUtils } from "./utils/CanvasUtils";
9
+ import Stack from "@mui/material/Stack";
10
+ import ButtonGroup from "@mui/material/ButtonGroup";
11
+ /**
12
+ * Extended SignaturePad class with trim method
13
+ */
14
+ export class SignaturePadEx extends SignaturePad {
15
+ canvasElement;
16
+ constructor(canvasElement, options) {
17
+ super(canvasElement, options);
18
+ this.canvasElement = canvasElement;
19
+ }
20
+ /**
21
+ * Trim the canvas to remove empty space around the signature
22
+ * @returns Trimmed canvas
23
+ */
24
+ trim() {
25
+ return CanvasUtils.trim(this.canvasElement);
26
+ }
27
+ }
28
+ /**
29
+ * React component for SignaturePad
30
+ * @param props Props
31
+ * @returns Component
32
+ */
33
+ export function SignaturePadComponent(props) {
34
+ // Destruct
35
+ const { width, height, options, ref } = props;
36
+ // Refs
37
+ const canvasRef = React.useRef(null);
38
+ React.useEffect(() => {
39
+ const canvas = canvasRef.current;
40
+ if (!canvas)
41
+ return;
42
+ ref.current = new SignaturePadEx(canvas, options);
43
+ return () => {
44
+ ref.current?.off();
45
+ };
46
+ }, [options, ref]);
47
+ return _jsx("canvas", { ref: canvasRef, width: width, height: height });
48
+ }
49
+ /**
50
+ * Full SignaturePad component with clear and save buttons
51
+ *
52
+ * @param props Props
53
+ * @returns Component
54
+ */
55
+ export function SignaturePadFull(props) {
56
+ // Destruct
57
+ const { clearLabel = "Clear", saveLabel = "Save", width = 600, height = 300, mRef, onSave, options, placement = ["right", "flex-start"], spacing = 0.5 } = props;
58
+ // Ref
59
+ const signaturePadRef = React.useRef(null);
60
+ React.useImperativeHandle(mRef, () => ({
61
+ getInstance: () => signaturePadRef.current
62
+ }));
63
+ function handleClear() {
64
+ signaturePadRef.current?.clear();
65
+ }
66
+ function handleSave() {
67
+ const sp = signaturePadRef.current;
68
+ if (!sp || sp.isEmpty())
69
+ return;
70
+ onSave?.(sp);
71
+ }
72
+ const [p = "right", a = "flex-start"] = placement;
73
+ let stackDirection;
74
+ let buttonGroupOrientation;
75
+ if (p === "top" || p === "bottom") {
76
+ stackDirection = p === "top" ? "column-reverse" : "column";
77
+ buttonGroupOrientation = "horizontal";
78
+ }
79
+ else {
80
+ stackDirection = p === "left" ? "row-reverse" : "row";
81
+ buttonGroupOrientation = "vertical";
82
+ }
83
+ return (_jsxs(Stack, { direction: stackDirection, spacing: spacing, sx: { alignItems: a }, children: [_jsx(Paper, { sx: { width, height }, children: _jsx(SignaturePadComponent, { width: width, height: height, options: options, ref: signaturePadRef }) }), _jsxs(ButtonGroup, { orientation: buttonGroupOrientation, children: [_jsx(Button, { onClick: handleClear, startIcon: _jsx(ClearIcon, {}), children: clearLabel }), onSave && (_jsx(Button, { variant: "contained", onClick: handleSave, startIcon: _jsx(SaveIcon, {}), children: saveLabel }))] })] }));
84
+ }
@@ -169,6 +169,6 @@ export function UserAvatarEditor(props) {
169
169
  }
170
170
  }
171
171
  };
172
- return (_jsxs(Stack, { direction: "column", spacing: 0.5, sx: { width: containerWidth }, children: [_jsx(FileUploadButton, { variant: "outlined", size: "medium", startIcon: _jsx(ImageIcon, {}), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { accept: "image/png, image/jpeg" }, children: selectFileLabel }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(React.Suspense, { fallback: _jsx(Skeleton, { variant: "rounded", width: width, height: localHeight }), children: _jsx(AvatarEditor, { ref: ref, border: border, width: width, height: localHeight, onLoadSuccess: handleLoad, image: previewImage ??
172
+ return (_jsxs(Stack, { direction: "column", spacing: 0.5, sx: { width: containerWidth }, children: [_jsx(FileUploadButton, { variant: "outlined", size: "medium", startIcon: _jsx(ImageIcon, {}), fullWidth: true, onUploadFiles: handleFileUpload, inputProps: { accept: "image/png, image/jpeg, image/webp" }, children: selectFileLabel }), _jsxs(Stack, { direction: "row", spacing: 0.5, children: [_jsx(React.Suspense, { fallback: _jsx(Skeleton, { variant: "rounded", width: width, height: localHeight }), children: _jsx(AvatarEditor, { ref: ref, border: border, width: width, height: localHeight, onLoadSuccess: handleLoad, image: previewImage ??
173
173
  "data:image/gif;base64,R0lGODlhAQABAAD/ACwAAAAAAQABAAACADs=", scale: editorState.scale, rotate: editorState.rotate }) }), _jsxs(ButtonGroup, { size: "small", orientation: "vertical", disabled: !ready, children: [_jsx(Button, { onClick: () => handleRotate(90), title: labels.rotateRight, children: _jsx(RotateRightIcon, {}) }), _jsx(Button, { onClick: () => handleRotate(-90), title: labels.rotateLeft, children: _jsx(RotateLeftIcon, {}) }), _jsx(Button, { onClick: handleReset, title: labels.reset, children: _jsx(ClearAllIcon, {}) })] })] }), _jsxs(Stack, { spacing: 0.5, direction: "row", sx: { alignItems: "center", paddingBottom: 2 }, children: [_jsx(IconButton, { size: "small", disabled: !ready || editorState.scale <= min, onClick: () => adjustScale(false), children: _jsx(RemoveIcon, {}) }), _jsx(Slider, { title: labels.zoom, disabled: !ready, min: min, max: max, step: step, value: editorState.scale, valueLabelDisplay: "auto", valueLabelFormat: (value) => `${Math.round(100 * value) / 100}`, marks: marks, onChange: handleZoom }), _jsx(IconButton, { size: "small", disabled: !ready || editorState.scale >= max, onClick: () => adjustScale(true), children: _jsx(AddIcon, {}) })] }), _jsx(Button, { ref: buttonRef, variant: "contained", startIcon: _jsx(DoneIcon, {}), disabled: !ready, onClick: handleDone, children: labels.done })] }));
174
174
  }
@@ -35,6 +35,7 @@ export * from "./pages/ViewPage";
35
35
  export * from "./texts/DateText";
36
36
  export * from "./texts/MoneyText";
37
37
  export * from "./texts/NumberText";
38
+ export * from "./utils/CanvasUtils";
38
39
  export * from "./AddresSelector";
39
40
  export * from "./AuditDisplay";
40
41
  export * from "./AutocompleteExtendedProps";
@@ -122,6 +123,7 @@ export * from "./SearchOptionGroup";
122
123
  export * from "./SelectBool";
123
124
  export * from "./SelectEx";
124
125
  export * from "./ShowDataComparison";
126
+ export * from "./SignaturePadComponent";
125
127
  export * from "./Switch";
126
128
  export * from "./SwitchAnt";
127
129
  export * from "./SwitchField";
package/lib/mjs/index.js CHANGED
@@ -35,6 +35,7 @@ export * from "./pages/ViewPage";
35
35
  export * from "./texts/DateText";
36
36
  export * from "./texts/MoneyText";
37
37
  export * from "./texts/NumberText";
38
+ export * from "./utils/CanvasUtils";
38
39
  export * from "./AddresSelector";
39
40
  export * from "./AuditDisplay";
40
41
  export * from "./AutocompleteExtendedProps";
@@ -122,6 +123,7 @@ export * from "./SearchOptionGroup";
122
123
  export * from "./SelectBool";
123
124
  export * from "./SelectEx";
124
125
  export * from "./ShowDataComparison";
126
+ export * from "./SignaturePadComponent";
125
127
  export * from "./Switch";
126
128
  export * from "./SwitchAnt";
127
129
  export * from "./SwitchField";
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Utility functions for canvas operations
3
+ */
4
+ export declare namespace CanvasUtils {
5
+ /**
6
+ * JPEG content type
7
+ */
8
+ const JPG = "image/jpeg";
9
+ /**
10
+ * PNG content type
11
+ */
12
+ const PNG = "image/png";
13
+ /**
14
+ * WEBP content type
15
+ */
16
+ const WEBP = "image/webp";
17
+ /**
18
+ * SVG content type
19
+ */
20
+ const SVG = "image/svg+xml";
21
+ /**
22
+ * Convert canvas to Blob
23
+ * @param canvas Canvas element
24
+ * @param type MIME type of the image
25
+ * @param quality Image quality (0 to 1)
26
+ * @returns Promise that resolves to a Blob
27
+ */
28
+ function toBlob(canvas: HTMLCanvasElement, type?: string, quality?: number): Promise<Blob>;
29
+ /**
30
+ * Trim the canvas to remove empty space around the signature
31
+ * @param canvas Canvas element
32
+ * @returns Trimmed canvas
33
+ */
34
+ function trim(canvas: HTMLCanvasElement): HTMLCanvasElement;
35
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Utility functions for canvas operations
3
+ */
4
+ export var CanvasUtils;
5
+ (function (CanvasUtils) {
6
+ /**
7
+ * JPEG content type
8
+ */
9
+ CanvasUtils.JPG = "image/jpeg";
10
+ /**
11
+ * PNG content type
12
+ */
13
+ CanvasUtils.PNG = "image/png";
14
+ /**
15
+ * WEBP content type
16
+ */
17
+ CanvasUtils.WEBP = "image/webp";
18
+ /**
19
+ * SVG content type
20
+ */
21
+ CanvasUtils.SVG = "image/svg+xml";
22
+ /**
23
+ * Convert canvas to Blob
24
+ * @param canvas Canvas element
25
+ * @param type MIME type of the image
26
+ * @param quality Image quality (0 to 1)
27
+ * @returns Promise that resolves to a Blob
28
+ */
29
+ function toBlob(canvas, type = "image/png", quality = 1) {
30
+ return new Promise((resolve, reject) => {
31
+ canvas.toBlob((blob) => {
32
+ if (blob) {
33
+ resolve(blob);
34
+ }
35
+ else {
36
+ reject(new Error("Failed to create Blob."));
37
+ }
38
+ }, type, quality);
39
+ });
40
+ }
41
+ CanvasUtils.toBlob = toBlob;
42
+ /**
43
+ * Trim the canvas to remove empty space around the signature
44
+ * @param canvas Canvas element
45
+ * @returns Trimmed canvas
46
+ */
47
+ function trim(canvas) {
48
+ const ctx = canvas.getContext("2d");
49
+ const { width, height } = canvas;
50
+ const { data } = ctx.getImageData(0, 0, width, height);
51
+ let top = height;
52
+ let left = width;
53
+ let right = 0;
54
+ let bottom = 0;
55
+ for (let y = 0; y < height; y++) {
56
+ for (let x = 0; x < width; x++) {
57
+ if (data[(y * width + x) * 4 + 3] === 0)
58
+ continue;
59
+ if (x < left)
60
+ left = x;
61
+ if (x > right)
62
+ right = x;
63
+ if (y < top)
64
+ top = y;
65
+ if (y > bottom)
66
+ bottom = y;
67
+ }
68
+ }
69
+ const trimmed = document.createElement("canvas");
70
+ // Empty canvas
71
+ if (right < left || bottom < top)
72
+ return trimmed;
73
+ trimmed.width = right - left + 1;
74
+ trimmed.height = bottom - top + 1;
75
+ trimmed
76
+ .getContext("2d")
77
+ .drawImage(canvas, left, top, trimmed.width, trimmed.height, 0, 0, trimmed.width, trimmed.height);
78
+ return trimmed;
79
+ }
80
+ CanvasUtils.trim = trim;
81
+ })(CanvasUtils || (CanvasUtils = {}));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@etsoo/materialui",
3
- "version": "1.6.76",
3
+ "version": "1.6.77",
4
4
  "description": "TypeScript Material-UI Implementation",
5
5
  "main": "lib/cjs/index.js",
6
6
  "module": "lib/mjs/index.js",
@@ -44,9 +44,9 @@
44
44
  "@etsoo/notificationbase": "^1.1.72",
45
45
  "@etsoo/react": "^1.8.94",
46
46
  "@etsoo/shared": "^1.2.87",
47
- "@mui/icons-material": "^9.1.1",
48
- "@mui/material": "^9.1.2",
49
- "@mui/x-data-grid": "^9.7.0",
47
+ "@mui/icons-material": "^9.2.0",
48
+ "@mui/material": "^9.2.0",
49
+ "@mui/x-data-grid": "^9.8.0",
50
50
  "chart.js": "^4.5.1",
51
51
  "chartjs-plugin-datalabels": "^2.2.0",
52
52
  "dompurify": "^3.4.11",
@@ -58,7 +58,8 @@
58
58
  "react-chartjs-2": "^5.3.1",
59
59
  "react-dom": "^19.2.7",
60
60
  "react-draggable": "^4.7.0",
61
- "react-imask": "7.6.1"
61
+ "react-imask": "7.6.1",
62
+ "signature_pad": "^5.1.3"
62
63
  },
63
64
  "overrides": {
64
65
  "react": "$react",
@@ -0,0 +1,215 @@
1
+ import Button from "@mui/material/Button";
2
+ import Paper from "@mui/material/Paper";
3
+ import React from "react";
4
+ import SignaturePad, { Options } from "signature_pad";
5
+ import ClearIcon from "@mui/icons-material/Clear";
6
+ import SaveIcon from "@mui/icons-material/Save";
7
+ import { ResponsiveStyleValue } from "@mui/system";
8
+ import { CanvasUtils } from "./utils/CanvasUtils";
9
+ import Stack from "@mui/material/Stack";
10
+ import ButtonGroup from "@mui/material/ButtonGroup";
11
+
12
+ /**
13
+ * Extended SignaturePad class with trim method
14
+ */
15
+ export class SignaturePadEx extends SignaturePad {
16
+ constructor(
17
+ private readonly canvasElement: HTMLCanvasElement,
18
+ options?: Options
19
+ ) {
20
+ super(canvasElement, options);
21
+ }
22
+
23
+ /**
24
+ * Trim the canvas to remove empty space around the signature
25
+ * @returns Trimmed canvas
26
+ */
27
+ trim(): HTMLCanvasElement {
28
+ return CanvasUtils.trim(this.canvasElement);
29
+ }
30
+ }
31
+
32
+ /**
33
+ * Props for SignaturePadComponent
34
+ */
35
+ export type SignaturePadComponentProps = {
36
+ /**
37
+ * Width of the canvas
38
+ */
39
+ width: number;
40
+
41
+ /**
42
+ * Height of the canvas
43
+ */
44
+ height: number;
45
+
46
+ /**
47
+ * Options for SignaturePad
48
+ */
49
+ options?: Options;
50
+
51
+ /**
52
+ * Ref for the SignaturePadEx instance
53
+ */
54
+ ref: React.RefObject<SignaturePadEx | null>;
55
+ };
56
+
57
+ /**
58
+ * React component for SignaturePad
59
+ * @param props Props
60
+ * @returns Component
61
+ */
62
+ export function SignaturePadComponent(props: SignaturePadComponentProps) {
63
+ // Destruct
64
+ const { width, height, options, ref } = props;
65
+
66
+ // Refs
67
+ const canvasRef = React.useRef<HTMLCanvasElement>(null);
68
+
69
+ React.useEffect(() => {
70
+ const canvas = canvasRef.current;
71
+ if (!canvas) return;
72
+
73
+ ref.current = new SignaturePadEx(canvas, options);
74
+
75
+ return () => {
76
+ ref.current?.off();
77
+ };
78
+ }, [options, ref]);
79
+
80
+ return <canvas ref={canvasRef} width={width} height={height} />;
81
+ }
82
+
83
+ type Placement = "top" | "right" | "bottom" | "left";
84
+ type Alignment = "flex-start" | "center" | "flex-end";
85
+
86
+ /**
87
+ * Methods for SignaturePadFull component
88
+ */
89
+ export interface SignaturePadFullMethods {
90
+ getInstance(): SignaturePadEx | null;
91
+ }
92
+
93
+ /**
94
+ * Props for SignaturePadFull component
95
+ */
96
+ export type SignaturePadFullProps = Partial<
97
+ Omit<SignaturePadComponentProps, "ref">
98
+ > & {
99
+ /**
100
+ * Label for the clear button
101
+ * @default "Clear"
102
+ */
103
+ clearLabel?: string;
104
+
105
+ /**
106
+ * Label for the save button
107
+ * @default "Save"
108
+ */
109
+ saveLabel?: string;
110
+
111
+ /**
112
+ * Ref for the SignaturePadFull instance
113
+ */
114
+ mRef?: React.RefObject<SignaturePadFullMethods | null>;
115
+
116
+ /**
117
+ * Placement of the buttons relative to the canvas
118
+ * @default ["right", "flex-start"]
119
+ */
120
+ placement?: [Placement?, alignment?: Alignment];
121
+
122
+ /**
123
+ * Callback when the signature is saved
124
+ * @param signaturePad SignaturePadEx instance
125
+ * @returns void or Promise<void>
126
+ */
127
+ onSave?: (signaturePad: SignaturePadEx) => void | Promise<void>;
128
+
129
+ /**
130
+ * Spacing between the canvas and buttons
131
+ * @default 0.5
132
+ */
133
+ spacing?: number;
134
+ };
135
+
136
+ /**
137
+ * Full SignaturePad component with clear and save buttons
138
+ *
139
+ * @param props Props
140
+ * @returns Component
141
+ */
142
+ export function SignaturePadFull(props: SignaturePadFullProps) {
143
+ // Destruct
144
+ const {
145
+ clearLabel = "Clear",
146
+ saveLabel = "Save",
147
+ width = 600,
148
+ height = 300,
149
+ mRef,
150
+ onSave,
151
+ options,
152
+ placement = ["right", "flex-start"],
153
+ spacing = 0.5
154
+ } = props;
155
+
156
+ // Ref
157
+ const signaturePadRef = React.useRef<SignaturePadEx>(null);
158
+
159
+ React.useImperativeHandle(mRef, () => ({
160
+ getInstance: () => signaturePadRef.current
161
+ }));
162
+
163
+ function handleClear() {
164
+ signaturePadRef.current?.clear();
165
+ }
166
+
167
+ function handleSave() {
168
+ const sp = signaturePadRef.current;
169
+ if (!sp || sp.isEmpty()) return;
170
+
171
+ onSave?.(sp);
172
+ }
173
+
174
+ const [p = "right", a = "flex-start"] = placement;
175
+
176
+ let stackDirection: ResponsiveStyleValue<
177
+ "row" | "row-reverse" | "column" | "column-reverse"
178
+ >;
179
+ let buttonGroupOrientation: "horizontal" | "vertical";
180
+
181
+ if (p === "top" || p === "bottom") {
182
+ stackDirection = p === "top" ? "column-reverse" : "column";
183
+ buttonGroupOrientation = "horizontal";
184
+ } else {
185
+ stackDirection = p === "left" ? "row-reverse" : "row";
186
+ buttonGroupOrientation = "vertical";
187
+ }
188
+
189
+ return (
190
+ <Stack direction={stackDirection} spacing={spacing} sx={{ alignItems: a }}>
191
+ <Paper sx={{ width, height }}>
192
+ <SignaturePadComponent
193
+ width={width}
194
+ height={height}
195
+ options={options}
196
+ ref={signaturePadRef}
197
+ />
198
+ </Paper>
199
+ <ButtonGroup orientation={buttonGroupOrientation}>
200
+ <Button onClick={handleClear} startIcon={<ClearIcon />}>
201
+ {clearLabel}
202
+ </Button>
203
+ {onSave && (
204
+ <Button
205
+ variant="contained"
206
+ onClick={handleSave}
207
+ startIcon={<SaveIcon />}
208
+ >
209
+ {saveLabel}
210
+ </Button>
211
+ )}
212
+ </ButtonGroup>
213
+ </Stack>
214
+ );
215
+ }
@@ -314,7 +314,7 @@ export function UserAvatarEditor(props: UserAvatarEditorProps) {
314
314
  startIcon={<ImageIcon />}
315
315
  fullWidth
316
316
  onUploadFiles={handleFileUpload}
317
- inputProps={{ accept: "image/png, image/jpeg" }}
317
+ inputProps={{ accept: "image/png, image/jpeg, image/webp" }}
318
318
  >
319
319
  {selectFileLabel}
320
320
  </FileUploadButton>
package/src/index.ts CHANGED
@@ -41,6 +41,8 @@ export * from "./texts/DateText";
41
41
  export * from "./texts/MoneyText";
42
42
  export * from "./texts/NumberText";
43
43
 
44
+ export * from "./utils/CanvasUtils";
45
+
44
46
  export * from "./AddresSelector";
45
47
  export * from "./AuditDisplay";
46
48
  export * from "./AutocompleteExtendedProps";
@@ -128,6 +130,7 @@ export * from "./SearchOptionGroup";
128
130
  export * from "./SelectBool";
129
131
  export * from "./SelectEx";
130
132
  export * from "./ShowDataComparison";
133
+ export * from "./SignaturePadComponent";
131
134
  export * from "./Switch";
132
135
  export * from "./SwitchAnt";
133
136
  export * from "./SwitchField";
@@ -0,0 +1,103 @@
1
+ /**
2
+ * Utility functions for canvas operations
3
+ */
4
+ export namespace CanvasUtils {
5
+ /**
6
+ * JPEG content type
7
+ */
8
+ export const JPG = "image/jpeg";
9
+
10
+ /**
11
+ * PNG content type
12
+ */
13
+ export const PNG = "image/png";
14
+
15
+ /**
16
+ * WEBP content type
17
+ */
18
+ export const WEBP = "image/webp";
19
+
20
+ /**
21
+ * SVG content type
22
+ */
23
+ export const SVG = "image/svg+xml";
24
+
25
+ /**
26
+ * Convert canvas to Blob
27
+ * @param canvas Canvas element
28
+ * @param type MIME type of the image
29
+ * @param quality Image quality (0 to 1)
30
+ * @returns Promise that resolves to a Blob
31
+ */
32
+ export function toBlob(
33
+ canvas: HTMLCanvasElement,
34
+ type = "image/png",
35
+ quality = 1
36
+ ): Promise<Blob> {
37
+ return new Promise((resolve, reject) => {
38
+ canvas.toBlob(
39
+ (blob) => {
40
+ if (blob) {
41
+ resolve(blob);
42
+ } else {
43
+ reject(new Error("Failed to create Blob."));
44
+ }
45
+ },
46
+ type,
47
+ quality
48
+ );
49
+ });
50
+ }
51
+
52
+ /**
53
+ * Trim the canvas to remove empty space around the signature
54
+ * @param canvas Canvas element
55
+ * @returns Trimmed canvas
56
+ */
57
+ export function trim(canvas: HTMLCanvasElement): HTMLCanvasElement {
58
+ const ctx = canvas.getContext("2d")!;
59
+ const { width, height } = canvas;
60
+
61
+ const { data } = ctx.getImageData(0, 0, width, height);
62
+
63
+ let top = height;
64
+ let left = width;
65
+ let right = 0;
66
+ let bottom = 0;
67
+
68
+ for (let y = 0; y < height; y++) {
69
+ for (let x = 0; x < width; x++) {
70
+ if (data[(y * width + x) * 4 + 3] === 0) continue;
71
+
72
+ if (x < left) left = x;
73
+ if (x > right) right = x;
74
+ if (y < top) top = y;
75
+ if (y > bottom) bottom = y;
76
+ }
77
+ }
78
+
79
+ const trimmed = document.createElement("canvas");
80
+
81
+ // Empty canvas
82
+ if (right < left || bottom < top) return trimmed;
83
+
84
+ trimmed.width = right - left + 1;
85
+ trimmed.height = bottom - top + 1;
86
+
87
+ trimmed
88
+ .getContext("2d")!
89
+ .drawImage(
90
+ canvas,
91
+ left,
92
+ top,
93
+ trimmed.width,
94
+ trimmed.height,
95
+ 0,
96
+ 0,
97
+ trimmed.width,
98
+ trimmed.height
99
+ );
100
+
101
+ return trimmed;
102
+ }
103
+ }