@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/helpers/hooks.tsx
CHANGED
@@ -1,142 +1,142 @@
|
|
1
|
-
import { isFunction, isNotEmptyArray, isNullOrEmptyString, isPrimitiveValue, jsonClone, jsonStringify, LoggerLevel, objectsEqual, wrapFunction } from "@kwiz/common";
|
2
|
-
import { MutableRefObject, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
|
3
|
-
import { GetLogger } from "../_modules/config";
|
4
|
-
|
5
|
-
/** Empty array ensures that effect is only run on mount */
|
6
|
-
export const useEffectOnlyOnMount = [];
|
7
|
-
|
8
|
-
type stateExOptions<ValueType> = {
|
9
|
-
onChange?: (newValue: SetStateAction<ValueType>, isValueChanged: boolean) => SetStateAction<ValueType>;
|
10
|
-
//will not set state if value did not change
|
11
|
-
skipUpdateIfSame?: boolean;
|
12
|
-
//optional, provide a name for better logging
|
13
|
-
name?: string;
|
14
|
-
};
|
15
|
-
function extractStringValue(e: any) {
|
16
|
-
try {
|
17
|
-
if (e instanceof HTMLElement)
|
18
|
-
return e.outerHTML;
|
19
|
-
} catch (e) { }
|
20
|
-
try {
|
21
|
-
let json = jsonStringify(e);
|
22
|
-
if (json === "{}") return Object.keys(e).join();//maybe just object with functions, no members or values
|
23
|
-
else return json;
|
24
|
-
} catch (e) { }
|
25
|
-
try {
|
26
|
-
return e.toString();
|
27
|
-
} catch (e) { }
|
28
|
-
return '';
|
29
|
-
}
|
30
|
-
/** set state on steroids. provide promise callback after render, onChange transformer and automatic skip-set when value not changed */
|
31
|
-
export function useStateEX<ValueType>(initialValue: ValueType, options?: stateExOptions<ValueType>):
|
32
|
-
[ValueType, (newValue: SetStateAction<ValueType>) => Promise<ValueType>, MutableRefObject<ValueType>] {
|
33
|
-
options = options || {};
|
34
|
-
const name = options.name || '';
|
35
|
-
|
36
|
-
let logger = GetLogger(`useStateWithTrack${isNullOrEmptyString(name) ? '' : ` ${name}`}`);
|
37
|
-
logger.setLevel(LoggerLevel.WARN);
|
38
|
-
|
39
|
-
const [value, setValueInState] = useState(initialValue);
|
40
|
-
const currentValue = useRef(value);
|
41
|
-
|
42
|
-
/** make this a collection in case several callers are awaiting the same propr update */
|
43
|
-
const resolveState = useRef<((v: ValueType) => void)[]>([]);
|
44
|
-
const isMounted = useRef(false);
|
45
|
-
|
46
|
-
useEffect(() => {
|
47
|
-
isMounted.current = true;
|
48
|
-
|
49
|
-
return () => {
|
50
|
-
isMounted.current = false;
|
51
|
-
};
|
52
|
-
}, useEffectOnlyOnMount);
|
53
|
-
|
54
|
-
function resolvePromises() {
|
55
|
-
if (isNotEmptyArray(resolveState.current)) {
|
56
|
-
let resolvers = resolveState.current.slice();
|
57
|
-
resolveState.current = [];//clear
|
58
|
-
resolvers.map(r => r(currentValue.current));
|
59
|
-
}
|
60
|
-
};
|
61
|
-
useEffect(() => {
|
62
|
-
resolvePromises();
|
63
|
-
if (isNotEmptyArray(resolveState.current)) {
|
64
|
-
logger.log(`resolved after render`);
|
65
|
-
let resolvers = resolveState.current.slice();
|
66
|
-
resolveState.current = [];//clear
|
67
|
-
resolvers.map(r => r(value));
|
68
|
-
}
|
69
|
-
}, [value, resolveState.current]);
|
70
|
-
|
71
|
-
function getIsValueChanged(newValue: ValueType): boolean {
|
72
|
-
let result: boolean;
|
73
|
-
if (!objectsEqual(newValue as object, currentValue.current as object)) {
|
74
|
-
result = true;
|
75
|
-
}
|
76
|
-
else {
|
77
|
-
result = false;
|
78
|
-
}
|
79
|
-
|
80
|
-
return logger.groupSync(result ? 'value changed' : 'value not changed', log => {
|
81
|
-
if (logger.getLevel() === LoggerLevel.VERBOSE) {
|
82
|
-
log('old: ' + extractStringValue(currentValue.current));
|
83
|
-
log('new: ' + extractStringValue(newValue));
|
84
|
-
}
|
85
|
-
return result;
|
86
|
-
});
|
87
|
-
};
|
88
|
-
|
89
|
-
let setValueWithCheck = !options.skipUpdateIfSame ? setValueInState : (newValue: ValueType) => {
|
90
|
-
const isValueChanged = getIsValueChanged(newValue);
|
91
|
-
if (isValueChanged) {
|
92
|
-
setValueInState(newValue);
|
93
|
-
}
|
94
|
-
else {
|
95
|
-
resolvePromises();
|
96
|
-
}
|
97
|
-
}
|
98
|
-
|
99
|
-
|
100
|
-
let setValueWithEvents = wrapFunction(setValueWithCheck, {
|
101
|
-
before: (newValue: ValueType) => isFunction(options.onChange) ? options.onChange(newValue, getIsValueChanged(newValue)) : newValue,
|
102
|
-
after: (newValue: ValueType) => currentValue.current = isPrimitiveValue(newValue) || isFunction(newValue)
|
103
|
-
? newValue
|
104
|
-
//fix skipUpdateIfSame for complex objects
|
105
|
-
//if we don't clone it, currentValue.current will be a ref to the value in the owner
|
106
|
-
//and will be treated as unchanged object, and it will be out of sync
|
107
|
-
//this leads to skipUpdateIfSame failing after just 1 unchanged update
|
108
|
-
: jsonClone(newValue) as ValueType
|
109
|
-
});
|
110
|
-
|
111
|
-
const setValue = useCallback((newState: ValueType) => new Promise<ValueType>(resolve => {
|
112
|
-
if (!isMounted.current) {
|
113
|
-
//unmounted may never resolve
|
114
|
-
logger.log(`resolved without wait`);
|
115
|
-
resolve(newState);
|
116
|
-
}
|
117
|
-
else {
|
118
|
-
resolveState.current.push(resolve);
|
119
|
-
setValueWithEvents(newState);
|
120
|
-
}
|
121
|
-
}), []);
|
122
|
-
|
123
|
-
return [value, setValue, currentValue];
|
124
|
-
}
|
125
|
-
|
126
|
-
/** use a ref, that can be tracked as useEffect dependency */
|
127
|
-
export function useRefWithState<T>(initialValue?: T, stateOptions: stateExOptions<T> = { skipUpdateIfSame: true, name: "useRefWithState" }) {
|
128
|
-
let asRef = useRef<T>(initialValue);
|
129
|
-
let [asState, setState] = useStateEX<T>(initialValue, stateOptions);
|
130
|
-
let setRef = useCallback((newValue: T) => {
|
131
|
-
asRef.current = newValue;
|
132
|
-
setState(newValue);
|
133
|
-
}, useEffectOnlyOnMount);
|
134
|
-
return {
|
135
|
-
/** ref object for getting latest value in handlers */
|
136
|
-
ref: asRef,
|
137
|
-
/** for useEffect dependency */
|
138
|
-
value: asState,
|
139
|
-
/** for setting on element: ref={e.set} */
|
140
|
-
set: setRef
|
141
|
-
};
|
1
|
+
import { isFunction, isNotEmptyArray, isNullOrEmptyString, isPrimitiveValue, jsonClone, jsonStringify, LoggerLevel, objectsEqual, wrapFunction } from "@kwiz/common";
|
2
|
+
import { MutableRefObject, SetStateAction, useCallback, useEffect, useRef, useState } from "react";
|
3
|
+
import { GetLogger } from "../_modules/config";
|
4
|
+
|
5
|
+
/** Empty array ensures that effect is only run on mount */
|
6
|
+
export const useEffectOnlyOnMount = [];
|
7
|
+
|
8
|
+
type stateExOptions<ValueType> = {
|
9
|
+
onChange?: (newValue: SetStateAction<ValueType>, isValueChanged: boolean) => SetStateAction<ValueType>;
|
10
|
+
//will not set state if value did not change
|
11
|
+
skipUpdateIfSame?: boolean;
|
12
|
+
//optional, provide a name for better logging
|
13
|
+
name?: string;
|
14
|
+
};
|
15
|
+
function extractStringValue(e: any) {
|
16
|
+
try {
|
17
|
+
if (e instanceof HTMLElement)
|
18
|
+
return e.outerHTML;
|
19
|
+
} catch (e) { }
|
20
|
+
try {
|
21
|
+
let json = jsonStringify(e);
|
22
|
+
if (json === "{}") return Object.keys(e).join();//maybe just object with functions, no members or values
|
23
|
+
else return json;
|
24
|
+
} catch (e) { }
|
25
|
+
try {
|
26
|
+
return e.toString();
|
27
|
+
} catch (e) { }
|
28
|
+
return '';
|
29
|
+
}
|
30
|
+
/** set state on steroids. provide promise callback after render, onChange transformer and automatic skip-set when value not changed */
|
31
|
+
export function useStateEX<ValueType>(initialValue: ValueType, options?: stateExOptions<ValueType>):
|
32
|
+
[ValueType, (newValue: SetStateAction<ValueType>) => Promise<ValueType>, MutableRefObject<ValueType>] {
|
33
|
+
options = options || {};
|
34
|
+
const name = options.name || '';
|
35
|
+
|
36
|
+
let logger = GetLogger(`useStateWithTrack${isNullOrEmptyString(name) ? '' : ` ${name}`}`);
|
37
|
+
logger.setLevel(LoggerLevel.WARN);
|
38
|
+
|
39
|
+
const [value, setValueInState] = useState(initialValue);
|
40
|
+
const currentValue = useRef(value);
|
41
|
+
|
42
|
+
/** make this a collection in case several callers are awaiting the same propr update */
|
43
|
+
const resolveState = useRef<((v: ValueType) => void)[]>([]);
|
44
|
+
const isMounted = useRef(false);
|
45
|
+
|
46
|
+
useEffect(() => {
|
47
|
+
isMounted.current = true;
|
48
|
+
|
49
|
+
return () => {
|
50
|
+
isMounted.current = false;
|
51
|
+
};
|
52
|
+
}, useEffectOnlyOnMount);
|
53
|
+
|
54
|
+
function resolvePromises() {
|
55
|
+
if (isNotEmptyArray(resolveState.current)) {
|
56
|
+
let resolvers = resolveState.current.slice();
|
57
|
+
resolveState.current = [];//clear
|
58
|
+
resolvers.map(r => r(currentValue.current));
|
59
|
+
}
|
60
|
+
};
|
61
|
+
useEffect(() => {
|
62
|
+
resolvePromises();
|
63
|
+
if (isNotEmptyArray(resolveState.current)) {
|
64
|
+
logger.log(`resolved after render`);
|
65
|
+
let resolvers = resolveState.current.slice();
|
66
|
+
resolveState.current = [];//clear
|
67
|
+
resolvers.map(r => r(value));
|
68
|
+
}
|
69
|
+
}, [value, resolveState.current]);
|
70
|
+
|
71
|
+
function getIsValueChanged(newValue: ValueType): boolean {
|
72
|
+
let result: boolean;
|
73
|
+
if (!objectsEqual(newValue as object, currentValue.current as object)) {
|
74
|
+
result = true;
|
75
|
+
}
|
76
|
+
else {
|
77
|
+
result = false;
|
78
|
+
}
|
79
|
+
|
80
|
+
return logger.groupSync(result ? 'value changed' : 'value not changed', log => {
|
81
|
+
if (logger.getLevel() === LoggerLevel.VERBOSE) {
|
82
|
+
log('old: ' + extractStringValue(currentValue.current));
|
83
|
+
log('new: ' + extractStringValue(newValue));
|
84
|
+
}
|
85
|
+
return result;
|
86
|
+
});
|
87
|
+
};
|
88
|
+
|
89
|
+
let setValueWithCheck = !options.skipUpdateIfSame ? setValueInState : (newValue: ValueType) => {
|
90
|
+
const isValueChanged = getIsValueChanged(newValue);
|
91
|
+
if (isValueChanged) {
|
92
|
+
setValueInState(newValue);
|
93
|
+
}
|
94
|
+
else {
|
95
|
+
resolvePromises();
|
96
|
+
}
|
97
|
+
}
|
98
|
+
|
99
|
+
|
100
|
+
let setValueWithEvents = wrapFunction(setValueWithCheck, {
|
101
|
+
before: (newValue: ValueType) => isFunction(options.onChange) ? options.onChange(newValue, getIsValueChanged(newValue)) : newValue,
|
102
|
+
after: (newValue: ValueType) => currentValue.current = isPrimitiveValue(newValue) || isFunction(newValue)
|
103
|
+
? newValue
|
104
|
+
//fix skipUpdateIfSame for complex objects
|
105
|
+
//if we don't clone it, currentValue.current will be a ref to the value in the owner
|
106
|
+
//and will be treated as unchanged object, and it will be out of sync
|
107
|
+
//this leads to skipUpdateIfSame failing after just 1 unchanged update
|
108
|
+
: jsonClone(newValue) as ValueType
|
109
|
+
});
|
110
|
+
|
111
|
+
const setValue = useCallback((newState: ValueType) => new Promise<ValueType>(resolve => {
|
112
|
+
if (!isMounted.current) {
|
113
|
+
//unmounted may never resolve
|
114
|
+
logger.log(`resolved without wait`);
|
115
|
+
resolve(newState);
|
116
|
+
}
|
117
|
+
else {
|
118
|
+
resolveState.current.push(resolve);
|
119
|
+
setValueWithEvents(newState);
|
120
|
+
}
|
121
|
+
}), []);
|
122
|
+
|
123
|
+
return [value, setValue, currentValue];
|
124
|
+
}
|
125
|
+
|
126
|
+
/** use a ref, that can be tracked as useEffect dependency */
|
127
|
+
export function useRefWithState<T>(initialValue?: T, stateOptions: stateExOptions<T> = { skipUpdateIfSame: true, name: "useRefWithState" }) {
|
128
|
+
let asRef = useRef<T>(initialValue);
|
129
|
+
let [asState, setState] = useStateEX<T>(initialValue, stateOptions);
|
130
|
+
let setRef = useCallback((newValue: T) => {
|
131
|
+
asRef.current = newValue;
|
132
|
+
setState(newValue);
|
133
|
+
}, useEffectOnlyOnMount);
|
134
|
+
return {
|
135
|
+
/** ref object for getting latest value in handlers */
|
136
|
+
ref: asRef,
|
137
|
+
/** for useEffect dependency */
|
138
|
+
value: asState,
|
139
|
+
/** for setting on element: ref={e.set} */
|
140
|
+
set: setRef
|
141
|
+
};
|
142
142
|
}
|
package/src/helpers/index.ts
CHANGED
@@ -1,8 +1,8 @@
|
|
1
|
-
export * from './block-nav';
|
2
|
-
export * from './context-export';
|
3
|
-
export * from './drag-drop';
|
4
|
-
export * from './hooks';
|
5
|
-
export * from './hooks-events';
|
6
|
-
export * from './use-alerts';
|
7
|
-
export * from './use-editable-control';
|
8
|
-
export * from './use-toast';
|
1
|
+
export * from './block-nav';
|
2
|
+
export * from './context-export';
|
3
|
+
export * from './drag-drop';
|
4
|
+
export * from './hooks';
|
5
|
+
export * from './hooks-events';
|
6
|
+
export * from './use-alerts';
|
7
|
+
export * from './use-editable-control';
|
8
|
+
export * from './use-toast';
|
@@ -1,75 +1,75 @@
|
|
1
|
-
import { Label } from "@fluentui/react-components";
|
2
|
-
import { isFunction, isString } from "@kwiz/common";
|
3
|
-
import { useCallback } from "react";
|
4
|
-
import { IPrompterProps, Prompter } from "../controls/prompt";
|
5
|
-
import { useEffectOnlyOnMount, useStateEX } from "./hooks";
|
6
|
-
|
7
|
-
export interface iAlerts {
|
8
|
-
promptEX: (info: IPrompterProps) => void;
|
9
|
-
confirmEX: (message: string | JSX.Element, onOK?: () => void, onCancel?: () => void) => Promise<boolean>;
|
10
|
-
alertEX: (message: string | JSX.Element, onOK?: () => void) => Promise<void>;
|
11
|
-
alertPrompt?: JSX.Element;
|
12
|
-
close: () => void;
|
13
|
-
}
|
14
|
-
|
15
|
-
/** set block message if you want to block nav.
|
16
|
-
* - call setMessage to add a blocker message
|
17
|
-
* - call onNav when you have internal navigation (open / close popups)
|
18
|
-
* - render the navPrompt control to your page
|
19
|
-
* FYI for page unload, most modern browsers won't show your message but a generic one instead. */
|
20
|
-
export function useAlerts(): iAlerts {
|
21
|
-
const [_prompt, _setPrompt] = useStateEX<IPrompterProps>(null);
|
22
|
-
|
23
|
-
const promptEX = useCallback((info: IPrompterProps) => {
|
24
|
-
//need to release react to re-render the prompt
|
25
|
-
window.setTimeout(() => {
|
26
|
-
//prompt, if ok - clear messages and nav.
|
27
|
-
_setPrompt({
|
28
|
-
...info,
|
29
|
-
onCancel: () => {
|
30
|
-
_setPrompt(null);
|
31
|
-
if (isFunction(info.onCancel)) info.onCancel();
|
32
|
-
},
|
33
|
-
onOK: () => {
|
34
|
-
_setPrompt(null);
|
35
|
-
if (isFunction(info.onOK)) info.onOK();
|
36
|
-
}
|
37
|
-
});
|
38
|
-
}, 1);
|
39
|
-
}, useEffectOnlyOnMount);
|
40
|
-
|
41
|
-
const confirmEX = useCallback((message: string | JSX.Element, onOK?: () => void, onCancel?: () => void) => {
|
42
|
-
return new Promise<boolean>(resolve => {
|
43
|
-
promptEX({
|
44
|
-
children: isString(message) ? <Label>{message}</Label> : message,
|
45
|
-
onCancel: () => {
|
46
|
-
if (isFunction(onCancel)) onCancel();
|
47
|
-
resolve(false);
|
48
|
-
},
|
49
|
-
onOK: () => {
|
50
|
-
if (isFunction(onOK)) onOK();
|
51
|
-
resolve(true);
|
52
|
-
}
|
53
|
-
});
|
54
|
-
});
|
55
|
-
}, useEffectOnlyOnMount);
|
56
|
-
|
57
|
-
const alertEX = useCallback((message: string | JSX.Element, onOK: () => void) => {
|
58
|
-
return new Promise<void>(resolve => {
|
59
|
-
promptEX({
|
60
|
-
children: isString(message) ? <Label>{message}</Label> : message,
|
61
|
-
hideCancel: true,
|
62
|
-
onOK: () => {
|
63
|
-
if (isFunction(onOK)) onOK();
|
64
|
-
resolve();
|
65
|
-
}
|
66
|
-
});
|
67
|
-
});
|
68
|
-
}, useEffectOnlyOnMount);
|
69
|
-
|
70
|
-
return {
|
71
|
-
promptEX, confirmEX, alertEX,
|
72
|
-
alertPrompt: _prompt ? <Prompter {..._prompt} /> : undefined,
|
73
|
-
close: () => _setPrompt(null)
|
74
|
-
};
|
1
|
+
import { Label } from "@fluentui/react-components";
|
2
|
+
import { isFunction, isString } from "@kwiz/common";
|
3
|
+
import { useCallback } from "react";
|
4
|
+
import { IPrompterProps, Prompter } from "../controls/prompt";
|
5
|
+
import { useEffectOnlyOnMount, useStateEX } from "./hooks";
|
6
|
+
|
7
|
+
export interface iAlerts {
|
8
|
+
promptEX: (info: IPrompterProps) => void;
|
9
|
+
confirmEX: (message: string | JSX.Element, onOK?: () => void, onCancel?: () => void) => Promise<boolean>;
|
10
|
+
alertEX: (message: string | JSX.Element, onOK?: () => void) => Promise<void>;
|
11
|
+
alertPrompt?: JSX.Element;
|
12
|
+
close: () => void;
|
13
|
+
}
|
14
|
+
|
15
|
+
/** set block message if you want to block nav.
|
16
|
+
* - call setMessage to add a blocker message
|
17
|
+
* - call onNav when you have internal navigation (open / close popups)
|
18
|
+
* - render the navPrompt control to your page
|
19
|
+
* FYI for page unload, most modern browsers won't show your message but a generic one instead. */
|
20
|
+
export function useAlerts(): iAlerts {
|
21
|
+
const [_prompt, _setPrompt] = useStateEX<IPrompterProps>(null);
|
22
|
+
|
23
|
+
const promptEX = useCallback((info: IPrompterProps) => {
|
24
|
+
//need to release react to re-render the prompt
|
25
|
+
window.setTimeout(() => {
|
26
|
+
//prompt, if ok - clear messages and nav.
|
27
|
+
_setPrompt({
|
28
|
+
...info,
|
29
|
+
onCancel: () => {
|
30
|
+
_setPrompt(null);
|
31
|
+
if (isFunction(info.onCancel)) info.onCancel();
|
32
|
+
},
|
33
|
+
onOK: () => {
|
34
|
+
_setPrompt(null);
|
35
|
+
if (isFunction(info.onOK)) info.onOK();
|
36
|
+
}
|
37
|
+
});
|
38
|
+
}, 1);
|
39
|
+
}, useEffectOnlyOnMount);
|
40
|
+
|
41
|
+
const confirmEX = useCallback((message: string | JSX.Element, onOK?: () => void, onCancel?: () => void) => {
|
42
|
+
return new Promise<boolean>(resolve => {
|
43
|
+
promptEX({
|
44
|
+
children: isString(message) ? <Label>{message}</Label> : message,
|
45
|
+
onCancel: () => {
|
46
|
+
if (isFunction(onCancel)) onCancel();
|
47
|
+
resolve(false);
|
48
|
+
},
|
49
|
+
onOK: () => {
|
50
|
+
if (isFunction(onOK)) onOK();
|
51
|
+
resolve(true);
|
52
|
+
}
|
53
|
+
});
|
54
|
+
});
|
55
|
+
}, useEffectOnlyOnMount);
|
56
|
+
|
57
|
+
const alertEX = useCallback((message: string | JSX.Element, onOK: () => void) => {
|
58
|
+
return new Promise<void>(resolve => {
|
59
|
+
promptEX({
|
60
|
+
children: isString(message) ? <Label>{message}</Label> : message,
|
61
|
+
hideCancel: true,
|
62
|
+
onOK: () => {
|
63
|
+
if (isFunction(onOK)) onOK();
|
64
|
+
resolve();
|
65
|
+
}
|
66
|
+
});
|
67
|
+
});
|
68
|
+
}, useEffectOnlyOnMount);
|
69
|
+
|
70
|
+
return {
|
71
|
+
promptEX, confirmEX, alertEX,
|
72
|
+
alertPrompt: _prompt ? <Prompter {..._prompt} /> : undefined,
|
73
|
+
close: () => _setPrompt(null)
|
74
|
+
};
|
75
75
|
}
|
@@ -1,38 +1,38 @@
|
|
1
|
-
import { Toast, ToastTitle, Toaster, useId, useToastController } from "@fluentui/react-components";
|
2
|
-
import { useCallback, useState } from "react";
|
3
|
-
import { PleaseWait } from "../controls/please-wait";
|
4
|
-
import { useEffectOnlyOnMount, useStateEX } from "./hooks";
|
5
|
-
|
6
|
-
/* Provides useful helpers for tracking if control has changes, and handling the save changes with progress bar and on success/fail messages. */
|
7
|
-
export function useEditableControl() {
|
8
|
-
const [showProgress, setShowProgress] = useState(false);
|
9
|
-
const [hasChanges, setHasChanges, hasChangesRef] = useStateEX(false, { skipUpdateIfSame: true });
|
10
|
-
|
11
|
-
const toasterId = useId("toaster");
|
12
|
-
const { dispatchToast } = useToastController(toasterId);
|
13
|
-
|
14
|
-
const onSaveChanges = useCallback(async (worker: () => Promise<{ success: boolean; message
|
15
|
-
setShowProgress(true);
|
16
|
-
const success = await worker();
|
17
|
-
setShowProgress(false);
|
18
|
-
|
19
|
-
if (success.success !== true) {
|
20
|
-
dispatchToast(<Toast>
|
21
|
-
<ToastTitle>{success.message || "Could not save your changes."}</ToastTitle>
|
22
|
-
</Toast>, { intent: "warning", timeout: 10000 });
|
23
|
-
}
|
24
|
-
else {
|
25
|
-
setHasChanges(false);
|
26
|
-
dispatchToast(<Toast>
|
27
|
-
<ToastTitle>{success.message || "Changes saved!"}</ToastTitle>
|
28
|
-
</Toast>, { intent: "success", timeout: 2000 });
|
29
|
-
}
|
30
|
-
}, useEffectOnlyOnMount);
|
31
|
-
|
32
|
-
return {
|
33
|
-
hasChanges, hasChangesRef, setHasChanges, onSaveChanges, editablePageElement: <>
|
34
|
-
{showProgress && <PleaseWait />}
|
35
|
-
<Toaster toasterId={toasterId} />
|
36
|
-
</>
|
37
|
-
};
|
1
|
+
import { Toast, ToastTitle, Toaster, useId, useToastController } from "@fluentui/react-components";
|
2
|
+
import { useCallback, useState } from "react";
|
3
|
+
import { PleaseWait } from "../controls/please-wait";
|
4
|
+
import { useEffectOnlyOnMount, useStateEX } from "./hooks";
|
5
|
+
|
6
|
+
/* Provides useful helpers for tracking if control has changes, and handling the save changes with progress bar and on success/fail messages. */
|
7
|
+
export function useEditableControl() {
|
8
|
+
const [showProgress, setShowProgress] = useState(false);
|
9
|
+
const [hasChanges, setHasChanges, hasChangesRef] = useStateEX(false, { skipUpdateIfSame: true });
|
10
|
+
|
11
|
+
const toasterId = useId("toaster");
|
12
|
+
const { dispatchToast } = useToastController(toasterId);
|
13
|
+
|
14
|
+
const onSaveChanges = useCallback(async (worker: () => Promise<{ success: boolean; message?: string; }>) => {
|
15
|
+
setShowProgress(true);
|
16
|
+
const success = await worker();
|
17
|
+
setShowProgress(false);
|
18
|
+
|
19
|
+
if (success.success !== true) {
|
20
|
+
dispatchToast(<Toast>
|
21
|
+
<ToastTitle>{success.message || "Could not save your changes."}</ToastTitle>
|
22
|
+
</Toast>, { intent: "warning", timeout: 10000 });
|
23
|
+
}
|
24
|
+
else {
|
25
|
+
setHasChanges(false);
|
26
|
+
dispatchToast(<Toast>
|
27
|
+
<ToastTitle>{success.message || "Changes saved!"}</ToastTitle>
|
28
|
+
</Toast>, { intent: "success", timeout: 2000 });
|
29
|
+
}
|
30
|
+
}, useEffectOnlyOnMount);
|
31
|
+
|
32
|
+
return {
|
33
|
+
hasChanges, hasChangesRef, setHasChanges, onSaveChanges, editablePageElement: <>
|
34
|
+
{showProgress && <PleaseWait />}
|
35
|
+
<Toaster toasterId={toasterId} />
|
36
|
+
</>
|
37
|
+
};
|
38
38
|
}
|
@@ -1,30 +1,30 @@
|
|
1
|
-
import { Link, Toast, ToastBody, Toaster, ToastFooter, ToastIntent, ToastTitle, useId, useToastController } from "@fluentui/react-components";
|
2
|
-
import { isNotEmptyArray } from "@kwiz/common";
|
3
|
-
import { useKWIZFluentContext } from "./context-internal";
|
4
|
-
|
5
|
-
export function useToast() {
|
6
|
-
const ctx = useKWIZFluentContext();
|
7
|
-
const toasterId = useId("toaster");
|
8
|
-
const { dispatchToast } = useToastController(toasterId);
|
9
|
-
return {
|
10
|
-
control: <Toaster mountNode={ctx.mountNode} toasterId={toasterId} />,
|
11
|
-
dispatch: (info: {
|
12
|
-
title?: string;
|
13
|
-
body?: string;
|
14
|
-
subtitle?: string;
|
15
|
-
titleAction?: { text: string, onClick: () => void },
|
16
|
-
footerActions?: { text: string, onClick: () => void }[],
|
17
|
-
intent?: ToastIntent
|
18
|
-
}) => {
|
19
|
-
dispatchToast(<Toast>
|
20
|
-
{info.title && <ToastTitle action={info.titleAction ? <Link onClick={info.titleAction.onClick}>{info.titleAction.text}</Link> : undefined}>{info.title}</ToastTitle>}
|
21
|
-
{info.body && <ToastBody subtitle={info.subtitle}>{info.body}</ToastBody>}
|
22
|
-
{isNotEmptyArray(info.footerActions) &&
|
23
|
-
<ToastFooter>
|
24
|
-
{info.footerActions.map((a, i) => <Link key={`l${i}`} onClick={a.onClick}>{a.text}</Link>)}
|
25
|
-
</ToastFooter>
|
26
|
-
}
|
27
|
-
</Toast>, { intent: info.intent || "info" });
|
28
|
-
}
|
29
|
-
}
|
1
|
+
import { Link, Toast, ToastBody, Toaster, ToastFooter, ToastIntent, ToastTitle, useId, useToastController } from "@fluentui/react-components";
|
2
|
+
import { isNotEmptyArray } from "@kwiz/common";
|
3
|
+
import { useKWIZFluentContext } from "./context-internal";
|
4
|
+
|
5
|
+
export function useToast() {
|
6
|
+
const ctx = useKWIZFluentContext();
|
7
|
+
const toasterId = useId("toaster");
|
8
|
+
const { dispatchToast } = useToastController(toasterId);
|
9
|
+
return {
|
10
|
+
control: <Toaster mountNode={ctx.mountNode} toasterId={toasterId} />,
|
11
|
+
dispatch: (info: {
|
12
|
+
title?: string;
|
13
|
+
body?: string;
|
14
|
+
subtitle?: string;
|
15
|
+
titleAction?: { text: string, onClick: () => void },
|
16
|
+
footerActions?: { text: string, onClick: () => void }[],
|
17
|
+
intent?: ToastIntent
|
18
|
+
}) => {
|
19
|
+
dispatchToast(<Toast>
|
20
|
+
{info.title && <ToastTitle action={info.titleAction ? <Link onClick={info.titleAction.onClick}>{info.titleAction.text}</Link> : undefined}>{info.title}</ToastTitle>}
|
21
|
+
{info.body && <ToastBody subtitle={info.subtitle}>{info.body}</ToastBody>}
|
22
|
+
{isNotEmptyArray(info.footerActions) &&
|
23
|
+
<ToastFooter>
|
24
|
+
{info.footerActions.map((a, i) => <Link key={`l${i}`} onClick={a.onClick}>{a.text}</Link>)}
|
25
|
+
</ToastFooter>
|
26
|
+
}
|
27
|
+
</Toast>, { intent: info.intent || "info" });
|
28
|
+
}
|
29
|
+
}
|
30
30
|
}
|
package/src/index.ts
CHANGED
@@ -1,3 +1,3 @@
|
|
1
|
-
export * from './controls';
|
2
|
-
export * from './helpers';
|
1
|
+
export * from './controls';
|
2
|
+
export * from './helpers';
|
3
3
|
export * from './styles';
|
package/src/styles/index.ts
CHANGED
@@ -1 +1 @@
|
|
1
|
-
export { commonSizes, KnownClassNames } from './styles';
|
1
|
+
export { commonSizes, KnownClassNames } from './styles';
|