@kwiz/fluentui 1.0.40 → 1.0.41

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  export * from './controls/accordion';
2
2
  export * from './controls/button';
3
+ export * from './controls/canvas/DrawPad';
3
4
  export * from './controls/centered';
5
+ export * from './controls/ColorPickerDialog';
4
6
  export * from './controls/date';
5
7
  export * from './controls/divider';
6
8
  export * from './controls/dropdown';
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,qBAAqB,CAAC;AACpC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE5E,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,sBAAsB,CAAC;AACrC,cAAc,mBAAmB,CAAC;AAClC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,qBAAqB,CAAC;AACpC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,yBAAyB,CAAC;AACxC,cAAc,wBAAwB,CAAC;AACvC,cAAc,uBAAuB,CAAC;AACtC,cAAc,+BAA+B,CAAC;AAC9C,cAAc,kBAAkB,CAAC;AACjC,cAAc,yBAAyB,CAAC;AACxC,cAAc,iBAAiB,CAAC;AAChC,cAAc,oBAAoB,CAAC;AACnC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC;AAClC,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,qBAAqB,CAAC;AACpC,cAAc,6BAA6B,CAAC;AAC5C,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAC;AAE5E,cAAc,6BAA6B,CAAC;AAC5C,cAAc,iBAAiB,CAAC;AAChC,OAAO,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kwiz/fluentui",
3
- "version": "1.0.40",
3
+ "version": "1.0.41",
4
4
  "description": "KWIZ common controls for FluentUI",
5
5
  "module": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -56,12 +56,13 @@
56
56
  "typescript": "^5.3.3"
57
57
  },
58
58
  "dependencies": {
59
- "@kwiz/common": "^1.0.83",
59
+ "@kwiz/common": "^1.0.84",
60
60
  "esbuild": "^0.19.12",
61
61
  "get-tsconfig": "^4.7.2",
62
62
  "jodit-react": "^4.1.2",
63
63
  "react-dnd": "^16.0.1",
64
64
  "react-dnd-html5-backend": "^16.0.1",
65
+ "react-pick-color": "^2.0.0",
65
66
  "resolve-pkg-maps": "^1.0.0"
66
67
  },
67
68
  "peerDependencies": {
@@ -0,0 +1,76 @@
1
+ import { Field } from "@fluentui/react-components";
2
+ import { ColorRegular } from "@fluentui/react-icons";
3
+ import { isFunction, isNullOrEmptyString, isNumber } from "@kwiz/common";
4
+ import * as React from "react";
5
+ import ColorPicker, { Color } from 'react-pick-color';
6
+ import { useEffectOnlyOnMount, useStateEX } from "../helpers/hooks";
7
+ import { ButtonEX } from "./button";
8
+ import { InputEx } from "./input";
9
+ import { Prompter } from "./prompt";
10
+ export interface iProps {
11
+ label?: string;
12
+ value: string;
13
+ onChange: (newValue: string) => void;
14
+ required?: boolean;
15
+ showValidationErrors?: boolean;
16
+ underlined?: boolean;
17
+ width?: number;
18
+ buttonOnly?: boolean;
19
+ placeholder?: string;
20
+ }
21
+
22
+ export const ColorPickerEx: React.FunctionComponent<iProps> = (props) => {
23
+ const [isOpen, setIsOpen] = useStateEX<boolean>(false);
24
+ const [selectedColor, setSelectedColor] = useStateEX<string>(props.value);
25
+
26
+ const getColorCells = React.useCallback(() => {
27
+ let cells: Color[] = [
28
+ "white", "black"
29
+ ];
30
+ return cells;
31
+ }, useEffectOnlyOnMount);
32
+ return <>
33
+ {props.buttonOnly
34
+ ? <ButtonEX
35
+ title="Open color picker"
36
+ icon={<ColorRegular
37
+ color={selectedColor} />
38
+ }
39
+ onClick={() => setIsOpen(true)} />
40
+ : <Field label={props.label}
41
+ required={props.required === true}
42
+ validationMessage={props.showValidationErrors && props.required === true && isNullOrEmptyString(selectedColor) ? "You can't leave this blank." : undefined}
43
+ >
44
+ <InputEx
45
+ placeholder={props.placeholder || "Enter value here"}
46
+ style={isNumber(props.width) ? { width: props.width } : undefined}
47
+ value={selectedColor}
48
+ onChange={(e, data) => {
49
+ setSelectedColor(data.value);
50
+ if (isFunction(props.onChange)) {
51
+ props.onChange(data.value);
52
+ }
53
+ }}
54
+ contentAfter={<ButtonEX
55
+ title="Open color picker"
56
+ icon={<ColorRegular
57
+ color={selectedColor} />
58
+ }
59
+ onClick={() => setIsOpen(true)} />}
60
+ />
61
+ </Field>}
62
+ {isOpen && <Prompter maxWidth={332}
63
+ hideOk hideCancel onCancel={() => {
64
+ if (isFunction(props.onChange)) {
65
+ props.onChange(selectedColor);
66
+ }
67
+ setIsOpen(false);
68
+ }} showCancelInTitle
69
+ title={props.label || "Choose a color"}
70
+ >
71
+ <ColorPicker color={selectedColor} onChange={color => setSelectedColor(color.hex)}
72
+ presets={getColorCells()}
73
+ />
74
+ </Prompter>}
75
+ </>;
76
+ };
@@ -0,0 +1,33 @@
1
+ export class CustomEventTargetBase implements EventTarget {
2
+ private _et: EventTarget;
3
+
4
+ public constructor() {
5
+ try {
6
+ this._et = new EventTarget();
7
+ } catch (error) {
8
+ // Using document as EventTarget to support iOS 13 and older.
9
+ // Because EventTarget constructor just exists at iOS 14 and later.
10
+ this._et = document;
11
+ }
12
+ }
13
+
14
+ public addEventListener(
15
+ type: string,
16
+ listener: EventListenerOrEventListenerObject | null,
17
+ options?: boolean | AddEventListenerOptions,
18
+ ): void {
19
+ this._et.addEventListener(type, listener, options);
20
+ }
21
+
22
+ public dispatchEvent(event: Event): boolean {
23
+ return this._et.dispatchEvent(event);
24
+ }
25
+
26
+ public removeEventListener(
27
+ type: string,
28
+ callback: EventListenerOrEventListenerObject | null,
29
+ options?: boolean | EventListenerOptions,
30
+ ): void {
31
+ this._et.removeEventListener(type, callback, options);
32
+ }
33
+ }
@@ -0,0 +1,195 @@
1
+ import { tokens } from "@fluentui/react-components";
2
+ import { ArrowUploadRegular, CalligraphyPenRegular, DismissRegular } from "@fluentui/react-icons";
3
+ import { ImageFileTypes, debounce, isElement, isFunction, isNullOrEmptyArray, isNullOrEmptyString } from "@kwiz/common";
4
+ import * as React from "react";
5
+ import { useStateEX } from "../../helpers/hooks";
6
+ import { ButtonEX } from "../button";
7
+ import { ColorPickerEx } from "../ColorPickerDialog";
8
+ import { FileUpload } from "../file-upload";
9
+ import { Horizontal } from "../horizontal";
10
+ import { Vertical } from "../vertical";
11
+ import DrawPadManager from "./DrawPadManager";
12
+
13
+ interface iProps {
14
+ BackgroundColor?: string;
15
+ BorderColor?: string;
16
+ LineColor?: string;
17
+ Width?: number;
18
+ Height?: number;
19
+ Value?: string;
20
+ OnChange?: (newValue: string) => void;
21
+ ReadOnly?: boolean;
22
+ HideButtons?: boolean;
23
+ SignAsText?: string;
24
+ }
25
+ export const DrawPad: React.FunctionComponent<iProps> = (props) => {
26
+ const [LineColor, setLineColor] = useStateEX<string>(props.LineColor || tokens.colorBrandForeground1);
27
+ const [manager, setmanager] = useStateEX<DrawPadManager>(null);
28
+ const [canUndo, setcanUndo] = useStateEX<boolean>(false);
29
+ const [loadedFontNames, setloadedFontNames] = useStateEX<string[]>([]);
30
+
31
+ const canvasArea: React.RefObject<HTMLCanvasElement> = React.useRef();
32
+ const containerEle = React.useRef<HTMLDivElement>();
33
+
34
+ //load font for sign as text
35
+ React.useEffect(() => {
36
+ if (props.SignAsText && isNullOrEmptyArray(loadedFontNames)) {
37
+ let DancingScriptFont = new FontFace(
38
+ "Dancing Script",
39
+ "url(https://fonts.gstatic.com/s/dancingscript/v25/If2RXTr6YS-zF4S-kcSWSVi_szLgiuE.woff2) format('woff2')",
40
+ {
41
+ style: "normal",
42
+ weight: "400 700",
43
+ display: "swap",
44
+ 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"
45
+ }
46
+ );
47
+
48
+ DancingScriptFont.load().then(async loadedFont => {
49
+ document.fonts.add(loadedFont);
50
+ await document.fonts.ready;
51
+ setloadedFontNames(["Dancing Script"]);
52
+ });
53
+ }
54
+ }, [props.SignAsText, loadedFontNames]);
55
+
56
+ //setup manager
57
+ React.useEffect(() => {
58
+ if (!props.ReadOnly) {
59
+ //this gets called after each render...
60
+ if (!manager) {
61
+ if (canvasArea.current) {
62
+ let manager = new DrawPadManager(canvasArea.current);
63
+ setmanager(manager).then(() => UpdateCanvas());
64
+ if (isFunction(props.OnChange)) {
65
+ manager.addEventListener("endStroke", () => {
66
+ let value = "";
67
+ if (!manager.isEmpty()) {
68
+ value = manager.toDataURL("image/png");
69
+ }
70
+ if (!canUndo)
71
+ setcanUndo(true);
72
+
73
+ props.OnChange(value);
74
+ });
75
+ }
76
+ }
77
+ }
78
+ else {
79
+ UpdateCanvas();
80
+ }
81
+ }
82
+ });//run every time after render
83
+
84
+ //set value to canvas
85
+ const UpdateCanvas = React.useCallback(debounce(() => {
86
+ if (manager) {
87
+ manager.penColor.value = LineColor;
88
+
89
+ if (!isNullOrEmptyString(props.Value))
90
+ manager.fromDataURL(props.Value, { clear: true });
91
+ else
92
+ manager.clear();
93
+ }
94
+ }, 200, this), [manager, props.Value, LineColor]);
95
+
96
+ const onSignAs = React.useCallback(() => {
97
+ let canvas = canvasArea.current;
98
+ if (!isElement(canvas)) {
99
+ return;
100
+ }
101
+
102
+ let height = canvas.clientHeight;
103
+ let width = canvas.clientWidth;
104
+ let fontName = loadedFontNames[0];
105
+ let signAs = props.SignAsText;
106
+
107
+ let ctx = canvas.getContext("2d");
108
+ ctx.fillStyle = LineColor || LineColor || tokens.colorBrandForeground1;
109
+
110
+ let fontSize = 0.6 * height;
111
+ ctx.font = `${fontSize}px ${fontName}`;
112
+ let textMeasurement = ctx.measureText(signAs);
113
+ let textWidth = textMeasurement.width;
114
+ let maxWidth = 0.9 * width;
115
+
116
+ while (textWidth > maxWidth && fontSize > 1) {
117
+ fontSize = fontSize - 1;
118
+ ctx.font = `${fontSize}px ${fontName}`;
119
+ textMeasurement = ctx.measureText(signAs);
120
+ textWidth = textMeasurement.width;
121
+ }
122
+
123
+ let x = (width - textWidth) / 2;
124
+ let y = 0.6 * height; //baseline not starting point
125
+ ctx.fillText(signAs, x, y, width);
126
+ let url = canvas.toDataURL("image/png");
127
+ props.OnChange(url);
128
+ }, [canvasArea, props.OnChange]);
129
+
130
+
131
+
132
+ let width = props.Width > 0 ? props.Width : 400;
133
+ let height = props.Height > 0 ? props.Height : 200;
134
+
135
+ return <div ref={containerEle}><Horizontal>
136
+ <div style={{
137
+ position: "relative",
138
+ width: width,
139
+ height: height,
140
+ backgroundColor: props.BackgroundColor,
141
+ border: `1px solid ${props.BorderColor || tokens.colorNeutralStroke1}`
142
+ }}>
143
+ {props.ReadOnly
144
+ ? <img src={props.Value} style={{ position: "absolute", left: 0, top: 0, width: width, height: height }} />
145
+ :
146
+ <div style={{ position: "absolute", left: 0, top: 0, width: width, height: height }}>
147
+ <canvas
148
+ ref={canvasArea}
149
+ style={{
150
+ touchAction: "none",
151
+ userSelect: "none",
152
+ position: "absolute",
153
+ left: 0,
154
+ top: 0,
155
+ width: width,
156
+ height: height,
157
+ border: tokens.colorBrandStroke1
158
+ }} />
159
+ {isNullOrEmptyString(props.Value)
160
+ && !isNullOrEmptyString(props.SignAsText)
161
+ && !isNullOrEmptyArray(loadedFontNames)
162
+ && <ButtonEX
163
+ style={{
164
+ position: "absolute",
165
+ bottom: 0,
166
+ border: 0,
167
+ margin: 0,
168
+ right: 0,
169
+ height: 16
170
+ }}
171
+ icon={<CalligraphyPenRegular />}
172
+ title={`Sign as ${props.SignAsText}`}
173
+ onClick={() => {
174
+ onSignAs();
175
+ }}
176
+ />}
177
+ </div>
178
+ }
179
+ </div>
180
+ {!props.ReadOnly && !props.HideButtons && <Vertical nogap>
181
+ <ColorPickerEx buttonOnly value={props.LineColor} onChange={newColor => {
182
+ setLineColor(newColor);
183
+ }} />
184
+ <ButtonEX disabled={isNullOrEmptyString(props.Value)} title="Clear" icon={<DismissRegular />} onClick={() => {
185
+ //can call clear on the canvas, or can call the onchange which will cause a re-draw
186
+ props.OnChange("");
187
+ }} />
188
+ <FileUpload title="Load background image" icon={<ArrowUploadRegular />} limitFileTypes={ImageFileTypes} asBase64={base64 => {
189
+ props.OnChange(base64[0].base64);//this will trigger a change and state update
190
+ //self.state.manager.fromDataURL(base64);//this will just set the image to the canvas but won't trigger a change
191
+ }} />
192
+ </Vertical>}
193
+ </Horizontal>
194
+ </div>;
195
+ }