@kwiz/fluentui 1.0.73 → 1.0.75

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.
Files changed (92) hide show
  1. package/.github/workflows/npm-publish.yml +24 -24
  2. package/LICENSE +21 -21
  3. package/README.md +53 -53
  4. package/dist/@types/forwardRef.d.ts +0 -0
  5. package/dist/@types/forwardRef.js +1 -0
  6. package/dist/@types/forwardRef.js.map +1 -0
  7. package/dist/controls/error-boundary copy.d.ts +23 -0
  8. package/dist/controls/error-boundary copy.js +33 -0
  9. package/dist/controls/error-boundary copy.js.map +1 -0
  10. package/dist/controls/menu.js +2 -2
  11. package/dist/controls/menu.js.map +1 -1
  12. package/dist/controls/search.js +19 -11
  13. package/dist/controls/search.js.map +1 -1
  14. package/dist/controls/svg.js +21 -21
  15. package/dist/controls/svg.js.map +1 -1
  16. package/dist/helpers/common.d.ts +4 -0
  17. package/dist/helpers/common.js +2 -0
  18. package/dist/helpers/common.js.map +1 -0
  19. package/dist/helpers/context.d.ts +26 -0
  20. package/dist/helpers/context.js +15 -0
  21. package/dist/helpers/context.js.map +1 -0
  22. package/dist/helpers/drag-drop/exports.d.ts +12 -0
  23. package/dist/helpers/drag-drop/exports.js +3 -0
  24. package/dist/helpers/drag-drop/exports.js.map +1 -0
  25. package/dist/helpers/exports.d.ts +7 -0
  26. package/dist/helpers/exports.js +8 -0
  27. package/dist/helpers/exports.js.map +1 -0
  28. package/dist/helpers/use-editable-control.d.ts +1 -1
  29. package/dist/helpers/use-editable-control.js.map +1 -1
  30. package/package.json +85 -84
  31. package/src/_modules/config.ts +9 -9
  32. package/src/_modules/constants.ts +3 -3
  33. package/src/controls/ColorPickerDialog.tsx +83 -83
  34. package/src/controls/accordion.tsx +62 -62
  35. package/src/controls/button.tsx +180 -180
  36. package/src/controls/canvas/CustomEventTargetBase.ts +32 -32
  37. package/src/controls/canvas/DrawPad.tsx +296 -296
  38. package/src/controls/canvas/DrawPadManager.ts +694 -694
  39. package/src/controls/canvas/bezier.ts +109 -109
  40. package/src/controls/canvas/point.ts +44 -44
  41. package/src/controls/card-list.tsx +31 -31
  42. package/src/controls/card.tsx +77 -77
  43. package/src/controls/centered.tsx +14 -14
  44. package/src/controls/date.tsx +87 -87
  45. package/src/controls/diagram-picker.tsx +96 -96
  46. package/src/controls/divider.tsx +15 -15
  47. package/src/controls/dropdown.tsx +66 -66
  48. package/src/controls/error-boundary.tsx +41 -41
  49. package/src/controls/field-editor.tsx +42 -42
  50. package/src/controls/file-upload.tsx +155 -155
  51. package/src/controls/horizontal.tsx +48 -48
  52. package/src/controls/html-editor/editor.tsx +182 -182
  53. package/src/controls/index.ts +33 -33
  54. package/src/controls/input.tsx +160 -160
  55. package/src/controls/kwizoverflow.tsx +106 -106
  56. package/src/controls/list.tsx +119 -119
  57. package/src/controls/loading.tsx +10 -10
  58. package/src/controls/menu.tsx +173 -173
  59. package/src/controls/merge-text.tsx +126 -126
  60. package/src/controls/please-wait.tsx +32 -32
  61. package/src/controls/progress-bar.tsx +109 -109
  62. package/src/controls/prompt.tsx +121 -121
  63. package/src/controls/qrcode.tsx +36 -36
  64. package/src/controls/search.tsx +71 -61
  65. package/src/controls/section.tsx +133 -133
  66. package/src/controls/svg.tsx +138 -138
  67. package/src/controls/toolbar.tsx +46 -46
  68. package/src/controls/vertical-content.tsx +49 -49
  69. package/src/controls/vertical.tsx +42 -42
  70. package/src/helpers/block-nav.tsx +88 -88
  71. package/src/helpers/context-const.ts +29 -29
  72. package/src/helpers/context-export.tsx +77 -77
  73. package/src/helpers/context-internal.ts +13 -13
  74. package/src/helpers/drag-drop/drag-drop-container.tsx +53 -53
  75. package/src/helpers/drag-drop/drag-drop-context-internal.tsx +9 -9
  76. package/src/helpers/drag-drop/drag-drop-context.tsx +61 -61
  77. package/src/helpers/drag-drop/drag-drop.types.ts +21 -21
  78. package/src/helpers/drag-drop/index.ts +12 -12
  79. package/src/helpers/drag-drop/readme.md +75 -75
  80. package/src/helpers/drag-drop/use-draggable.ts +47 -47
  81. package/src/helpers/drag-drop/use-droppable.ts +38 -38
  82. package/src/helpers/forwardRef.ts +7 -7
  83. package/src/helpers/hooks-events.ts +149 -149
  84. package/src/helpers/hooks.tsx +141 -141
  85. package/src/helpers/index.ts +8 -8
  86. package/src/helpers/use-alerts.tsx +74 -74
  87. package/src/helpers/use-editable-control.tsx +37 -37
  88. package/src/helpers/use-toast.tsx +29 -29
  89. package/src/index.ts +2 -2
  90. package/src/styles/index.ts +1 -1
  91. package/src/styles/styles.ts +104 -104
  92. package/src/styles/theme.ts +90 -90
@@ -1,297 +1,297 @@
1
- import { Field, tokens } from "@fluentui/react-components";
2
- import { ArrowMaximizeRegular, ArrowMinimizeRegular, ArrowUploadRegular, CalligraphyPenRegular, DismissRegular } from "@fluentui/react-icons";
3
- import { debounce, getCSSVariableValue, ImageFileTypes, isElement, isNotEmptyString, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined } from "@kwiz/common";
4
- import * as React from "react";
5
- import { useAlerts, useElementSize, useStateEX } from "../../helpers";
6
- import { ButtonEX } from "../button";
7
- import { ColorPickerEx } from "../ColorPickerDialog";
8
- import { FileUpload } from "../file-upload";
9
- import { Horizontal } from "../horizontal";
10
- import { InputEx } from "../input";
11
- import { Vertical } from "../vertical";
12
- import DrawPadManager from "./DrawPadManager";
13
-
14
- interface iProps {
15
- BackgroundColor?: string;
16
- BorderColor?: string;
17
- LineColor?: string;
18
- minWidth?: number;
19
- minHeight?: number;
20
- /** url or base64 image data:image/png;base64,.... */
21
- Value?: string;
22
- /** when user hits clear, it will reset to this value
23
- * url or base64 image data:image/png;base64,....
24
- */
25
- DefaultBackdrop?: string;
26
- OnChange?: (newValue: string) => void;
27
- ReadOnly?: boolean;
28
- HideUpload?: boolean;
29
- HideClear?: boolean;
30
- //HideUndo?: boolean;
31
- HideColorPicker?: boolean;
32
- disabled?: boolean;
33
- /** true - will prompt user for his name, string will just sign as that string */
34
- allowSigning?: boolean | string;
35
- allowFullscreen?: boolean;
36
- }
37
- var _userName: string = null;
38
- export const DrawPadUserName = {
39
- get: () => { return _userName },
40
- set: (userName: string) => { _userName = userName }
41
- };
42
-
43
- const fontName = "Dancing Script";
44
- let fontLoading: Promise<boolean> = null;
45
- let fontReady = false;
46
- export const DrawPad: React.FunctionComponent<iProps> = (props) => {
47
- const [LineColor, setLineColor] = useStateEX<string>(props.LineColor || tokens.colorBrandForeground1);
48
- const [manager, setmanager] = useStateEX<DrawPadManager>(null);
49
- const [canUndo, setcanUndo] = useStateEX<boolean>(false, { skipUpdateIfSame: true });
50
- const [signed, setSigned] = useStateEX<boolean>(false);
51
- const [fullscreen, setFullscreen] = useStateEX<boolean>(false);
52
- const onChangeRef = React.useRef(props.OnChange);
53
- const alerts = useAlerts();
54
- const canvasArea: React.RefObject<HTMLCanvasElement> = React.useRef();
55
- const canvasContainerDiv = React.useRef<HTMLDivElement>();
56
-
57
- //keep onChange up to date
58
- React.useEffect(() => {
59
- onChangeRef.current = props.OnChange;
60
- }, [props.OnChange]);
61
- //if user name provided - keep it
62
- React.useEffect(() => {
63
- if (isNotEmptyString(props.allowSigning)) {
64
- DrawPadUserName.set(props.allowSigning);
65
- }
66
- }, [props.allowSigning]);
67
-
68
- //load font for sign as text, if needed
69
- React.useEffect(() => {
70
- if (props.allowSigning && !fontLoading) {
71
- let DancingScriptFont = new FontFace(
72
- fontName,
73
- "url(https://fonts.gstatic.com/s/dancingscript/v25/If2RXTr6YS-zF4S-kcSWSVi_szLgiuE.woff2) format('woff2')",
74
- {
75
- style: "normal",
76
- weight: "400 700",
77
- display: "swap",
78
- unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
79
- }
80
- );
81
-
82
- fontLoading = DancingScriptFont.load().then(async loadedFont => {
83
- document.fonts.add(loadedFont);
84
- await document.fonts.ready;
85
- fontReady = true;
86
- return true;
87
- });
88
- }
89
- }, [props.allowSigning]);
90
-
91
- //setup manager
92
- React.useEffect(() => {
93
- if (!props.ReadOnly && canvasArea.current && !canvasArea.current["canvas_initialized"]) {
94
- canvasArea.current["canvas_initialized"] = true;//for some reason this still fires twice on the same div
95
- //this gets called after each render...
96
- let manager = new DrawPadManager(canvasArea.current);
97
- setmanager(manager);
98
- manager.addEventListener("endStroke", () => {
99
- //because this is used in an addEventListener - need to have a ref to get the most up to date onChange
100
- if (onChangeRef.current) {
101
- let canvasValue = manager.toPng();
102
- setcanUndo(true);
103
- onChangeRef.current(canvasValue);
104
- }
105
- });
106
- }
107
- }, [canvasArea]);
108
- //update line color selection
109
- React.useEffect(() => {
110
- if (manager) {
111
- manager.penColor.value = getCSSVariableValue(LineColor, canvasArea.current);
112
- }
113
- }, [manager, LineColor]);
114
-
115
- //Set props value to canvas on initial load or when owner element changes the prop
116
- React.useEffect(() => {
117
- if (manager) {
118
- let canvasValue = manager.toPng();
119
- let neededValue = isNotEmptyString(props.Value)
120
- ? props.Value
121
- : isNotEmptyString(props.DefaultBackdrop)
122
- ? props.DefaultBackdrop
123
- : "";
124
- if (canvasValue !== neededValue ||
125
- (
126
- isNullOrEmptyString(canvasValue) &&
127
- isNullOrEmptyString(neededValue)
128
- )
129
- ) {
130
- UpdateCanvas(neededValue);//if called repeatedly or too fast - may not load correctly
131
- }
132
- }
133
- });//cant use those - canvas loses value on drag so must run this every time: [props.Value, props.DefaultBackdrop, manager]);
134
-
135
- //set value to canvas
136
- const UpdateCanvas = React.useCallback(debounce((valueToSet: string) => {
137
- if (valueToSet === "") manager.clear();
138
- else manager.fromDataURL(valueToSet);
139
- }, 200, this), [manager]);
140
-
141
- //enable/disable canvas manager
142
- React.useEffect(() => {
143
- if (manager) {
144
- if (props.disabled) manager.off();//stop accepting strokes, but still allow to set a default value
145
- else manager.on();
146
- }
147
- }, [manager, props.disabled]);
148
-
149
- const sign = React.useCallback((name: string) => {
150
- let canvas = canvasArea.current;
151
- if (!isElement(canvas)) {
152
- return;
153
- }
154
- setSigned(true);
155
-
156
- let height = canvas.clientHeight;
157
- let width = canvas.clientWidth;
158
-
159
- let ctx = canvas.getContext("2d");
160
- ctx.fillStyle = getCSSVariableValue(LineColor, canvasArea.current);
161
-
162
- let fontSize = 0.6 * height;
163
- ctx.font = `${fontSize}px ${fontName}`;
164
- let textMeasurement = ctx.measureText(name);
165
- let textWidth = textMeasurement.width;
166
- let maxWidth = 0.9 * width;
167
-
168
- while (textWidth > maxWidth && fontSize > 1) {
169
- fontSize = fontSize - 1;
170
- ctx.font = `${fontSize}px ${fontName}`;
171
- textMeasurement = ctx.measureText(name);
172
- textWidth = textMeasurement.width;
173
- }
174
-
175
- let x = (width - textWidth) / 2;
176
- let y = 0.6 * height; //baseline not starting point
177
- ctx.fillText(name, x, y, width);
178
- let url = canvas.toDataURL("image/png");
179
- onChangeRef.current?.(url);
180
- }, [canvasArea, LineColor]);
181
-
182
- const onSignAs = React.useCallback(async () => {
183
- if (isNullOrUndefined(DrawPadUserName.get())) {
184
- //prompt user to type his name - then continue
185
- alerts.promptEX({
186
- //mountNode: canvasContainerDiv.current, this lets other content on the form cover the dialog
187
- title: "Sign as name",
188
- children: <Field label="Signing as" hint="Please type in your name" required>
189
- <InputEx onChange={(e, data) => DrawPadUserName.set(data.value)} />
190
- </Field>,
191
- onCancel: () => {
192
- DrawPadUserName.set(null);//get rid of anything they typed while dialog was open
193
- },
194
- onOK: () => {
195
- if (!isNullOrEmptyString(DrawPadUserName.get()))//need to test current since this won't be updated when state changes
196
- {
197
- sign(DrawPadUserName.get());
198
- }
199
- },
200
- });
201
- }
202
- else sign(DrawPadUserName.get());
203
- }, [canvasArea, LineColor]);
204
-
205
- const HideButtons = props.HideClear && props.HideColorPicker && props.HideUpload;
206
-
207
- const sizer = useElementSize(canvasContainerDiv.current);
208
- const [size, setSize] = useStateEX<{ width?: number; height?: number }>({});
209
- //handle canvas resizing
210
- React.useEffect(() => {
211
- if (canvasContainerDiv.current) {
212
- setSize({
213
- width: canvasContainerDiv.current.clientWidth,
214
- height: canvasContainerDiv.current.clientHeight,
215
- });
216
- if (manager) manager.resizeCanvas();
217
- }
218
- }, [canvasContainerDiv, sizer, manager]);
219
-
220
- return <Horizontal nogap fullscreen={fullscreen}>
221
- {alerts.alertPrompt}
222
- <div ref={canvasContainerDiv}
223
- style={{
224
- flexGrow: 1,
225
- position: "relative",
226
- minWidth: props.minWidth,
227
- minHeight: props.minHeight,
228
- backgroundColor: props.BackgroundColor,
229
- border: `1px solid ${props.BorderColor || tokens.colorNeutralStroke1}`
230
- }}>
231
- {props.ReadOnly
232
- ? <img src={isNotEmptyString(props.Value) ? props.Value : props.DefaultBackdrop} style={{ position: "absolute", left: 0, top: 0, width: size.width, height: size.height }} />
233
- :
234
- <div style={{ position: "absolute", left: 0, top: 0, width: size.width, height: size.height }}>
235
- <canvas
236
- ref={canvasArea}
237
- style={{
238
- touchAction: "none",
239
- userSelect: "none",
240
- position: "absolute",
241
- left: 0,
242
- top: 0,
243
- width: size.width,
244
- height: size.height,
245
- border: tokens.colorBrandStroke1
246
- }} />
247
- {!signed
248
- && !isNullOrEmptyString(props.allowSigning)
249
- && !isNullOrEmptyArray(fontReady)
250
- && <ButtonEX
251
- style={{
252
- position: "absolute",
253
- bottom: 0,
254
- border: 0,
255
- margin: 0,
256
- right: 0,
257
- height: 16
258
- }}
259
- disabled={props.disabled}
260
- icon={<CalligraphyPenRegular />}
261
- title={`Sign as ${props.allowSigning === true ? "..." : props.allowSigning}`}
262
- onClick={() => {
263
- onSignAs();
264
- }}
265
- />}
266
- </div>
267
- }
268
- </div>
269
- {!props.ReadOnly && !HideButtons && <Vertical nogap>
270
- {props.HideColorPicker || <ColorPickerEx //mountNode={canvasContainerDiv.current} this lets other content on the form cover the dialog
271
- disabled={props.disabled} buttonOnly value={props.LineColor} onChange={newColor => {
272
- setLineColor(newColor);
273
- }} />}
274
- {/* todo: undo isn't working properly
275
- {props.HideUndo || <ButtonEX disabled={!canUndo} title="Undo" icon={<ArrowUndoRegular />} onClick={() => {
276
- manager.undoLast();
277
- setcanUndo(manager.canUndo());
278
- }} />} */}
279
- {props.HideClear || <ButtonEX disabled={props.disabled || isNullOrEmptyString(props.Value)} title="Clear" icon={<DismissRegular />} onClick={() => {
280
- //can call clear on the canvas, or can call the onchange which will cause a re-draw
281
- setSigned(false);
282
- onChangeRef.current?.("");
283
- }} />}
284
- {props.HideUpload || <FileUpload disabled={props.disabled} title="Load background image" icon={<ArrowUploadRegular />} limitFileTypes={ImageFileTypes} asBase64={base64 => {
285
- if (onChangeRef.current)
286
- onChangeRef.current?.(base64[0].base64);//this will trigger a change and state update
287
- else
288
- manager?.fromDataURL(base64[0].base64);//this will just set the image to the canvas but won't trigger a change event for the caller
289
- }} />}
290
- {props.allowFullscreen && <ButtonEX title="Full screen" disabled={props.disabled} icon={fullscreen ? <ArrowMinimizeRegular /> : <ArrowMaximizeRegular />} onClick={async () => {
291
- //can call clear on the canvas, or can call the onchange which will cause a re-draw
292
- await setFullscreen(!fullscreen);
293
- if (manager) manager.resizeCanvas();
294
- }} />}
295
- </Vertical>}
296
- </Horizontal>;
1
+ import { Field, tokens } from "@fluentui/react-components";
2
+ import { ArrowMaximizeRegular, ArrowMinimizeRegular, ArrowUploadRegular, CalligraphyPenRegular, DismissRegular } from "@fluentui/react-icons";
3
+ import { debounce, getCSSVariableValue, ImageFileTypes, isElement, isNotEmptyString, isNullOrEmptyArray, isNullOrEmptyString, isNullOrUndefined } from "@kwiz/common";
4
+ import * as React from "react";
5
+ import { useAlerts, useElementSize, useStateEX } from "../../helpers";
6
+ import { ButtonEX } from "../button";
7
+ import { ColorPickerEx } from "../ColorPickerDialog";
8
+ import { FileUpload } from "../file-upload";
9
+ import { Horizontal } from "../horizontal";
10
+ import { InputEx } from "../input";
11
+ import { Vertical } from "../vertical";
12
+ import DrawPadManager from "./DrawPadManager";
13
+
14
+ interface iProps {
15
+ BackgroundColor?: string;
16
+ BorderColor?: string;
17
+ LineColor?: string;
18
+ minWidth?: number;
19
+ minHeight?: number;
20
+ /** url or base64 image data:image/png;base64,.... */
21
+ Value?: string;
22
+ /** when user hits clear, it will reset to this value
23
+ * url or base64 image data:image/png;base64,....
24
+ */
25
+ DefaultBackdrop?: string;
26
+ OnChange?: (newValue: string) => void;
27
+ ReadOnly?: boolean;
28
+ HideUpload?: boolean;
29
+ HideClear?: boolean;
30
+ //HideUndo?: boolean;
31
+ HideColorPicker?: boolean;
32
+ disabled?: boolean;
33
+ /** true - will prompt user for his name, string will just sign as that string */
34
+ allowSigning?: boolean | string;
35
+ allowFullscreen?: boolean;
36
+ }
37
+ var _userName: string = null;
38
+ export const DrawPadUserName = {
39
+ get: () => { return _userName },
40
+ set: (userName: string) => { _userName = userName }
41
+ };
42
+
43
+ const fontName = "Dancing Script";
44
+ let fontLoading: Promise<boolean> = null;
45
+ let fontReady = false;
46
+ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
47
+ const [LineColor, setLineColor] = useStateEX<string>(props.LineColor || tokens.colorBrandForeground1);
48
+ const [manager, setmanager] = useStateEX<DrawPadManager>(null);
49
+ const [canUndo, setcanUndo] = useStateEX<boolean>(false, { skipUpdateIfSame: true });
50
+ const [signed, setSigned] = useStateEX<boolean>(false);
51
+ const [fullscreen, setFullscreen] = useStateEX<boolean>(false);
52
+ const onChangeRef = React.useRef(props.OnChange);
53
+ const alerts = useAlerts();
54
+ const canvasArea: React.RefObject<HTMLCanvasElement> = React.useRef();
55
+ const canvasContainerDiv = React.useRef<HTMLDivElement>();
56
+
57
+ //keep onChange up to date
58
+ React.useEffect(() => {
59
+ onChangeRef.current = props.OnChange;
60
+ }, [props.OnChange]);
61
+ //if user name provided - keep it
62
+ React.useEffect(() => {
63
+ if (isNotEmptyString(props.allowSigning)) {
64
+ DrawPadUserName.set(props.allowSigning);
65
+ }
66
+ }, [props.allowSigning]);
67
+
68
+ //load font for sign as text, if needed
69
+ React.useEffect(() => {
70
+ if (props.allowSigning && !fontLoading) {
71
+ let DancingScriptFont = new FontFace(
72
+ fontName,
73
+ "url(https://fonts.gstatic.com/s/dancingscript/v25/If2RXTr6YS-zF4S-kcSWSVi_szLgiuE.woff2) format('woff2')",
74
+ {
75
+ style: "normal",
76
+ weight: "400 700",
77
+ display: "swap",
78
+ unicodeRange: "U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD"
79
+ }
80
+ );
81
+
82
+ fontLoading = DancingScriptFont.load().then(async loadedFont => {
83
+ document.fonts.add(loadedFont);
84
+ await document.fonts.ready;
85
+ fontReady = true;
86
+ return true;
87
+ });
88
+ }
89
+ }, [props.allowSigning]);
90
+
91
+ //setup manager
92
+ React.useEffect(() => {
93
+ if (!props.ReadOnly && canvasArea.current && !canvasArea.current["canvas_initialized"]) {
94
+ canvasArea.current["canvas_initialized"] = true;//for some reason this still fires twice on the same div
95
+ //this gets called after each render...
96
+ let manager = new DrawPadManager(canvasArea.current);
97
+ setmanager(manager);
98
+ manager.addEventListener("endStroke", () => {
99
+ //because this is used in an addEventListener - need to have a ref to get the most up to date onChange
100
+ if (onChangeRef.current) {
101
+ let canvasValue = manager.toPng();
102
+ setcanUndo(true);
103
+ onChangeRef.current(canvasValue);
104
+ }
105
+ });
106
+ }
107
+ }, [canvasArea]);
108
+ //update line color selection
109
+ React.useEffect(() => {
110
+ if (manager) {
111
+ manager.penColor.value = getCSSVariableValue(LineColor, canvasArea.current);
112
+ }
113
+ }, [manager, LineColor]);
114
+
115
+ //Set props value to canvas on initial load or when owner element changes the prop
116
+ React.useEffect(() => {
117
+ if (manager) {
118
+ let canvasValue = manager.toPng();
119
+ let neededValue = isNotEmptyString(props.Value)
120
+ ? props.Value
121
+ : isNotEmptyString(props.DefaultBackdrop)
122
+ ? props.DefaultBackdrop
123
+ : "";
124
+ if (canvasValue !== neededValue ||
125
+ (
126
+ isNullOrEmptyString(canvasValue) &&
127
+ isNullOrEmptyString(neededValue)
128
+ )
129
+ ) {
130
+ UpdateCanvas(neededValue);//if called repeatedly or too fast - may not load correctly
131
+ }
132
+ }
133
+ });//cant use those - canvas loses value on drag so must run this every time: [props.Value, props.DefaultBackdrop, manager]);
134
+
135
+ //set value to canvas
136
+ const UpdateCanvas = React.useCallback(debounce((valueToSet: string) => {
137
+ if (valueToSet === "") manager.clear();
138
+ else manager.fromDataURL(valueToSet);
139
+ }, 200, this), [manager]);
140
+
141
+ //enable/disable canvas manager
142
+ React.useEffect(() => {
143
+ if (manager) {
144
+ if (props.disabled) manager.off();//stop accepting strokes, but still allow to set a default value
145
+ else manager.on();
146
+ }
147
+ }, [manager, props.disabled]);
148
+
149
+ const sign = React.useCallback((name: string) => {
150
+ let canvas = canvasArea.current;
151
+ if (!isElement(canvas)) {
152
+ return;
153
+ }
154
+ setSigned(true);
155
+
156
+ let height = canvas.clientHeight;
157
+ let width = canvas.clientWidth;
158
+
159
+ let ctx = canvas.getContext("2d");
160
+ ctx.fillStyle = getCSSVariableValue(LineColor, canvasArea.current);
161
+
162
+ let fontSize = 0.6 * height;
163
+ ctx.font = `${fontSize}px ${fontName}`;
164
+ let textMeasurement = ctx.measureText(name);
165
+ let textWidth = textMeasurement.width;
166
+ let maxWidth = 0.9 * width;
167
+
168
+ while (textWidth > maxWidth && fontSize > 1) {
169
+ fontSize = fontSize - 1;
170
+ ctx.font = `${fontSize}px ${fontName}`;
171
+ textMeasurement = ctx.measureText(name);
172
+ textWidth = textMeasurement.width;
173
+ }
174
+
175
+ let x = (width - textWidth) / 2;
176
+ let y = 0.6 * height; //baseline not starting point
177
+ ctx.fillText(name, x, y, width);
178
+ let url = canvas.toDataURL("image/png");
179
+ onChangeRef.current?.(url);
180
+ }, [canvasArea, LineColor]);
181
+
182
+ const onSignAs = React.useCallback(async () => {
183
+ if (isNullOrUndefined(DrawPadUserName.get())) {
184
+ //prompt user to type his name - then continue
185
+ alerts.promptEX({
186
+ //mountNode: canvasContainerDiv.current, this lets other content on the form cover the dialog
187
+ title: "Sign as name",
188
+ children: <Field label="Signing as" hint="Please type in your name" required>
189
+ <InputEx onChange={(e, data) => DrawPadUserName.set(data.value)} />
190
+ </Field>,
191
+ onCancel: () => {
192
+ DrawPadUserName.set(null);//get rid of anything they typed while dialog was open
193
+ },
194
+ onOK: () => {
195
+ if (!isNullOrEmptyString(DrawPadUserName.get()))//need to test current since this won't be updated when state changes
196
+ {
197
+ sign(DrawPadUserName.get());
198
+ }
199
+ },
200
+ });
201
+ }
202
+ else sign(DrawPadUserName.get());
203
+ }, [canvasArea, LineColor]);
204
+
205
+ const HideButtons = props.HideClear && props.HideColorPicker && props.HideUpload;
206
+
207
+ const sizer = useElementSize(canvasContainerDiv.current);
208
+ const [size, setSize] = useStateEX<{ width?: number; height?: number }>({});
209
+ //handle canvas resizing
210
+ React.useEffect(() => {
211
+ if (canvasContainerDiv.current) {
212
+ setSize({
213
+ width: canvasContainerDiv.current.clientWidth,
214
+ height: canvasContainerDiv.current.clientHeight,
215
+ });
216
+ if (manager) manager.resizeCanvas();
217
+ }
218
+ }, [canvasContainerDiv, sizer, manager]);
219
+
220
+ return <Horizontal nogap fullscreen={fullscreen}>
221
+ {alerts.alertPrompt}
222
+ <div ref={canvasContainerDiv}
223
+ style={{
224
+ flexGrow: 1,
225
+ position: "relative",
226
+ minWidth: props.minWidth,
227
+ minHeight: props.minHeight,
228
+ backgroundColor: props.BackgroundColor,
229
+ border: `1px solid ${props.BorderColor || tokens.colorNeutralStroke1}`
230
+ }}>
231
+ {props.ReadOnly
232
+ ? <img src={isNotEmptyString(props.Value) ? props.Value : props.DefaultBackdrop} style={{ position: "absolute", left: 0, top: 0, width: size.width, height: size.height }} />
233
+ :
234
+ <div style={{ position: "absolute", left: 0, top: 0, width: size.width, height: size.height }}>
235
+ <canvas
236
+ ref={canvasArea}
237
+ style={{
238
+ touchAction: "none",
239
+ userSelect: "none",
240
+ position: "absolute",
241
+ left: 0,
242
+ top: 0,
243
+ width: size.width,
244
+ height: size.height,
245
+ border: tokens.colorBrandStroke1
246
+ }} />
247
+ {!signed
248
+ && !isNullOrEmptyString(props.allowSigning)
249
+ && !isNullOrEmptyArray(fontReady)
250
+ && <ButtonEX
251
+ style={{
252
+ position: "absolute",
253
+ bottom: 0,
254
+ border: 0,
255
+ margin: 0,
256
+ right: 0,
257
+ height: 16
258
+ }}
259
+ disabled={props.disabled}
260
+ icon={<CalligraphyPenRegular />}
261
+ title={`Sign as ${props.allowSigning === true ? "..." : props.allowSigning}`}
262
+ onClick={() => {
263
+ onSignAs();
264
+ }}
265
+ />}
266
+ </div>
267
+ }
268
+ </div>
269
+ {!props.ReadOnly && !HideButtons && <Vertical nogap>
270
+ {props.HideColorPicker || <ColorPickerEx //mountNode={canvasContainerDiv.current} this lets other content on the form cover the dialog
271
+ disabled={props.disabled} buttonOnly value={props.LineColor} onChange={newColor => {
272
+ setLineColor(newColor);
273
+ }} />}
274
+ {/* todo: undo isn't working properly
275
+ {props.HideUndo || <ButtonEX disabled={!canUndo} title="Undo" icon={<ArrowUndoRegular />} onClick={() => {
276
+ manager.undoLast();
277
+ setcanUndo(manager.canUndo());
278
+ }} />} */}
279
+ {props.HideClear || <ButtonEX disabled={props.disabled || isNullOrEmptyString(props.Value)} title="Clear" icon={<DismissRegular />} onClick={() => {
280
+ //can call clear on the canvas, or can call the onchange which will cause a re-draw
281
+ setSigned(false);
282
+ onChangeRef.current?.("");
283
+ }} />}
284
+ {props.HideUpload || <FileUpload disabled={props.disabled} title="Load background image" icon={<ArrowUploadRegular />} limitFileTypes={ImageFileTypes} asBase64={base64 => {
285
+ if (onChangeRef.current)
286
+ onChangeRef.current?.(base64[0].base64);//this will trigger a change and state update
287
+ else
288
+ manager?.fromDataURL(base64[0].base64);//this will just set the image to the canvas but won't trigger a change event for the caller
289
+ }} />}
290
+ {props.allowFullscreen && <ButtonEX title="Full screen" disabled={props.disabled} icon={fullscreen ? <ArrowMinimizeRegular /> : <ArrowMaximizeRegular />} onClick={async () => {
291
+ //can call clear on the canvas, or can call the onchange which will cause a re-draw
292
+ await setFullscreen(!fullscreen);
293
+ if (manager) manager.resizeCanvas();
294
+ }} />}
295
+ </Vertical>}
296
+ </Horizontal>;
297
297
  }