@kwiz/fluentui 1.0.78 → 1.0.79
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/LICENSE +21 -21
- package/README.md +53 -53
- package/dist/controls/svg.js +21 -21
- package/dist/controls/svg.js.map +1 -1
- package/package.json +85 -85
- package/.dependency-cruiser.js +0 -399
- package/.github/workflows/npm-publish.yml +0 -24
- package/dist/@types/forwardRef.d.ts +0 -0
- package/dist/@types/forwardRef.js +0 -1
- package/dist/@types/forwardRef.js.map +0 -1
- package/dist/controls/error-boundary copy.d.ts +0 -23
- package/dist/controls/error-boundary copy.js +0 -33
- package/dist/controls/error-boundary copy.js.map +0 -1
- package/dist/helpers/common.d.ts +0 -4
- package/dist/helpers/common.js +0 -2
- package/dist/helpers/common.js.map +0 -1
- package/dist/helpers/context.d.ts +0 -26
- package/dist/helpers/context.js +0 -15
- package/dist/helpers/context.js.map +0 -1
- package/dist/helpers/drag-drop/exports.d.ts +0 -12
- package/dist/helpers/drag-drop/exports.js +0 -3
- package/dist/helpers/drag-drop/exports.js.map +0 -1
- package/dist/helpers/exports.d.ts +0 -7
- package/dist/helpers/exports.js +0 -8
- package/dist/helpers/exports.js.map +0 -1
- package/src/_modules/config.ts +0 -9
- package/src/_modules/constants.ts +0 -3
- package/src/controls/ColorPickerDialog.tsx +0 -84
- package/src/controls/accordion.tsx +0 -62
- package/src/controls/button.tsx +0 -181
- package/src/controls/canvas/CustomEventTargetBase.ts +0 -33
- package/src/controls/canvas/DrawPad.tsx +0 -297
- package/src/controls/canvas/DrawPadManager.ts +0 -695
- package/src/controls/canvas/bezier.ts +0 -110
- package/src/controls/canvas/point.ts +0 -45
- package/src/controls/card-list.tsx +0 -32
- package/src/controls/card.tsx +0 -78
- package/src/controls/centered.tsx +0 -15
- package/src/controls/date.tsx +0 -88
- package/src/controls/diagram-picker.tsx +0 -97
- package/src/controls/divider.tsx +0 -16
- package/src/controls/dropdown.tsx +0 -67
- package/src/controls/error-boundary.tsx +0 -42
- package/src/controls/field-editor.tsx +0 -43
- package/src/controls/file-upload.tsx +0 -156
- package/src/controls/horizontal.tsx +0 -49
- package/src/controls/html-editor/editor.tsx +0 -182
- package/src/controls/index.ts +0 -33
- package/src/controls/input.tsx +0 -161
- package/src/controls/kwizoverflow.tsx +0 -107
- package/src/controls/list.tsx +0 -120
- package/src/controls/loading.tsx +0 -11
- package/src/controls/menu.tsx +0 -196
- package/src/controls/merge-text.tsx +0 -126
- package/src/controls/please-wait.tsx +0 -33
- package/src/controls/progress-bar.tsx +0 -110
- package/src/controls/prompt.tsx +0 -122
- package/src/controls/qrcode.tsx +0 -37
- package/src/controls/search.tsx +0 -72
- package/src/controls/section.tsx +0 -134
- package/src/controls/svg.tsx +0 -139
- package/src/controls/toolbar.tsx +0 -47
- package/src/controls/vertical-content.tsx +0 -50
- package/src/controls/vertical.tsx +0 -43
- package/src/helpers/block-nav.tsx +0 -89
- package/src/helpers/context-const.ts +0 -30
- package/src/helpers/context-export.tsx +0 -78
- package/src/helpers/context-internal.ts +0 -14
- package/src/helpers/drag-drop/drag-drop-container.tsx +0 -54
- package/src/helpers/drag-drop/drag-drop-context-internal.tsx +0 -10
- package/src/helpers/drag-drop/drag-drop-context.tsx +0 -62
- package/src/helpers/drag-drop/drag-drop.types.ts +0 -21
- package/src/helpers/drag-drop/index.ts +0 -12
- package/src/helpers/drag-drop/readme.md +0 -76
- package/src/helpers/drag-drop/use-draggable.ts +0 -48
- package/src/helpers/drag-drop/use-droppable.ts +0 -39
- package/src/helpers/forwardRef.ts +0 -7
- package/src/helpers/hooks-events.ts +0 -150
- package/src/helpers/hooks.tsx +0 -175
- package/src/helpers/index.ts +0 -8
- package/src/helpers/use-alerts.tsx +0 -75
- package/src/helpers/use-editable-control.tsx +0 -38
- package/src/helpers/use-toast.tsx +0 -30
- package/src/index.ts +0 -3
- package/src/styles/index.ts +0 -1
- package/src/styles/styles.ts +0 -105
- package/src/styles/theme.ts +0 -91
@@ -1,84 +0,0 @@
|
|
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";
|
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
|
-
disabled?: boolean;
|
21
|
-
|
22
|
-
/** specify a specific mount node for this dialog */
|
23
|
-
mountNode?: HTMLElement | null | {
|
24
|
-
element?: HTMLElement | null;
|
25
|
-
className?: string;
|
26
|
-
}
|
27
|
-
}
|
28
|
-
|
29
|
-
export const ColorPickerEx: React.FunctionComponent<iProps> = (props) => {
|
30
|
-
const [isOpen, setIsOpen] = useStateEX<boolean>(false);
|
31
|
-
const [selectedColor, setSelectedColor] = useStateEX<string>(props.value);
|
32
|
-
|
33
|
-
const getColorCells = React.useCallback(() => {
|
34
|
-
let cells: Color[] = [
|
35
|
-
"white", "black"
|
36
|
-
];
|
37
|
-
return cells;
|
38
|
-
}, useEffectOnlyOnMount);
|
39
|
-
return <>
|
40
|
-
{props.buttonOnly
|
41
|
-
? <ButtonEX disabled={props.disabled}
|
42
|
-
title="Open color picker"
|
43
|
-
icon={<ColorRegular
|
44
|
-
color={selectedColor} />
|
45
|
-
}
|
46
|
-
onClick={(e) => setIsOpen(true)} />
|
47
|
-
: <Field label={props.label}
|
48
|
-
required={props.required === true}
|
49
|
-
validationMessage={props.showValidationErrors && props.required === true && isNullOrEmptyString(selectedColor) ? "You can't leave this blank." : undefined}
|
50
|
-
>
|
51
|
-
<InputEx disabled={props.disabled}
|
52
|
-
placeholder={props.placeholder || "Enter value here"}
|
53
|
-
style={isNumber(props.width) ? { width: props.width } : undefined}
|
54
|
-
value={selectedColor}
|
55
|
-
onChange={(e, data) => {
|
56
|
-
setSelectedColor(data.value);
|
57
|
-
if (isFunction(props.onChange)) {
|
58
|
-
props.onChange(data.value);
|
59
|
-
}
|
60
|
-
}}
|
61
|
-
contentAfter={<ButtonEX disabled={props.disabled}
|
62
|
-
title="Open color picker"
|
63
|
-
icon={<ColorRegular
|
64
|
-
color={selectedColor} />
|
65
|
-
}
|
66
|
-
onClick={(e) => setIsOpen(true)} />
|
67
|
-
}
|
68
|
-
/>
|
69
|
-
</Field>}
|
70
|
-
{isOpen && <Prompter maxWidth={332} mountNode={props.mountNode}
|
71
|
-
hideOk hideCancel onCancel={() => {
|
72
|
-
if (isFunction(props.onChange)) {
|
73
|
-
props.onChange(selectedColor);
|
74
|
-
}
|
75
|
-
setIsOpen(false);
|
76
|
-
}} showCancelInTitle
|
77
|
-
title={props.label || "Choose a color"}
|
78
|
-
>
|
79
|
-
<ColorPicker color={selectedColor} onChange={color => setSelectedColor(color.hex)}
|
80
|
-
presets={getColorCells()}
|
81
|
-
/>
|
82
|
-
</Prompter>}
|
83
|
-
</>;
|
84
|
-
};
|
@@ -1,62 +0,0 @@
|
|
1
|
-
import { makeStyles } from "@fluentui/react-components";
|
2
|
-
import { ChevronRightRegular } from "@fluentui/react-icons";
|
3
|
-
import * as React from 'react';
|
4
|
-
import { KnownClassNames } from "../styles/styles";
|
5
|
-
import { ButtonEX } from "./button";
|
6
|
-
import { DividerEX } from "./divider";
|
7
|
-
import { Horizontal } from "./horizontal";
|
8
|
-
import { Vertical } from "./vertical";
|
9
|
-
|
10
|
-
const useStyles = makeStyles({
|
11
|
-
opened: {
|
12
|
-
transform: "rotate(90deg)",
|
13
|
-
transition: "transform 200ms ease-out"
|
14
|
-
},
|
15
|
-
header: {
|
16
|
-
paddingLeft: 0
|
17
|
-
},
|
18
|
-
root: {
|
19
|
-
maxHeight: "100%"
|
20
|
-
},
|
21
|
-
rootFill: {
|
22
|
-
minHeight: "100%"
|
23
|
-
},
|
24
|
-
body: {
|
25
|
-
overflow: "auto",
|
26
|
-
}
|
27
|
-
});
|
28
|
-
|
29
|
-
interface iProps {
|
30
|
-
/** optional: send the key for the group you want to open by default */
|
31
|
-
opened?: string;
|
32
|
-
fillHeight?: boolean;
|
33
|
-
groups: {
|
34
|
-
key: string;
|
35
|
-
title: string;
|
36
|
-
icon?: JSX.Element;
|
37
|
-
content: JSX.Element;
|
38
|
-
}[];
|
39
|
-
}
|
40
|
-
export const AccordionEX: React.FunctionComponent<iProps> = (props) => {
|
41
|
-
const classes = useStyles();
|
42
|
-
const [opened, setOpened] = React.useState(props.opened || props.groups[0].key);
|
43
|
-
return (<Vertical main css={[classes.root, props.fillHeight && classes.rootFill, KnownClassNames.accordion]}>
|
44
|
-
{props.groups.map(group => <React.Fragment key={group.key}>
|
45
|
-
<ButtonEX className={`${classes.header} ${KnownClassNames.accordionHeader} ${opened === group.key ? ` ${KnownClassNames.isOpen}` : ''}`}
|
46
|
-
icon={<ChevronRightRegular className={opened === group.key ? classes.opened : ''} />}
|
47
|
-
title={group.title} showTitleWithIcon dontCenterText
|
48
|
-
onClick={() => setOpened(group.key)}
|
49
|
-
/>
|
50
|
-
<DividerEX />
|
51
|
-
{group.key === opened && <>
|
52
|
-
<Horizontal main css={[classes.body, KnownClassNames.accordionBodyWrapper]}>
|
53
|
-
<Vertical main css={[KnownClassNames.accordionBody]}>
|
54
|
-
{group.content}
|
55
|
-
</Vertical>
|
56
|
-
</Horizontal>
|
57
|
-
<DividerEX />
|
58
|
-
</>}
|
59
|
-
</React.Fragment>)}
|
60
|
-
</Vertical>
|
61
|
-
);
|
62
|
-
}
|
package/src/controls/button.tsx
DELETED
@@ -1,181 +0,0 @@
|
|
1
|
-
import { Button, ButtonProps, CompoundButton, compoundButtonClassNames, CompoundButtonProps, makeStyles, mergeClasses, tokens, Tooltip } from '@fluentui/react-components';
|
2
|
-
import { capitalizeFirstLetter, isFunction, isNullOrEmptyString, isNullOrUndefined, isString, PushNoDuplicate } from '@kwiz/common';
|
3
|
-
import React from 'react';
|
4
|
-
import { useKWIZFluentContext } from '../helpers/context-internal';
|
5
|
-
import { commonSizes, KnownClassNames } from '../styles/styles';
|
6
|
-
|
7
|
-
interface IProps {
|
8
|
-
title: string;//required
|
9
|
-
showTitleWithIcon?: boolean;
|
10
|
-
dontStretch?: boolean;
|
11
|
-
hideOnPrint?: boolean;
|
12
|
-
dontCenterText?: boolean;
|
13
|
-
hoverIcon?: JSX.Element;
|
14
|
-
hoverTitle?: string;
|
15
|
-
onClick?: (e: React.MouseEvent<HTMLButtonElement | HTMLAnchorElement, MouseEvent>) => void | undefined;//type the onClick arg
|
16
|
-
}
|
17
|
-
interface IPropsCompound extends IProps {
|
18
|
-
width?: string | number;
|
19
|
-
}
|
20
|
-
|
21
|
-
export type ButtonEXProps = IProps & Omit<ButtonProps, "onClick" | "title">;
|
22
|
-
export type CompoundButtonEXProps = IPropsCompound & Omit<CompoundButtonProps, "onClick" | "title">;;
|
23
|
-
|
24
|
-
const useStyles = makeStyles({
|
25
|
-
buttonNoCenter: {
|
26
|
-
justifyContent: 'flex-start',
|
27
|
-
'& *': {
|
28
|
-
/* a button with no center that has content of a vertical, or multiple labels */
|
29
|
-
alignItems: 'flex-start'
|
30
|
-
}
|
31
|
-
},
|
32
|
-
danger: {
|
33
|
-
backgroundColor: tokens.colorStatusDangerBackground1,
|
34
|
-
color: tokens.colorStatusWarningForeground2,
|
35
|
-
|
36
|
-
[`& .${compoundButtonClassNames.secondaryContent}`]: {
|
37
|
-
color: tokens.colorStatusWarningForeground1
|
38
|
-
}
|
39
|
-
},
|
40
|
-
success: {
|
41
|
-
color: tokens.colorStatusSuccessForeground1,
|
42
|
-
|
43
|
-
[`& .${compoundButtonClassNames.secondaryContent}`]: {
|
44
|
-
color: tokens.colorStatusSuccessForeground1,
|
45
|
-
}
|
46
|
-
},
|
47
|
-
primarySubtle: {
|
48
|
-
color: tokens.colorBrandForeground1,
|
49
|
-
|
50
|
-
[`& .${compoundButtonClassNames.secondaryContent}`]: {
|
51
|
-
color: tokens.colorBrandForeground1,
|
52
|
-
}
|
53
|
-
},
|
54
|
-
dangerSubtle: {
|
55
|
-
color: tokens.colorStatusWarningForeground2,
|
56
|
-
|
57
|
-
[`& .${compoundButtonClassNames.secondaryContent}`]: {
|
58
|
-
color: tokens.colorStatusWarningForeground1
|
59
|
-
}
|
60
|
-
}
|
61
|
-
})
|
62
|
-
|
63
|
-
|
64
|
-
export const ButtonEX = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
65
|
-
const ctx = useKWIZFluentContext();
|
66
|
-
const [hover, setHover] = React.useState(false);
|
67
|
-
const trackHover = !isNullOrEmptyString(props.hoverTitle) || !isNullOrUndefined(props.hoverIcon);
|
68
|
-
|
69
|
-
const title = hover && !isNullOrEmptyString(props.hoverTitle) ? props.hoverTitle
|
70
|
-
: props.title || props['aria-label'];
|
71
|
-
const icon = hover && !isNullOrUndefined(props.hoverIcon) ? props.hoverIcon : props.icon;
|
72
|
-
let hasIcon = !isNullOrUndefined(icon);
|
73
|
-
let hasText = props.children || !hasIcon || (hasIcon && props.showTitleWithIcon === true);
|
74
|
-
|
75
|
-
const cssNames = useStyles();
|
76
|
-
let css: string[] = [];
|
77
|
-
|
78
|
-
if (props.hideOnPrint) PushNoDuplicate(css, KnownClassNames.printHide);
|
79
|
-
if (props.dontCenterText) PushNoDuplicate(css, cssNames.buttonNoCenter);
|
80
|
-
|
81
|
-
let btn = <Button ref={ref} appearance='subtle' {...props as any as ButtonProps} className={mergeClasses(...css, props.className)}
|
82
|
-
aria-label={title} title={undefined} icon={icon}
|
83
|
-
onMouseEnter={trackHover ? (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
84
|
-
setHover(true);
|
85
|
-
if (isFunction(props.onMouseEnter))
|
86
|
-
props.onMouseEnter(e as any);
|
87
|
-
} : props.onMouseEnter as any}
|
88
|
-
onMouseLeave={trackHover ? (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
|
89
|
-
setHover(false);
|
90
|
-
if (isFunction(props.onMouseLeave))
|
91
|
-
props.onMouseLeave(e as any);
|
92
|
-
} : props.onMouseLeave as any}
|
93
|
-
>{props.children ||
|
94
|
-
//no icon? will show the title by default
|
95
|
-
(hasText && capitalizeFirstLetter(title))}</Button>;
|
96
|
-
if (!hasText || props.children)//icon only or when content is different than props.title
|
97
|
-
btn = <Tooltip showDelay={1000} relationship='label' withArrow appearance='inverted' content={title}
|
98
|
-
mountNode={ctx.mountNode}
|
99
|
-
>
|
100
|
-
{btn}
|
101
|
-
</Tooltip>;
|
102
|
-
|
103
|
-
return (
|
104
|
-
props.dontStretch ? <div>{btn}</div> : btn
|
105
|
-
|
106
|
-
);
|
107
|
-
});
|
108
|
-
export const ButtonEXSecondary = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
109
|
-
const ctx = useKWIZFluentContext();
|
110
|
-
return (
|
111
|
-
<ButtonEX ref={ref} appearance='secondary' shape={ctx.buttonShape} {...props}></ButtonEX>
|
112
|
-
);
|
113
|
-
});
|
114
|
-
/** to be used in MessageBarActions for prominent actions. Otherwise use ButtonEX or ButtonEXDangerSubtle */
|
115
|
-
export const ButtonEXMessageBarAction = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
116
|
-
const ctx = useKWIZFluentContext();
|
117
|
-
return (
|
118
|
-
<ButtonEX ref={ref} appearance='secondary' hideOnPrint {...props}></ButtonEX>
|
119
|
-
);
|
120
|
-
});
|
121
|
-
export const ButtonEXPrimary = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
122
|
-
return (
|
123
|
-
<ButtonEXSecondary ref={ref} appearance='primary' {...props}>{props.children}</ButtonEXSecondary>
|
124
|
-
);
|
125
|
-
});
|
126
|
-
export const ButtonEXDanger = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
127
|
-
const cssNames = useStyles();
|
128
|
-
return (
|
129
|
-
<ButtonEXSecondary ref={ref} className={props.disabled ? undefined : cssNames.danger} {...props}>{props.children}</ButtonEXSecondary>
|
130
|
-
);
|
131
|
-
});
|
132
|
-
export const ButtonEXSuccess = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
133
|
-
const cssNames = useStyles();
|
134
|
-
return (
|
135
|
-
<ButtonEX ref={ref} className={cssNames.success} {...props}>{props.children}</ButtonEX>
|
136
|
-
);
|
137
|
-
});
|
138
|
-
export const ButtonEXPrimarySubtle = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
139
|
-
const cssNames = useStyles();
|
140
|
-
return (
|
141
|
-
<ButtonEX ref={ref} className={props.disabled ? undefined : cssNames.primarySubtle} {...props}>{props.children}</ButtonEX>
|
142
|
-
);
|
143
|
-
});
|
144
|
-
export const ButtonEXDangerSubtle = React.forwardRef<HTMLButtonElement, (ButtonEXProps)>((props, ref) => {
|
145
|
-
const cssNames = useStyles();
|
146
|
-
return (
|
147
|
-
<ButtonEX ref={ref} className={props.disabled ? undefined : cssNames.dangerSubtle} {...props}>{props.children}</ButtonEX>
|
148
|
-
);
|
149
|
-
});
|
150
|
-
|
151
|
-
export const CompoundButtonEX = React.forwardRef<HTMLButtonElement, (CompoundButtonEXProps)>((props, ref) => {
|
152
|
-
const ctx = useKWIZFluentContext();
|
153
|
-
let title = props.title || props['aria-label'];
|
154
|
-
let tooltip = isString(props.secondaryContent) ? props.secondaryContent : title;
|
155
|
-
let max = typeof (props.width) === "undefined" ? commonSizes.widthMedium : props.width;
|
156
|
-
return (
|
157
|
-
<Tooltip showDelay={1000} relationship='label' withArrow appearance='inverted' content={tooltip}
|
158
|
-
mountNode={ctx.mountNode}
|
159
|
-
>
|
160
|
-
<CompoundButton ref={ref} appearance='subtle' style={{ justifyContent: "flex-start", maxWidth: max }} {...props as any as CompoundButtonProps} aria-label={tooltip} title={undefined}>
|
161
|
-
{props.children || capitalizeFirstLetter(title)}</CompoundButton>
|
162
|
-
</Tooltip>
|
163
|
-
);
|
164
|
-
});
|
165
|
-
export const CompoundButtonEXSecondary = React.forwardRef<HTMLButtonElement, (CompoundButtonEXProps)>((props, ref) => {
|
166
|
-
const ctx = useKWIZFluentContext();
|
167
|
-
return (
|
168
|
-
<CompoundButtonEX ref={ref} appearance='secondary' shape={ctx.buttonShape} {...props}></CompoundButtonEX>
|
169
|
-
);
|
170
|
-
});
|
171
|
-
export const CompoundButtonEXPrimary = React.forwardRef<HTMLButtonElement, (CompoundButtonEXProps)>((props, ref) => {
|
172
|
-
return (
|
173
|
-
<CompoundButtonEXSecondary ref={ref} appearance='primary' {...props}>{props.children}</CompoundButtonEXSecondary>
|
174
|
-
);
|
175
|
-
});
|
176
|
-
export const CompoundButtonEXDanger = React.forwardRef<HTMLButtonElement, (CompoundButtonEXProps)>((props, ref) => {
|
177
|
-
const cssNames = useStyles();
|
178
|
-
return (
|
179
|
-
<CompoundButtonEXSecondary ref={ref} className={cssNames.danger} {...props}>{props.children}</CompoundButtonEXSecondary>
|
180
|
-
);
|
181
|
-
});
|
@@ -1,33 +0,0 @@
|
|
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
|
-
}
|
@@ -1,297 +0,0 @@
|
|
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
|
-
}
|