@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.
- package/.github/workflows/npm-publish.yml +24 -24
- package/LICENSE +21 -21
- package/README.md +53 -53
- package/dist/@types/forwardRef.d.ts +0 -0
- package/dist/@types/forwardRef.js +1 -0
- package/dist/@types/forwardRef.js.map +1 -0
- package/dist/controls/error-boundary copy.d.ts +23 -0
- package/dist/controls/error-boundary copy.js +33 -0
- package/dist/controls/error-boundary copy.js.map +1 -0
- package/dist/controls/menu.js +2 -2
- package/dist/controls/menu.js.map +1 -1
- package/dist/controls/search.js +19 -11
- package/dist/controls/search.js.map +1 -1
- package/dist/controls/svg.js +21 -21
- package/dist/controls/svg.js.map +1 -1
- package/dist/helpers/common.d.ts +4 -0
- package/dist/helpers/common.js +2 -0
- package/dist/helpers/common.js.map +1 -0
- package/dist/helpers/context.d.ts +26 -0
- package/dist/helpers/context.js +15 -0
- package/dist/helpers/context.js.map +1 -0
- package/dist/helpers/drag-drop/exports.d.ts +12 -0
- package/dist/helpers/drag-drop/exports.js +3 -0
- package/dist/helpers/drag-drop/exports.js.map +1 -0
- package/dist/helpers/exports.d.ts +7 -0
- package/dist/helpers/exports.js +8 -0
- package/dist/helpers/exports.js.map +1 -0
- package/dist/helpers/use-editable-control.d.ts +1 -1
- package/dist/helpers/use-editable-control.js.map +1 -1
- package/package.json +85 -84
- package/src/_modules/config.ts +9 -9
- package/src/_modules/constants.ts +3 -3
- package/src/controls/ColorPickerDialog.tsx +83 -83
- package/src/controls/accordion.tsx +62 -62
- package/src/controls/button.tsx +180 -180
- package/src/controls/canvas/CustomEventTargetBase.ts +32 -32
- package/src/controls/canvas/DrawPad.tsx +296 -296
- package/src/controls/canvas/DrawPadManager.ts +694 -694
- package/src/controls/canvas/bezier.ts +109 -109
- package/src/controls/canvas/point.ts +44 -44
- package/src/controls/card-list.tsx +31 -31
- package/src/controls/card.tsx +77 -77
- package/src/controls/centered.tsx +14 -14
- package/src/controls/date.tsx +87 -87
- package/src/controls/diagram-picker.tsx +96 -96
- package/src/controls/divider.tsx +15 -15
- package/src/controls/dropdown.tsx +66 -66
- package/src/controls/error-boundary.tsx +41 -41
- package/src/controls/field-editor.tsx +42 -42
- package/src/controls/file-upload.tsx +155 -155
- package/src/controls/horizontal.tsx +48 -48
- package/src/controls/html-editor/editor.tsx +182 -182
- package/src/controls/index.ts +33 -33
- package/src/controls/input.tsx +160 -160
- package/src/controls/kwizoverflow.tsx +106 -106
- package/src/controls/list.tsx +119 -119
- package/src/controls/loading.tsx +10 -10
- package/src/controls/menu.tsx +173 -173
- package/src/controls/merge-text.tsx +126 -126
- package/src/controls/please-wait.tsx +32 -32
- package/src/controls/progress-bar.tsx +109 -109
- package/src/controls/prompt.tsx +121 -121
- package/src/controls/qrcode.tsx +36 -36
- package/src/controls/search.tsx +71 -61
- package/src/controls/section.tsx +133 -133
- package/src/controls/svg.tsx +138 -138
- package/src/controls/toolbar.tsx +46 -46
- package/src/controls/vertical-content.tsx +49 -49
- package/src/controls/vertical.tsx +42 -42
- package/src/helpers/block-nav.tsx +88 -88
- package/src/helpers/context-const.ts +29 -29
- package/src/helpers/context-export.tsx +77 -77
- package/src/helpers/context-internal.ts +13 -13
- package/src/helpers/drag-drop/drag-drop-container.tsx +53 -53
- package/src/helpers/drag-drop/drag-drop-context-internal.tsx +9 -9
- package/src/helpers/drag-drop/drag-drop-context.tsx +61 -61
- package/src/helpers/drag-drop/drag-drop.types.ts +21 -21
- package/src/helpers/drag-drop/index.ts +12 -12
- package/src/helpers/drag-drop/readme.md +75 -75
- package/src/helpers/drag-drop/use-draggable.ts +47 -47
- package/src/helpers/drag-drop/use-droppable.ts +38 -38
- package/src/helpers/forwardRef.ts +7 -7
- package/src/helpers/hooks-events.ts +149 -149
- package/src/helpers/hooks.tsx +141 -141
- package/src/helpers/index.ts +8 -8
- package/src/helpers/use-alerts.tsx +74 -74
- package/src/helpers/use-editable-control.tsx +37 -37
- package/src/helpers/use-toast.tsx +29 -29
- package/src/index.ts +2 -2
- package/src/styles/index.ts +1 -1
- package/src/styles/styles.ts +104 -104
- package/src/styles/theme.ts +90 -90
package/src/controls/toolbar.tsx
CHANGED
@@ -1,47 +1,47 @@
|
|
1
|
-
import { Toolbar, ToolbarDivider, ToolbarGroup } from '@fluentui/react-components';
|
2
|
-
import React from 'react';
|
3
|
-
import { KnownClassNames } from '../styles/styles';
|
4
|
-
import { KWIZOverflow } from './kwizoverflow';
|
5
|
-
|
6
|
-
interface IProps {
|
7
|
-
/** toolbar buttons that may render or render in overflow */
|
8
|
-
buttonGroups: { elm: JSX.Element, overflowElement?: JSX.Element }[][];
|
9
|
-
/** side buttons that will always render, never overflow */
|
10
|
-
sideButtons?: JSX.Element;
|
11
|
-
}
|
12
|
-
export const ToolbarEX: React.FunctionComponent<IProps> = (props) => {
|
13
|
-
let elements: { id: string, priority?: number, elm: JSX.Element, overflowElement?: JSX.Element }[] = [];
|
14
|
-
props.buttonGroups.forEach((group, groupIndex) => {
|
15
|
-
group.forEach((button, buttonIndex) => {
|
16
|
-
const mapped = { ...button, id: `m${groupIndex}-${buttonIndex}` };
|
17
|
-
if (buttonIndex === 0 && groupIndex > 0 && props.buttonGroups[groupIndex - 1].length > 0) {
|
18
|
-
//first button, not first group, and previous group was not empty
|
19
|
-
//add divider
|
20
|
-
mapped.overflowElement = mapped.overflowElement || mapped.elm;
|
21
|
-
//this way if button rendes it is with the divider, but if it is in overflow - there will be no divider
|
22
|
-
mapped.elm = <span>
|
23
|
-
<ToolbarDivider style={{ display: 'inline-flex' }} />
|
24
|
-
{mapped.elm}
|
25
|
-
</span>;
|
26
|
-
|
27
|
-
}
|
28
|
-
elements.push(mapped);
|
29
|
-
})
|
30
|
-
});
|
31
|
-
|
32
|
-
return (
|
33
|
-
<KWIZOverflow className={KnownClassNames.printHide}
|
34
|
-
items={elements}
|
35
|
-
getKey={e => e.id}
|
36
|
-
renderItem={(e, i, overflow) => overflow && e.overflowElement || e.elm}
|
37
|
-
getPriority={e => e.priority || -1}
|
38
|
-
groupWrapper={children => <Toolbar aria-label="Default" style={{ justifyContent: "space-between" }}>
|
39
|
-
<ToolbarGroup role="presentation">
|
40
|
-
{children}
|
41
|
-
</ToolbarGroup>
|
42
|
-
<ToolbarGroup role="presentation">
|
43
|
-
{props.sideButtons}
|
44
|
-
</ToolbarGroup>
|
45
|
-
</Toolbar>}
|
46
|
-
/>);
|
1
|
+
import { Toolbar, ToolbarDivider, ToolbarGroup } from '@fluentui/react-components';
|
2
|
+
import React from 'react';
|
3
|
+
import { KnownClassNames } from '../styles/styles';
|
4
|
+
import { KWIZOverflow } from './kwizoverflow';
|
5
|
+
|
6
|
+
interface IProps {
|
7
|
+
/** toolbar buttons that may render or render in overflow */
|
8
|
+
buttonGroups: { elm: JSX.Element, overflowElement?: JSX.Element }[][];
|
9
|
+
/** side buttons that will always render, never overflow */
|
10
|
+
sideButtons?: JSX.Element;
|
11
|
+
}
|
12
|
+
export const ToolbarEX: React.FunctionComponent<IProps> = (props) => {
|
13
|
+
let elements: { id: string, priority?: number, elm: JSX.Element, overflowElement?: JSX.Element }[] = [];
|
14
|
+
props.buttonGroups.forEach((group, groupIndex) => {
|
15
|
+
group.forEach((button, buttonIndex) => {
|
16
|
+
const mapped = { ...button, id: `m${groupIndex}-${buttonIndex}` };
|
17
|
+
if (buttonIndex === 0 && groupIndex > 0 && props.buttonGroups[groupIndex - 1].length > 0) {
|
18
|
+
//first button, not first group, and previous group was not empty
|
19
|
+
//add divider
|
20
|
+
mapped.overflowElement = mapped.overflowElement || mapped.elm;
|
21
|
+
//this way if button rendes it is with the divider, but if it is in overflow - there will be no divider
|
22
|
+
mapped.elm = <span>
|
23
|
+
<ToolbarDivider style={{ display: 'inline-flex' }} />
|
24
|
+
{mapped.elm}
|
25
|
+
</span>;
|
26
|
+
|
27
|
+
}
|
28
|
+
elements.push(mapped);
|
29
|
+
})
|
30
|
+
});
|
31
|
+
|
32
|
+
return (
|
33
|
+
<KWIZOverflow className={KnownClassNames.printHide}
|
34
|
+
items={elements}
|
35
|
+
getKey={e => e.id}
|
36
|
+
renderItem={(e, i, overflow) => overflow && e.overflowElement || e.elm}
|
37
|
+
getPriority={e => e.priority || -1}
|
38
|
+
groupWrapper={children => <Toolbar aria-label="Default" style={{ justifyContent: "space-between" }}>
|
39
|
+
<ToolbarGroup role="presentation">
|
40
|
+
{children}
|
41
|
+
</ToolbarGroup>
|
42
|
+
<ToolbarGroup role="presentation">
|
43
|
+
{props.sideButtons}
|
44
|
+
</ToolbarGroup>
|
45
|
+
</Toolbar>}
|
46
|
+
/>);
|
47
47
|
}
|
@@ -1,50 +1,50 @@
|
|
1
|
-
import { makeStyles, mergeClasses } from '@fluentui/react-components';
|
2
|
-
import { isNotEmptyArray } from '@kwiz/common';
|
3
|
-
import React from 'react';
|
4
|
-
import { useWindowSize } from '../helpers';
|
5
|
-
|
6
|
-
const useStyles = makeStyles({
|
7
|
-
verticalContainer: {
|
8
|
-
position: "relative",
|
9
|
-
['& > div']: {
|
10
|
-
position: "absolute",
|
11
|
-
transform: "rotate(90deg)"
|
12
|
-
}
|
13
|
-
}
|
14
|
-
});
|
15
|
-
|
16
|
-
interface IProps {
|
17
|
-
css?: string[];
|
18
|
-
}
|
19
|
-
export const VerticalContent: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
|
20
|
-
const classes = useStyles();
|
21
|
-
let css: string[] = [classes.verticalContainer];
|
22
|
-
const size = useWindowSize();
|
23
|
-
|
24
|
-
let div = React.useRef();
|
25
|
-
let rotate = React.useRef();
|
26
|
-
|
27
|
-
if (isNotEmptyArray(props.css)) css.push(...props.css);
|
28
|
-
|
29
|
-
React.useEffect(() => {
|
30
|
-
if (div.current && rotate.current) {
|
31
|
-
let rootDiv = (div.current as HTMLDivElement);
|
32
|
-
let rotateDiv = (rotate.current as HTMLDivElement);
|
33
|
-
rootDiv.style.height = `${rotateDiv.clientWidth}px`;
|
34
|
-
rootDiv.style.width = `${rotateDiv.clientHeight}px`;
|
35
|
-
rootDiv.style.minHeight = `${rotateDiv.clientWidth}px`;
|
36
|
-
rootDiv.style.minWidth = `${rotateDiv.clientHeight}px`;
|
37
|
-
|
38
|
-
rotateDiv.style.top = `${(rotateDiv.clientWidth - rotateDiv.clientHeight) / 2}px`;
|
39
|
-
rotateDiv.style.left = `-${(rotateDiv.clientWidth - rotateDiv.clientHeight) / 2}px`;
|
40
|
-
}
|
41
|
-
}, [div, rotate, size.height, size.width]);
|
42
|
-
|
43
|
-
return (
|
44
|
-
<div ref={div} className={mergeClasses(...css)}>
|
45
|
-
<div ref={rotate}>
|
46
|
-
{props.children}
|
47
|
-
</div>
|
48
|
-
</div>
|
49
|
-
);
|
1
|
+
import { makeStyles, mergeClasses } from '@fluentui/react-components';
|
2
|
+
import { isNotEmptyArray } from '@kwiz/common';
|
3
|
+
import React from 'react';
|
4
|
+
import { useWindowSize } from '../helpers';
|
5
|
+
|
6
|
+
const useStyles = makeStyles({
|
7
|
+
verticalContainer: {
|
8
|
+
position: "relative",
|
9
|
+
['& > div']: {
|
10
|
+
position: "absolute",
|
11
|
+
transform: "rotate(90deg)"
|
12
|
+
}
|
13
|
+
}
|
14
|
+
});
|
15
|
+
|
16
|
+
interface IProps {
|
17
|
+
css?: string[];
|
18
|
+
}
|
19
|
+
export const VerticalContent: React.FunctionComponent<React.PropsWithChildren<IProps>> = (props) => {
|
20
|
+
const classes = useStyles();
|
21
|
+
let css: string[] = [classes.verticalContainer];
|
22
|
+
const size = useWindowSize();
|
23
|
+
|
24
|
+
let div = React.useRef();
|
25
|
+
let rotate = React.useRef();
|
26
|
+
|
27
|
+
if (isNotEmptyArray(props.css)) css.push(...props.css);
|
28
|
+
|
29
|
+
React.useEffect(() => {
|
30
|
+
if (div.current && rotate.current) {
|
31
|
+
let rootDiv = (div.current as HTMLDivElement);
|
32
|
+
let rotateDiv = (rotate.current as HTMLDivElement);
|
33
|
+
rootDiv.style.height = `${rotateDiv.clientWidth}px`;
|
34
|
+
rootDiv.style.width = `${rotateDiv.clientHeight}px`;
|
35
|
+
rootDiv.style.minHeight = `${rotateDiv.clientWidth}px`;
|
36
|
+
rootDiv.style.minWidth = `${rotateDiv.clientHeight}px`;
|
37
|
+
|
38
|
+
rotateDiv.style.top = `${(rotateDiv.clientWidth - rotateDiv.clientHeight) / 2}px`;
|
39
|
+
rotateDiv.style.left = `-${(rotateDiv.clientWidth - rotateDiv.clientHeight) / 2}px`;
|
40
|
+
}
|
41
|
+
}, [div, rotate, size.height, size.width]);
|
42
|
+
|
43
|
+
return (
|
44
|
+
<div ref={div} className={mergeClasses(...css)}>
|
45
|
+
<div ref={rotate}>
|
46
|
+
{props.children}
|
47
|
+
</div>
|
48
|
+
</div>
|
49
|
+
);
|
50
50
|
}
|
@@ -1,43 +1,43 @@
|
|
1
|
-
import { makeStyles } from '@fluentui/react-components';
|
2
|
-
import { isNotEmptyArray } from '@kwiz/common';
|
3
|
-
import React from 'react';
|
4
|
-
import { KnownClassNames, mixins } from '../styles/styles';
|
5
|
-
import { ISectionProps, Section } from './section';
|
6
|
-
|
7
|
-
const useStyles = makeStyles({
|
8
|
-
vertical: {
|
9
|
-
...mixins.flex,
|
10
|
-
flexDirection: 'column'
|
11
|
-
},
|
12
|
-
wrap: mixins.wrap,
|
13
|
-
nogap: mixins.nogap,
|
14
|
-
vCentered: {
|
15
|
-
justifyContent: "center"
|
16
|
-
},
|
17
|
-
|
18
|
-
})
|
19
|
-
|
20
|
-
interface IProps extends ISectionProps {
|
21
|
-
wrap?: boolean;
|
22
|
-
nogap?: boolean;
|
23
|
-
/** vertical centered */
|
24
|
-
vCentered?: boolean;
|
25
|
-
}
|
26
|
-
export const Vertical = React.forwardRef<HTMLDivElement, React.PropsWithChildren<IProps>>((props, ref) => {
|
27
|
-
const cssNames = useStyles();
|
28
|
-
let css: string[] = [KnownClassNames.vertical];
|
29
|
-
|
30
|
-
css.push(cssNames.vertical);
|
31
|
-
if (props.wrap)
|
32
|
-
css.push(cssNames.wrap);
|
33
|
-
if (props.nogap)
|
34
|
-
css.push(cssNames.nogap);
|
35
|
-
if (props.vCentered)
|
36
|
-
css.push(cssNames.vCentered);
|
37
|
-
|
38
|
-
if (isNotEmptyArray(props.css)) css.push(...props.css);
|
39
|
-
|
40
|
-
return (
|
41
|
-
<Section {...props} css={css} />
|
42
|
-
);
|
1
|
+
import { makeStyles } from '@fluentui/react-components';
|
2
|
+
import { isNotEmptyArray } from '@kwiz/common';
|
3
|
+
import React from 'react';
|
4
|
+
import { KnownClassNames, mixins } from '../styles/styles';
|
5
|
+
import { ISectionProps, Section } from './section';
|
6
|
+
|
7
|
+
const useStyles = makeStyles({
|
8
|
+
vertical: {
|
9
|
+
...mixins.flex,
|
10
|
+
flexDirection: 'column'
|
11
|
+
},
|
12
|
+
wrap: mixins.wrap,
|
13
|
+
nogap: mixins.nogap,
|
14
|
+
vCentered: {
|
15
|
+
justifyContent: "center"
|
16
|
+
},
|
17
|
+
|
18
|
+
})
|
19
|
+
|
20
|
+
interface IProps extends ISectionProps {
|
21
|
+
wrap?: boolean;
|
22
|
+
nogap?: boolean;
|
23
|
+
/** vertical centered */
|
24
|
+
vCentered?: boolean;
|
25
|
+
}
|
26
|
+
export const Vertical = React.forwardRef<HTMLDivElement, React.PropsWithChildren<IProps>>((props, ref) => {
|
27
|
+
const cssNames = useStyles();
|
28
|
+
let css: string[] = [KnownClassNames.vertical];
|
29
|
+
|
30
|
+
css.push(cssNames.vertical);
|
31
|
+
if (props.wrap)
|
32
|
+
css.push(cssNames.wrap);
|
33
|
+
if (props.nogap)
|
34
|
+
css.push(cssNames.nogap);
|
35
|
+
if (props.vCentered)
|
36
|
+
css.push(cssNames.vCentered);
|
37
|
+
|
38
|
+
if (isNotEmptyArray(props.css)) css.push(...props.css);
|
39
|
+
|
40
|
+
return (
|
41
|
+
<Section {...props} css={css} />
|
42
|
+
);
|
43
43
|
});
|
@@ -1,89 +1,89 @@
|
|
1
|
-
import { IDictionary, isNotEmptyArray, isNullOrEmptyString, jsonClone, objectsEqual } from "@kwiz/common";
|
2
|
-
import { useCallback, useEffect } from "react";
|
3
|
-
import { IPrompterProps, Prompter } from "../controls/prompt";
|
4
|
-
import { useEffectOnlyOnMount, useStateEX } from "./hooks";
|
5
|
-
|
6
|
-
export interface iBlockNav {
|
7
|
-
setMessage: (id: string, message?: string) => void;
|
8
|
-
onNav: (nav: () => void) => void;
|
9
|
-
navPrompt?: JSX.Element;
|
10
|
-
|
11
|
-
}
|
12
|
-
/** set block message if you want to block nav.
|
13
|
-
* - call setMessage to add a blocker message
|
14
|
-
* - call onNav when you have internal navigation (open / close popups)
|
15
|
-
* - render the navPrompt control to your page
|
16
|
-
* FYI for page unload, most modern browsers won't show your message but a generic one instead. */
|
17
|
-
export function useBlockNav(): iBlockNav {
|
18
|
-
const [, setBlockNavMessages, blockNavMessagesRef] = useStateEX<IDictionary<string>>({});
|
19
|
-
const [_prompt, setPrompt] = useStateEX<IPrompterProps>(null);
|
20
|
-
|
21
|
-
const getMessagesArr = useCallback(() => {
|
22
|
-
return Object.keys(blockNavMessagesRef.current).map(id => blockNavMessagesRef.current[id]);
|
23
|
-
}, useEffectOnlyOnMount);
|
24
|
-
|
25
|
-
const getMessages = useCallback(() => {
|
26
|
-
return getMessagesArr().join();
|
27
|
-
}, useEffectOnlyOnMount);
|
28
|
-
|
29
|
-
const onNav = useCallback((nav: () => void) => {
|
30
|
-
let messages = getMessagesArr();
|
31
|
-
if (isNotEmptyArray(messages)) {
|
32
|
-
//need to release react to re-render the prompt
|
33
|
-
window.setTimeout(() => {
|
34
|
-
//prompt, if ok - clear messages and nav.
|
35
|
-
setPrompt({
|
36
|
-
okButtonText: "Leave",
|
37
|
-
cancelButtonText: "Cancel",
|
38
|
-
title: "Leave page?",
|
39
|
-
children: messages.length > 1
|
40
|
-
? <ul>
|
41
|
-
{messages.map((m, i) => <li key={`m${i}`}>{m}</li>)}
|
42
|
-
</ul>
|
43
|
-
: <p>{messages[0]}</p>,
|
44
|
-
onCancel: () => setPrompt(null),
|
45
|
-
onOK: () => {
|
46
|
-
setPrompt(null);
|
47
|
-
setBlockNavMessages({});//clear messages
|
48
|
-
nav();
|
49
|
-
}
|
50
|
-
});
|
51
|
-
}, 1);
|
52
|
-
}
|
53
|
-
else nav();
|
54
|
-
}, useEffectOnlyOnMount);
|
55
|
-
|
56
|
-
|
57
|
-
useEffect(() => {
|
58
|
-
function handleBeforeUnload(e: BeforeUnloadEvent) {
|
59
|
-
//todo: use blockMessageRef.current so that we don't have to re-register every time message changes.
|
60
|
-
//otherwise we would have to add blockMessage as a dependency for this useEffect
|
61
|
-
const message = getMessages();
|
62
|
-
if (!isNullOrEmptyString(message)) {
|
63
|
-
e.preventDefault();
|
64
|
-
e.returnValue = message;
|
65
|
-
}
|
66
|
-
}
|
67
|
-
// Add event listener
|
68
|
-
window.addEventListener("beforeunload", handleBeforeUnload);
|
69
|
-
// Remove event listener on cleanup
|
70
|
-
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
71
|
-
}, useEffectOnlyOnMount);
|
72
|
-
return {
|
73
|
-
setMessage: (id: string, message?: string) => {
|
74
|
-
let current = jsonClone(blockNavMessagesRef.current);
|
75
|
-
if (isNullOrEmptyString(message))
|
76
|
-
delete current[id];
|
77
|
-
else current[id] = message;
|
78
|
-
if (!objectsEqual(current, blockNavMessagesRef.current))
|
79
|
-
setBlockNavMessages(current);
|
80
|
-
},
|
81
|
-
// clearMessages: () => {
|
82
|
-
// setBlockNavMessages({});
|
83
|
-
// },
|
84
|
-
// getMessages,
|
85
|
-
// getMessagesArr,
|
86
|
-
onNav,
|
87
|
-
navPrompt: _prompt ? <Prompter {..._prompt} /> : undefined
|
88
|
-
};
|
1
|
+
import { IDictionary, isNotEmptyArray, isNullOrEmptyString, jsonClone, objectsEqual } from "@kwiz/common";
|
2
|
+
import { useCallback, useEffect } from "react";
|
3
|
+
import { IPrompterProps, Prompter } from "../controls/prompt";
|
4
|
+
import { useEffectOnlyOnMount, useStateEX } from "./hooks";
|
5
|
+
|
6
|
+
export interface iBlockNav {
|
7
|
+
setMessage: (id: string, message?: string) => void;
|
8
|
+
onNav: (nav: () => void) => void;
|
9
|
+
navPrompt?: JSX.Element;
|
10
|
+
|
11
|
+
}
|
12
|
+
/** set block message if you want to block nav.
|
13
|
+
* - call setMessage to add a blocker message
|
14
|
+
* - call onNav when you have internal navigation (open / close popups)
|
15
|
+
* - render the navPrompt control to your page
|
16
|
+
* FYI for page unload, most modern browsers won't show your message but a generic one instead. */
|
17
|
+
export function useBlockNav(): iBlockNav {
|
18
|
+
const [, setBlockNavMessages, blockNavMessagesRef] = useStateEX<IDictionary<string>>({});
|
19
|
+
const [_prompt, setPrompt] = useStateEX<IPrompterProps>(null);
|
20
|
+
|
21
|
+
const getMessagesArr = useCallback(() => {
|
22
|
+
return Object.keys(blockNavMessagesRef.current).map(id => blockNavMessagesRef.current[id]);
|
23
|
+
}, useEffectOnlyOnMount);
|
24
|
+
|
25
|
+
const getMessages = useCallback(() => {
|
26
|
+
return getMessagesArr().join();
|
27
|
+
}, useEffectOnlyOnMount);
|
28
|
+
|
29
|
+
const onNav = useCallback((nav: () => void) => {
|
30
|
+
let messages = getMessagesArr();
|
31
|
+
if (isNotEmptyArray(messages)) {
|
32
|
+
//need to release react to re-render the prompt
|
33
|
+
window.setTimeout(() => {
|
34
|
+
//prompt, if ok - clear messages and nav.
|
35
|
+
setPrompt({
|
36
|
+
okButtonText: "Leave",
|
37
|
+
cancelButtonText: "Cancel",
|
38
|
+
title: "Leave page?",
|
39
|
+
children: messages.length > 1
|
40
|
+
? <ul>
|
41
|
+
{messages.map((m, i) => <li key={`m${i}`}>{m}</li>)}
|
42
|
+
</ul>
|
43
|
+
: <p>{messages[0]}</p>,
|
44
|
+
onCancel: () => setPrompt(null),
|
45
|
+
onOK: () => {
|
46
|
+
setPrompt(null);
|
47
|
+
setBlockNavMessages({});//clear messages
|
48
|
+
nav();
|
49
|
+
}
|
50
|
+
});
|
51
|
+
}, 1);
|
52
|
+
}
|
53
|
+
else nav();
|
54
|
+
}, useEffectOnlyOnMount);
|
55
|
+
|
56
|
+
|
57
|
+
useEffect(() => {
|
58
|
+
function handleBeforeUnload(e: BeforeUnloadEvent) {
|
59
|
+
//todo: use blockMessageRef.current so that we don't have to re-register every time message changes.
|
60
|
+
//otherwise we would have to add blockMessage as a dependency for this useEffect
|
61
|
+
const message = getMessages();
|
62
|
+
if (!isNullOrEmptyString(message)) {
|
63
|
+
e.preventDefault();
|
64
|
+
e.returnValue = message;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
// Add event listener
|
68
|
+
window.addEventListener("beforeunload", handleBeforeUnload);
|
69
|
+
// Remove event listener on cleanup
|
70
|
+
return () => window.removeEventListener("beforeunload", handleBeforeUnload);
|
71
|
+
}, useEffectOnlyOnMount);
|
72
|
+
return {
|
73
|
+
setMessage: (id: string, message?: string) => {
|
74
|
+
let current = jsonClone(blockNavMessagesRef.current);
|
75
|
+
if (isNullOrEmptyString(message))
|
76
|
+
delete current[id];
|
77
|
+
else current[id] = message;
|
78
|
+
if (!objectsEqual(current, blockNavMessagesRef.current))
|
79
|
+
setBlockNavMessages(current);
|
80
|
+
},
|
81
|
+
// clearMessages: () => {
|
82
|
+
// setBlockNavMessages({});
|
83
|
+
// },
|
84
|
+
// getMessages,
|
85
|
+
// getMessagesArr,
|
86
|
+
onNav,
|
87
|
+
navPrompt: _prompt ? <Prompter {..._prompt} /> : undefined
|
88
|
+
};
|
89
89
|
}
|
@@ -1,30 +1,30 @@
|
|
1
|
-
import React from "react";
|
2
|
-
export interface iKWIZFluentContext {
|
3
|
-
/**
|
4
|
-
* Where the portal children are mounted on DOM
|
5
|
-
*
|
6
|
-
* @default a new element on document.body without any styling
|
7
|
-
*/
|
8
|
-
mountNode?: HTMLElement | null | {
|
9
|
-
element?: HTMLElement | null;
|
10
|
-
className?: string;
|
11
|
-
}
|
12
|
-
/**
|
13
|
-
* Controls the colors and borders of the input.
|
14
|
-
*
|
15
|
-
* @default 'underline'
|
16
|
-
*/
|
17
|
-
inputAppearance?: 'outline' | 'underline' | 'filled-darker' | 'filled-lighter';
|
18
|
-
|
19
|
-
/**
|
20
|
-
* A button can be rounded, circular, or square.
|
21
|
-
*
|
22
|
-
* @default 'rounded'
|
23
|
-
*/
|
24
|
-
buttonShape?: 'rounded' | 'circular' | 'square';
|
25
|
-
/** true if using dark theme */
|
26
|
-
dark?: boolean;
|
27
|
-
}
|
28
|
-
|
29
|
-
//create context
|
1
|
+
import React from "react";
|
2
|
+
export interface iKWIZFluentContext {
|
3
|
+
/**
|
4
|
+
* Where the portal children are mounted on DOM
|
5
|
+
*
|
6
|
+
* @default a new element on document.body without any styling
|
7
|
+
*/
|
8
|
+
mountNode?: HTMLElement | null | {
|
9
|
+
element?: HTMLElement | null;
|
10
|
+
className?: string;
|
11
|
+
}
|
12
|
+
/**
|
13
|
+
* Controls the colors and borders of the input.
|
14
|
+
*
|
15
|
+
* @default 'underline'
|
16
|
+
*/
|
17
|
+
inputAppearance?: 'outline' | 'underline' | 'filled-darker' | 'filled-lighter';
|
18
|
+
|
19
|
+
/**
|
20
|
+
* A button can be rounded, circular, or square.
|
21
|
+
*
|
22
|
+
* @default 'rounded'
|
23
|
+
*/
|
24
|
+
buttonShape?: 'rounded' | 'circular' | 'square';
|
25
|
+
/** true if using dark theme */
|
26
|
+
dark?: boolean;
|
27
|
+
}
|
28
|
+
|
29
|
+
//create context
|
30
30
|
export const KWIZFluentContext = React.createContext<iKWIZFluentContext>(null);
|