@kwiz/fluentui 1.0.74 → 1.0.76

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 (94) 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.d.ts +3 -1
  11. package/dist/controls/menu.js +44 -26
  12. package/dist/controls/menu.js.map +1 -1
  13. package/dist/controls/search.js +19 -11
  14. package/dist/controls/search.js.map +1 -1
  15. package/dist/controls/svg.js +21 -21
  16. package/dist/controls/svg.js.map +1 -1
  17. package/dist/helpers/common.d.ts +4 -0
  18. package/dist/helpers/common.js +2 -0
  19. package/dist/helpers/common.js.map +1 -0
  20. package/dist/helpers/context.d.ts +26 -0
  21. package/dist/helpers/context.js +15 -0
  22. package/dist/helpers/context.js.map +1 -0
  23. package/dist/helpers/drag-drop/exports.d.ts +12 -0
  24. package/dist/helpers/drag-drop/exports.js +3 -0
  25. package/dist/helpers/drag-drop/exports.js.map +1 -0
  26. package/dist/helpers/exports.d.ts +7 -0
  27. package/dist/helpers/exports.js +8 -0
  28. package/dist/helpers/exports.js.map +1 -0
  29. package/dist/helpers/hooks.d.ts +3 -1
  30. package/dist/helpers/hooks.js +18 -0
  31. package/dist/helpers/hooks.js.map +1 -1
  32. package/package.json +85 -84
  33. package/src/_modules/config.ts +9 -9
  34. package/src/_modules/constants.ts +3 -3
  35. package/src/controls/ColorPickerDialog.tsx +83 -83
  36. package/src/controls/accordion.tsx +62 -62
  37. package/src/controls/button.tsx +180 -180
  38. package/src/controls/canvas/CustomEventTargetBase.ts +32 -32
  39. package/src/controls/canvas/DrawPad.tsx +296 -296
  40. package/src/controls/canvas/DrawPadManager.ts +694 -694
  41. package/src/controls/canvas/bezier.ts +109 -109
  42. package/src/controls/canvas/point.ts +44 -44
  43. package/src/controls/card-list.tsx +31 -31
  44. package/src/controls/card.tsx +77 -77
  45. package/src/controls/centered.tsx +14 -14
  46. package/src/controls/date.tsx +87 -87
  47. package/src/controls/diagram-picker.tsx +96 -96
  48. package/src/controls/divider.tsx +15 -15
  49. package/src/controls/dropdown.tsx +66 -66
  50. package/src/controls/error-boundary.tsx +41 -41
  51. package/src/controls/field-editor.tsx +42 -42
  52. package/src/controls/file-upload.tsx +155 -155
  53. package/src/controls/horizontal.tsx +48 -48
  54. package/src/controls/html-editor/editor.tsx +182 -182
  55. package/src/controls/index.ts +33 -33
  56. package/src/controls/input.tsx +160 -160
  57. package/src/controls/kwizoverflow.tsx +106 -106
  58. package/src/controls/list.tsx +119 -119
  59. package/src/controls/loading.tsx +10 -10
  60. package/src/controls/menu.tsx +195 -173
  61. package/src/controls/merge-text.tsx +126 -126
  62. package/src/controls/please-wait.tsx +32 -32
  63. package/src/controls/progress-bar.tsx +109 -109
  64. package/src/controls/prompt.tsx +121 -121
  65. package/src/controls/qrcode.tsx +36 -36
  66. package/src/controls/search.tsx +71 -61
  67. package/src/controls/section.tsx +133 -133
  68. package/src/controls/svg.tsx +138 -138
  69. package/src/controls/toolbar.tsx +46 -46
  70. package/src/controls/vertical-content.tsx +49 -49
  71. package/src/controls/vertical.tsx +42 -42
  72. package/src/helpers/block-nav.tsx +88 -88
  73. package/src/helpers/context-const.ts +29 -29
  74. package/src/helpers/context-export.tsx +77 -77
  75. package/src/helpers/context-internal.ts +13 -13
  76. package/src/helpers/drag-drop/drag-drop-container.tsx +53 -53
  77. package/src/helpers/drag-drop/drag-drop-context-internal.tsx +9 -9
  78. package/src/helpers/drag-drop/drag-drop-context.tsx +61 -61
  79. package/src/helpers/drag-drop/drag-drop.types.ts +21 -21
  80. package/src/helpers/drag-drop/index.ts +12 -12
  81. package/src/helpers/drag-drop/readme.md +75 -75
  82. package/src/helpers/drag-drop/use-draggable.ts +47 -47
  83. package/src/helpers/drag-drop/use-droppable.ts +38 -38
  84. package/src/helpers/forwardRef.ts +7 -7
  85. package/src/helpers/hooks-events.ts +149 -149
  86. package/src/helpers/hooks.tsx +162 -141
  87. package/src/helpers/index.ts +8 -8
  88. package/src/helpers/use-alerts.tsx +74 -74
  89. package/src/helpers/use-editable-control.tsx +37 -37
  90. package/src/helpers/use-toast.tsx +29 -29
  91. package/src/index.ts +2 -2
  92. package/src/styles/index.ts +1 -1
  93. package/src/styles/styles.ts +104 -104
  94. 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
  }