dce-reactkit 5.0.8 → 5.0.10
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/cjs/index.js +304 -16
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/types/helpers/useBackButton.d.ts +20 -0
- package/dist/cjs/types/index.d.ts +5 -2
- package/dist/cjs/types/types/BackButtonController.d.ts +40 -0
- package/dist/cjs/types/types/BackState.d.ts +11 -0
- package/dist/esm/index.js +299 -18
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/types/helpers/useBackButton.d.ts +20 -0
- package/dist/esm/types/index.d.ts +5 -2
- package/dist/esm/types/types/BackButtonController.d.ts +40 -0
- package/dist/esm/types/types/BackState.d.ts +11 -0
- package/dist/index.d.ts +71 -2
- package/package.json +2 -2
- package/src/components/ItemPicker/NestableItemList.tsx +44 -42
- package/src/helpers/useBackButton.tsx +352 -0
- package/src/index.ts +9 -0
- package/src/types/BackButtonController.ts +43 -0
- package/src/types/BackState.ts +15 -0
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import BackButtonController from '../types/BackButtonController';
|
|
2
|
+
/**
|
|
3
|
+
* Controller for driving back navigation from anywhere in the app. Requires
|
|
4
|
+
* useBackButton to have been called in the top-level app
|
|
5
|
+
* @author Yuen Ler Chow
|
|
6
|
+
*/
|
|
7
|
+
export declare const backButtonController: BackButtonController;
|
|
8
|
+
/**
|
|
9
|
+
* Hook that makes the browser's back button navigate within the app instead of
|
|
10
|
+
* leaving it. Call this once in your top-level app, then use
|
|
11
|
+
* backButtonController to enter subpanels and describe their state.
|
|
12
|
+
*
|
|
13
|
+
* Assumes a single level of navigation: one home screen plus subpanels that the
|
|
14
|
+
* user returns home from.
|
|
15
|
+
* @author Yuen Ler Chow
|
|
16
|
+
* @param handleGoHomeFunc handler that performs the app state changes required
|
|
17
|
+
* to return to the home screen
|
|
18
|
+
*/
|
|
19
|
+
declare const useBackButton: (handleGoHomeFunc: () => void) => void;
|
|
20
|
+
export default useBackButton;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_ROUTE_PATH, LOG_REVIEW_STATUS_ROUTE, LOG_REVIEW_GET_LOGS_ROUTE, SELECT_ADMIN_CHECK_ROUTE, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, getLocalTimeInfo, genCommaList, validateEmail, validatePhoneNumber, validateString, idify, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, getWordCount, cloneDeep, getTimestampFromTimeInfoInET, spaceAtCapitals, ParamType, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, LogTypeSpecificInfo, LogMainInfo, LogSourceSpecificInfo, LogLevel, CommonKitErrorCode, LogReviewerFilterState } from 'dce-commonkit';
|
|
1
|
+
import { ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_ROUTE_PATH, LOG_REVIEW_STATUS_ROUTE, LOG_REVIEW_GET_LOGS_ROUTE, SELECT_ADMIN_CHECK_ROUTE, abbreviate, avg, ceilToNumDecimals, convertDateKeyToDateInfo, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, getLocalTimeInfo, genCommaList, validateEmail, validatePhoneNumber, validateString, idify, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, getWordCount, cloneDeep, getTimestampFromTimeInfoInET, spaceAtCapitals, ParamType, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, LogTypeSpecificInfo, LogMainInfo, LogSourceSpecificInfo, LogLevel, CommonKitErrorCode, LogReviewerFilterState } from 'dce-commonkit';
|
|
2
2
|
import AppWrapper, { alert, prompt, confirm, showFatalError, addFatalErrorHandler, leaveToURL } from './components/AppWrapper';
|
|
3
3
|
import LoadingSpinner from './components/LoadingSpinner';
|
|
4
4
|
import ErrorBox from './components/ErrorBox';
|
|
@@ -37,6 +37,7 @@ import isMobileOrTablet from './helpers/isMobileOrTablet';
|
|
|
37
37
|
import makeLinksClickable from './helpers/makeLinksClickable';
|
|
38
38
|
import combineClassNames from './helpers/combineClassNames';
|
|
39
39
|
import useForceRender from './helpers/useForceRender';
|
|
40
|
+
import useBackButton, { backButtonController } from './helpers/useBackButton';
|
|
40
41
|
import isSelectAdmin from './helpers/isSelectAdmin';
|
|
41
42
|
import ModalButtonType from './types/ModalButtonType';
|
|
42
43
|
import ModalSize from './types/ModalSize';
|
|
@@ -45,8 +46,10 @@ import Variant from './types/Variant';
|
|
|
45
46
|
import IntelliTableColumn from './types/IntelliTableColumn';
|
|
46
47
|
import DropdownItemType from './types/DropdownItemType';
|
|
47
48
|
import ProgressBarSize from './types/ProgressBarSize';
|
|
49
|
+
import BackState from './types/BackState';
|
|
50
|
+
import BackButtonController from './types/BackButtonController';
|
|
48
51
|
import PickableItem from './components/ItemPicker/types/PickableItem';
|
|
49
52
|
import DBEntry from './components/DBEntryManagerPanel/types/DBEntry';
|
|
50
53
|
import DBEntryField from './components/DBEntryManagerPanel/types/DBEntryField';
|
|
51
54
|
import DBEntryFieldType from './components/DBEntryManagerPanel/types/DBEntryFieldType';
|
|
52
|
-
export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, SimpleMonthChooser, SimpleTimeChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, MultiSwitch, Dropdown, ProgressBar, FakeProgressBar, alert, prompt, confirm, showFatalError, DynamicWord, stubServerEndpoint, canReviewLogs, isMobileOrTablet, makeLinksClickable, isSelectAdmin, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, combineClassNames, useForceRender, setClientEventMetadataPopulator, ModalButtonType, ModalSize, ModalType, Variant, IntelliTableColumn, DropdownItemType, LogReviewerFilterState, ProgressBarSize, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_ROUTE_PATH, LOG_REVIEW_STATUS_ROUTE, LOG_REVIEW_GET_LOGS_ROUTE, SELECT_ADMIN_CHECK_ROUTE, abbreviate, avg, ceilToNumDecimals, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, getLocalTimeInfo, genCommaList, validateEmail, validatePhoneNumber, validateString, idify, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, getWordCount, cloneDeep, getTimestampFromTimeInfoInET, spaceAtCapitals, ParamType, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, LogTypeSpecificInfo, LogMainInfo, LogSourceSpecificInfo, LogLevel, CommonKitErrorCode, };
|
|
55
|
+
export { AppWrapper, LoadingSpinner, ErrorBox, Modal, TabBox, RadioButton, CheckboxButton, ButtonInputGroup, SimpleDateChooser, SimpleMonthChooser, SimpleTimeChooser, Drawer, PopSuccessMark, PopFailureMark, PopPendingMark, CopiableBox, ItemPicker, LogReviewer, IntelliTable, CSVDownloadButton, DBEntryManagerPanel, Tooltip, ToggleSwitch, AutoscrollToBottomContainer, MultiSwitch, Dropdown, ProgressBar, FakeProgressBar, alert, prompt, confirm, showFatalError, DynamicWord, stubServerEndpoint, canReviewLogs, isMobileOrTablet, makeLinksClickable, isSelectAdmin, initClient, visitServerEndpoint, logClientEvent, addFatalErrorHandler, leaveToURL, combineClassNames, useForceRender, useBackButton, backButtonController, setClientEventMetadataPopulator, ModalButtonType, ModalSize, ModalType, Variant, IntelliTableColumn, DropdownItemType, LogReviewerFilterState, ProgressBarSize, BackState, BackButtonController, PickableItem, DBEntry, DBEntryField, DBEntryFieldType, ErrorWithCode, MINUTE_IN_MS, HOUR_IN_MS, DAY_IN_MS, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_ROUTE_PATH, LOG_REVIEW_STATUS_ROUTE, LOG_REVIEW_GET_LOGS_ROUTE, SELECT_ADMIN_CHECK_ROUTE, abbreviate, avg, ceilToNumDecimals, convertDateKeyToDateInfo, floorToNumDecimals, forceNumIntoBounds, padDecimalZeros, padZerosLeft, roundToNumDecimals, sum, waitMs, getOrdinal, getTimeInfoInET, getMondayOfTimestamp, startMinWait, getHumanReadableDate, getPartOfDay, stringsToHumanReadableList, onlyKeepLetters, parallelLimit, getMonthName, genCSV, extractProp, compareArraysByProp, getLocalTimeInfo, genCommaList, validateEmail, validatePhoneNumber, validateString, idify, prefixWithAOrAn, everyAsync, filterAsync, forEachAsync, mapAsync, someAsync, capitalize, shuffleArray, getWordCount, cloneDeep, getTimestampFromTimeInfoInET, spaceAtCapitals, ParamType, DayOfWeek, Log, LogType, LogSource, LogAction, LogBuiltInMetadata, LogMetadataType, LogFunction, LogTypeSpecificInfo, LogMainInfo, LogSourceSpecificInfo, LogLevel, CommonKitErrorCode, };
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import BackState from './BackState';
|
|
2
|
+
/**
|
|
3
|
+
* Controller for the back button, used to drive back navigation from anywhere
|
|
4
|
+
* in the app. Pass this to subpanels so they can describe their state and
|
|
5
|
+
* send the user home.
|
|
6
|
+
* @author Yuen Ler Chow
|
|
7
|
+
*/
|
|
8
|
+
type BackButtonController = {
|
|
9
|
+
/**
|
|
10
|
+
* Call this when the user navigates to a child of the home screen (something
|
|
11
|
+
* they can come back from)
|
|
12
|
+
*/
|
|
13
|
+
onSubpanelEntered: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Send the user back to the home screen
|
|
16
|
+
* @param [force] if true, go home immediately without checking the subpanel
|
|
17
|
+
* state (no confirmation, not blocked). If falsy, nothing happens when
|
|
18
|
+
* blocked and confirmation is required when there are unsaved changes
|
|
19
|
+
*/
|
|
20
|
+
goHome: (force?: boolean) => Promise<void>;
|
|
21
|
+
/**
|
|
22
|
+
* Set the state of the current subpanel, which determines what happens when
|
|
23
|
+
* the user tries to go back
|
|
24
|
+
* @param newSubpanelState the new state of the subpanel
|
|
25
|
+
*/
|
|
26
|
+
setSubpanelState: (newSubpanelState: BackState) => void;
|
|
27
|
+
/**
|
|
28
|
+
* Set the confirmation message shown if the user tries to go back while there
|
|
29
|
+
* are unsaved changes (cleared upon returning to the home screen)
|
|
30
|
+
* @param message the message to show
|
|
31
|
+
*/
|
|
32
|
+
setCustomUnsavedChangesMessage: (message: string) => void;
|
|
33
|
+
/**
|
|
34
|
+
* Set the message shown if the user tries to go back while blocked (cleared
|
|
35
|
+
* upon returning to the home screen)
|
|
36
|
+
* @param message the message to show
|
|
37
|
+
*/
|
|
38
|
+
setCustomBlockedMessage: (message: string) => void;
|
|
39
|
+
};
|
|
40
|
+
export default BackButtonController;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* State of the current subpanel, determining what happens when the user tries
|
|
3
|
+
* to go back to the home screen
|
|
4
|
+
* @author Yuen Ler Chow
|
|
5
|
+
*/
|
|
6
|
+
declare enum BackState {
|
|
7
|
+
Normal = "Normal",
|
|
8
|
+
UnsavedChanges = "UnsavedChanges",
|
|
9
|
+
Blocked = "Blocked"
|
|
10
|
+
}
|
|
11
|
+
export default BackState;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { LogMetadataType, ParamType, LogFunction } from 'dce-commonkit';
|
|
3
|
-
export { CommonKitErrorCode, DAY_IN_MS, DayOfWeek, ErrorWithCode, HOUR_IN_MS, LOG_REVIEW_GET_LOGS_ROUTE, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, Log, LogAction, LogBuiltInMetadata, LogFunction, LogLevel, LogMainInfo, LogMetadataType, LogReviewerFilterState, LogSource, LogSourceSpecificInfo, LogType, LogTypeSpecificInfo, MINUTE_IN_MS, ParamType, SELECT_ADMIN_CHECK_ROUTE, abbreviate, avg, capitalize, ceilToNumDecimals, cloneDeep, compareArraysByProp, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMondayOfTimestamp, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getTimestampFromTimeInfoInET, getWordCount, idify, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, roundToNumDecimals, shuffleArray, someAsync, spaceAtCapitals, startMinWait, stringsToHumanReadableList, sum, validateEmail, validatePhoneNumber, validateString, waitMs } from 'dce-commonkit';
|
|
3
|
+
export { CommonKitErrorCode, DAY_IN_MS, DayOfWeek, ErrorWithCode, HOUR_IN_MS, LOG_REVIEW_GET_LOGS_ROUTE, LOG_REVIEW_ROUTE_PATH_PREFIX, LOG_REVIEW_STATUS_ROUTE, LOG_ROUTE_PATH, Log, LogAction, LogBuiltInMetadata, LogFunction, LogLevel, LogMainInfo, LogMetadataType, LogReviewerFilterState, LogSource, LogSourceSpecificInfo, LogType, LogTypeSpecificInfo, MINUTE_IN_MS, ParamType, SELECT_ADMIN_CHECK_ROUTE, abbreviate, avg, capitalize, ceilToNumDecimals, cloneDeep, compareArraysByProp, convertDateKeyToDateInfo, everyAsync, extractProp, filterAsync, floorToNumDecimals, forEachAsync, forceNumIntoBounds, genCSV, genCommaList, getHumanReadableDate, getLocalTimeInfo, getMondayOfTimestamp, getMonthName, getOrdinal, getPartOfDay, getTimeInfoInET, getTimestampFromTimeInfoInET, getWordCount, idify, mapAsync, onlyKeepLetters, padDecimalZeros, padZerosLeft, parallelLimit, prefixWithAOrAn, roundToNumDecimals, shuffleArray, someAsync, spaceAtCapitals, startMinWait, stringsToHumanReadableList, sum, validateEmail, validatePhoneNumber, validateString, waitMs } from 'dce-commonkit';
|
|
4
4
|
import React$1 from 'react';
|
|
5
5
|
import { IconProp } from '@fortawesome/fontawesome-svg-core';
|
|
6
6
|
|
|
@@ -967,6 +967,75 @@ declare const combineClassNames: (classNames: (string | undefined | null | false
|
|
|
967
967
|
*/
|
|
968
968
|
declare const useForceRender: (useReducer: any) => () => void;
|
|
969
969
|
|
|
970
|
+
/**
|
|
971
|
+
* State of the current subpanel, determining what happens when the user tries
|
|
972
|
+
* to go back to the home screen
|
|
973
|
+
* @author Yuen Ler Chow
|
|
974
|
+
*/
|
|
975
|
+
declare enum BackState {
|
|
976
|
+
Normal = "Normal",
|
|
977
|
+
UnsavedChanges = "UnsavedChanges",
|
|
978
|
+
Blocked = "Blocked"
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
/**
|
|
982
|
+
* Controller for the back button, used to drive back navigation from anywhere
|
|
983
|
+
* in the app. Pass this to subpanels so they can describe their state and
|
|
984
|
+
* send the user home.
|
|
985
|
+
* @author Yuen Ler Chow
|
|
986
|
+
*/
|
|
987
|
+
type BackButtonController = {
|
|
988
|
+
/**
|
|
989
|
+
* Call this when the user navigates to a child of the home screen (something
|
|
990
|
+
* they can come back from)
|
|
991
|
+
*/
|
|
992
|
+
onSubpanelEntered: () => void;
|
|
993
|
+
/**
|
|
994
|
+
* Send the user back to the home screen
|
|
995
|
+
* @param [force] if true, go home immediately without checking the subpanel
|
|
996
|
+
* state (no confirmation, not blocked). If falsy, nothing happens when
|
|
997
|
+
* blocked and confirmation is required when there are unsaved changes
|
|
998
|
+
*/
|
|
999
|
+
goHome: (force?: boolean) => Promise<void>;
|
|
1000
|
+
/**
|
|
1001
|
+
* Set the state of the current subpanel, which determines what happens when
|
|
1002
|
+
* the user tries to go back
|
|
1003
|
+
* @param newSubpanelState the new state of the subpanel
|
|
1004
|
+
*/
|
|
1005
|
+
setSubpanelState: (newSubpanelState: BackState) => void;
|
|
1006
|
+
/**
|
|
1007
|
+
* Set the confirmation message shown if the user tries to go back while there
|
|
1008
|
+
* are unsaved changes (cleared upon returning to the home screen)
|
|
1009
|
+
* @param message the message to show
|
|
1010
|
+
*/
|
|
1011
|
+
setCustomUnsavedChangesMessage: (message: string) => void;
|
|
1012
|
+
/**
|
|
1013
|
+
* Set the message shown if the user tries to go back while blocked (cleared
|
|
1014
|
+
* upon returning to the home screen)
|
|
1015
|
+
* @param message the message to show
|
|
1016
|
+
*/
|
|
1017
|
+
setCustomBlockedMessage: (message: string) => void;
|
|
1018
|
+
};
|
|
1019
|
+
|
|
1020
|
+
/**
|
|
1021
|
+
* Controller for driving back navigation from anywhere in the app. Requires
|
|
1022
|
+
* useBackButton to have been called in the top-level app
|
|
1023
|
+
* @author Yuen Ler Chow
|
|
1024
|
+
*/
|
|
1025
|
+
declare const backButtonController: BackButtonController;
|
|
1026
|
+
/**
|
|
1027
|
+
* Hook that makes the browser's back button navigate within the app instead of
|
|
1028
|
+
* leaving it. Call this once in your top-level app, then use
|
|
1029
|
+
* backButtonController to enter subpanels and describe their state.
|
|
1030
|
+
*
|
|
1031
|
+
* Assumes a single level of navigation: one home screen plus subpanels that the
|
|
1032
|
+
* user returns home from.
|
|
1033
|
+
* @author Yuen Ler Chow
|
|
1034
|
+
* @param handleGoHomeFunc handler that performs the app state changes required
|
|
1035
|
+
* to return to the home screen
|
|
1036
|
+
*/
|
|
1037
|
+
declare const useBackButton: (handleGoHomeFunc: () => void) => void;
|
|
1038
|
+
|
|
970
1039
|
/**
|
|
971
1040
|
* Checks if the current user is a select admin
|
|
972
1041
|
* @author Gardenia Liu
|
|
@@ -974,4 +1043,4 @@ declare const useForceRender: (useReducer: any) => () => void;
|
|
|
974
1043
|
*/
|
|
975
1044
|
declare const isSelectAdmin: () => Promise<boolean>;
|
|
976
1045
|
|
|
977
|
-
export { AppWrapper, AutoscrollToBottomContainer, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, Drawer, Dropdown, DropdownItemType, DynamicWord, ErrorBox, FakeProgressBar, IntelliTable, IntelliTableColumn, ItemPicker, LoadingSpinner, LogReviewer, Modal, ModalButtonType, ModalSize, ModalType, MultiSwitch, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, ProgressBar, ProgressBarSize, RadioButton, SimpleDateChooser, SimpleMonthChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant, addFatalErrorHandler, alert, canReviewLogs, combineClassNames, confirm, initClient, isMobileOrTablet, isSelectAdmin, leaveToURL, logClientEvent, makeLinksClickable, prompt, setClientEventMetadataPopulator, showFatalError, stubServerEndpoint, useForceRender, visitServerEndpoint };
|
|
1046
|
+
export { AppWrapper, AutoscrollToBottomContainer, BackButtonController, BackState, ButtonInputGroup, CSVDownloadButton, CheckboxButton, CopiableBox, DBEntry, DBEntryField, DBEntryFieldType, DBEntryManagerPanel, Drawer, Dropdown, DropdownItemType, DynamicWord, ErrorBox, FakeProgressBar, IntelliTable, IntelliTableColumn, ItemPicker, LoadingSpinner, LogReviewer, Modal, ModalButtonType, ModalSize, ModalType, MultiSwitch, PickableItem, PopFailureMark, PopPendingMark, PopSuccessMark, ProgressBar, ProgressBarSize, RadioButton, SimpleDateChooser, SimpleMonthChooser, SimpleTimeChooser, TabBox, ToggleSwitch, Tooltip, Variant, addFatalErrorHandler, alert, backButtonController, canReviewLogs, combineClassNames, confirm, initClient, isMobileOrTablet, isSelectAdmin, leaveToURL, logClientEvent, makeLinksClickable, prompt, setClientEventMetadataPopulator, showFatalError, stubServerEndpoint, useBackButton, useForceRender, visitServerEndpoint };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dce-reactkit",
|
|
3
|
-
"version": "5.0.
|
|
3
|
+
"version": "5.0.10",
|
|
4
4
|
"description": "Shared components for Harvard DCE apps",
|
|
5
5
|
"main": "dist/cjs/index.js",
|
|
6
6
|
"module": "dist/esm/index.js",
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
},
|
|
25
25
|
"homepage": "https://github.com/harvard-edtech/dce-reactkit#readme",
|
|
26
26
|
"dependencies": {
|
|
27
|
-
"dce-commonkit": "^5.0.
|
|
27
|
+
"dce-commonkit": "^5.0.3",
|
|
28
28
|
"react-select": "^5.7.3"
|
|
29
29
|
},
|
|
30
30
|
"peerDependencies": {
|
|
@@ -258,48 +258,50 @@ const NestableItemList: React.FC<Props> = (props) => {
|
|
|
258
258
|
|
|
259
259
|
return (
|
|
260
260
|
<div key={item.id}>
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
<
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
261
|
+
<div className="NestableItemList-item d-flex align-items-center">
|
|
262
|
+
{/* Dropdown Button */}
|
|
263
|
+
<span
|
|
264
|
+
className="NestableItemList-dropdown-button-container"
|
|
265
|
+
style={{
|
|
266
|
+
minWidth: '2rem',
|
|
267
|
+
}}
|
|
268
|
+
>
|
|
269
|
+
{item.isGroup && (
|
|
270
|
+
<button
|
|
271
|
+
className={`NestableItemList-dropdown-button NestableItemList-dropdown-button-${item.id}`}
|
|
272
|
+
style={{
|
|
273
|
+
border: 0,
|
|
274
|
+
backgroundColor: 'transparent',
|
|
275
|
+
}}
|
|
276
|
+
type="button"
|
|
277
|
+
onClick={() => {
|
|
278
|
+
dispatch({
|
|
279
|
+
type: ActionType.ToggleChild,
|
|
280
|
+
id: item.id,
|
|
281
|
+
});
|
|
282
|
+
}}
|
|
283
|
+
aria-label={`${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${accessibleName}`}
|
|
284
|
+
>
|
|
285
|
+
<FontAwesomeIcon
|
|
286
|
+
icon={childExpanded[item.id] ? faChevronDown : faChevronRight}
|
|
287
|
+
/>
|
|
288
|
+
</button>
|
|
289
|
+
)}
|
|
290
|
+
</span>
|
|
291
|
+
|
|
292
|
+
{/* Checkbox and Text (optionally wrapped in a tooltip) */}
|
|
293
|
+
{
|
|
294
|
+
item.tooltip
|
|
295
|
+
? (
|
|
296
|
+
<Tooltip text={item.tooltip}>
|
|
297
|
+
<span className="d-inline-block">
|
|
298
|
+
{checkbox}
|
|
299
|
+
</span>
|
|
300
|
+
</Tooltip>
|
|
301
|
+
)
|
|
302
|
+
: checkbox
|
|
303
|
+
}
|
|
304
|
+
</div>
|
|
303
305
|
|
|
304
306
|
{/* Children */}
|
|
305
307
|
{(item.isGroup && childExpanded[item.id]) && (
|
|
@@ -0,0 +1,352 @@
|
|
|
1
|
+
// Import React
|
|
2
|
+
import { useEffect } from 'react';
|
|
3
|
+
|
|
4
|
+
// Import components
|
|
5
|
+
import { alert, confirm } from '../components/AppWrapper';
|
|
6
|
+
|
|
7
|
+
// Import types
|
|
8
|
+
import BackState from '../types/BackState';
|
|
9
|
+
import BackButtonController from '../types/BackButtonController';
|
|
10
|
+
|
|
11
|
+
/*------------------------------------------------------------------------*/
|
|
12
|
+
/* ------------------------------ Constants ----------------------------- */
|
|
13
|
+
/*------------------------------------------------------------------------*/
|
|
14
|
+
|
|
15
|
+
// Marker stored on history entries owned by this hook
|
|
16
|
+
const HISTORY_STATE_MARKER = { dceReactKitBackButton: true };
|
|
17
|
+
|
|
18
|
+
// Default message shown when the user tries to go back while blocked
|
|
19
|
+
const BLOCKED_TITLE = 'Cannot Go Back';
|
|
20
|
+
const BLOCKED_MESSAGE = 'A task is currently in progress. Please try again once it finishes.';
|
|
21
|
+
|
|
22
|
+
// Default message shown when the user tries to go back with unsaved changes
|
|
23
|
+
const UNSAVED_CHANGES_TITLE = 'Abandon Changes?';
|
|
24
|
+
const UNSAVED_CHANGES_MESSAGE = 'Any unsaved changes may be lost.';
|
|
25
|
+
|
|
26
|
+
/*------------------------------------------------------------------------*/
|
|
27
|
+
/* -------------------------------- Types ------------------------------- */
|
|
28
|
+
/*------------------------------------------------------------------------*/
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* All state for the back button, kept in one place because the pieces are
|
|
32
|
+
* interdependent. A subpanel's back state and its custom messages only have
|
|
33
|
+
* meaning while the user is inside a subpanel, so they are nested within it:
|
|
34
|
+
* returning home is then a single change (dropping the subpanel) that cannot
|
|
35
|
+
* leave a stale message or back state behind
|
|
36
|
+
* @author Yuen Ler Chow
|
|
37
|
+
*/
|
|
38
|
+
type BackButtonState = {
|
|
39
|
+
// Handler that performs the app state changes required to return to the home
|
|
40
|
+
// screen. Undefined until useBackButton has been called
|
|
41
|
+
handleGoHome?: () => void,
|
|
42
|
+
// The subpanel the user is currently in. Undefined while on the home screen
|
|
43
|
+
subpanel?: {
|
|
44
|
+
// What should happen when the user tries to go back
|
|
45
|
+
backState: BackState,
|
|
46
|
+
// Message shown when confirming that unsaved changes will be lost
|
|
47
|
+
customUnsavedChangesMessage?: string,
|
|
48
|
+
// Message shown when the user tries to go back while blocked
|
|
49
|
+
customBlockedMessage?: string,
|
|
50
|
+
},
|
|
51
|
+
// True while a prompt is on screen, so that repeated back presses don't stack
|
|
52
|
+
// up duplicate prompts
|
|
53
|
+
promptVisible: boolean,
|
|
54
|
+
// True if the next back navigation was triggered by us and must pass through
|
|
55
|
+
// without being intercepted
|
|
56
|
+
bypassNextPop: boolean,
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/*------------------------------------------------------------------------*/
|
|
60
|
+
/* --------------------------- Static State ----------------------------- */
|
|
61
|
+
/*------------------------------------------------------------------------*/
|
|
62
|
+
|
|
63
|
+
// Current state, stored statically so that any subpanel can drive the back
|
|
64
|
+
// button without prop drilling or context
|
|
65
|
+
let state: BackButtonState = {
|
|
66
|
+
promptVisible: false,
|
|
67
|
+
bypassNextPop: false,
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
/*------------------------------------------------------------------------*/
|
|
71
|
+
/* ------------------------- Helper Functions --------------------------- */
|
|
72
|
+
/*------------------------------------------------------------------------*/
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Get the app's go-home handler, throwing if the hook has not been set up yet
|
|
76
|
+
* @author Yuen Ler Chow
|
|
77
|
+
* @returns handler that returns the app to its home screen
|
|
78
|
+
*/
|
|
79
|
+
const getHandleGoHome = () => {
|
|
80
|
+
if (!state.handleGoHome) {
|
|
81
|
+
throw new Error('Cannot use the back button: call useBackButton in your top-level app before using backButtonController.');
|
|
82
|
+
}
|
|
83
|
+
return state.handleGoHome;
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Return to the home screen: clear the current subpanel and run the app's
|
|
88
|
+
* go-home handler
|
|
89
|
+
* @author Yuen Ler Chow
|
|
90
|
+
* @param consumeHistoryEntry if true, also step back over the history entry
|
|
91
|
+
* that was added when the subpanel was entered
|
|
92
|
+
*/
|
|
93
|
+
const returnHome = (consumeHistoryEntry: boolean) => {
|
|
94
|
+
const handleGoHome = getHandleGoHome();
|
|
95
|
+
const wasInSubpanel = !!state.subpanel;
|
|
96
|
+
|
|
97
|
+
// Dropping the subpanel clears its back state and custom messages at once
|
|
98
|
+
state.subpanel = undefined;
|
|
99
|
+
|
|
100
|
+
// Update the app
|
|
101
|
+
handleGoHome();
|
|
102
|
+
|
|
103
|
+
// Entering a subpanel added a history entry. When the user leaves via an
|
|
104
|
+
// in-app control, that entry is still on the stack, so we step over it to keep
|
|
105
|
+
// the browser history in sync with the app. When the browser's back button is
|
|
106
|
+
// what brought us here, that entry has already been consumed by the browser,
|
|
107
|
+
// and stepping back again would take the user out of the app entirely
|
|
108
|
+
if (consumeHistoryEntry && wasInSubpanel) {
|
|
109
|
+
state.bypassNextPop = true;
|
|
110
|
+
window.history.back();
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Keep the user in place after a back navigation that should not be allowed.
|
|
116
|
+
* The browser's popstate event is not cancelable, so the only way to stay put
|
|
117
|
+
* is to immediately push a new entry to replace the one that was just popped.
|
|
118
|
+
* This has to happen synchronously while handling the pop, before awaiting
|
|
119
|
+
* anything, otherwise the navigation has already taken effect
|
|
120
|
+
* @author Yuen Ler Chow
|
|
121
|
+
*/
|
|
122
|
+
const undoPop = () => {
|
|
123
|
+
window.history.pushState(HISTORY_STATE_MARKER, '');
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Tell the user that they cannot go back right now
|
|
128
|
+
* @author Yuen Ler Chow
|
|
129
|
+
*/
|
|
130
|
+
const showBlockedMessage = async () => {
|
|
131
|
+
state.promptVisible = true;
|
|
132
|
+
await alert(
|
|
133
|
+
BLOCKED_TITLE,
|
|
134
|
+
state.subpanel?.customBlockedMessage ?? BLOCKED_MESSAGE,
|
|
135
|
+
);
|
|
136
|
+
state.promptVisible = false;
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Ask the user whether they want to leave despite having unsaved changes
|
|
141
|
+
* @author Yuen Ler Chow
|
|
142
|
+
* @returns true if the user wants to leave
|
|
143
|
+
*/
|
|
144
|
+
const askToAbandonChanges = async (): Promise<boolean> => {
|
|
145
|
+
state.promptVisible = true;
|
|
146
|
+
const confirmed = await confirm(
|
|
147
|
+
UNSAVED_CHANGES_TITLE,
|
|
148
|
+
state.subpanel?.customUnsavedChangesMessage ?? UNSAVED_CHANGES_MESSAGE,
|
|
149
|
+
{
|
|
150
|
+
confirmButtonText: 'Abandon Changes',
|
|
151
|
+
cancelButtonText: 'Stay Here',
|
|
152
|
+
},
|
|
153
|
+
);
|
|
154
|
+
state.promptVisible = false;
|
|
155
|
+
return confirmed;
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Handle a back navigation performed by the browser
|
|
160
|
+
* @author Yuen Ler Chow
|
|
161
|
+
*/
|
|
162
|
+
const handlePopState = () => {
|
|
163
|
+
// Back navigation that we triggered ourselves: let it through
|
|
164
|
+
if (state.bypassNextPop) {
|
|
165
|
+
state.bypassNextPop = false;
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Already on the home screen: nothing for us to intercept
|
|
170
|
+
if (!state.subpanel) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// A prompt is already on screen: stay put instead of stacking another one
|
|
175
|
+
if (state.promptVisible) {
|
|
176
|
+
undoPop();
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// Blocked: stay put and explain why
|
|
181
|
+
if (state.subpanel.backState === BackState.Blocked) {
|
|
182
|
+
undoPop();
|
|
183
|
+
showBlockedMessage();
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Unsaved changes: stay put until the user confirms. The pop is undone
|
|
188
|
+
// asynchronously here, before awaiting the confirmation, so that the user
|
|
189
|
+
// remains in the subpanel while they decide
|
|
190
|
+
if (state.subpanel.backState === BackState.UnsavedChanges) {
|
|
191
|
+
undoPop();
|
|
192
|
+
(async () => {
|
|
193
|
+
if (await askToAbandonChanges()) {
|
|
194
|
+
returnHome(true);
|
|
195
|
+
}
|
|
196
|
+
})();
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Normal: allow it. The browser already consumed the history entry, so there
|
|
201
|
+
// is nothing left for us to step over
|
|
202
|
+
returnHome(false);
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
/*------------------------------------------------------------------------*/
|
|
206
|
+
/* ------------------------------ Controller ---------------------------- */
|
|
207
|
+
/*------------------------------------------------------------------------*/
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Controller for driving back navigation from anywhere in the app. Requires
|
|
211
|
+
* useBackButton to have been called in the top-level app
|
|
212
|
+
* @author Yuen Ler Chow
|
|
213
|
+
*/
|
|
214
|
+
export const backButtonController: BackButtonController = {
|
|
215
|
+
/**
|
|
216
|
+
* Call this when the user navigates to a child of the home screen (something
|
|
217
|
+
* they can come back from)
|
|
218
|
+
* @author Yuen Ler Chow
|
|
219
|
+
*/
|
|
220
|
+
onSubpanelEntered: () => {
|
|
221
|
+
getHandleGoHome();
|
|
222
|
+
|
|
223
|
+
state.subpanel = {
|
|
224
|
+
backState: BackState.Normal,
|
|
225
|
+
};
|
|
226
|
+
|
|
227
|
+
// Add a history entry to come back to, so that the next back navigation is
|
|
228
|
+
// intercepted instead of leaving the app
|
|
229
|
+
window.history.pushState(HISTORY_STATE_MARKER, '');
|
|
230
|
+
},
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Send the user back to the home screen
|
|
234
|
+
* @author Yuen Ler Chow
|
|
235
|
+
* @param [force] if true, go home immediately without checking the subpanel
|
|
236
|
+
* state. If falsy, nothing happens while blocked and confirmation is
|
|
237
|
+
* required when there are unsaved changes
|
|
238
|
+
*/
|
|
239
|
+
goHome: async (force?: boolean) => {
|
|
240
|
+
getHandleGoHome();
|
|
241
|
+
|
|
242
|
+
// Unless the caller is forcing the navigation, the subpanel's state decides
|
|
243
|
+
// whether the user may leave: a blocked subpanel refuses and explains why,
|
|
244
|
+
// and one with unsaved changes leaves only if the user confirms
|
|
245
|
+
if (!force) {
|
|
246
|
+
// A prompt is already asking the user this same question
|
|
247
|
+
if (state.promptVisible) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (state.subpanel?.backState === BackState.Blocked) {
|
|
252
|
+
await showBlockedMessage();
|
|
253
|
+
return;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
if (state.subpanel?.backState === BackState.UnsavedChanges) {
|
|
257
|
+
const confirmed = await askToAbandonChanges();
|
|
258
|
+
if (!confirmed) {
|
|
259
|
+
return;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
returnHome(true);
|
|
265
|
+
},
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Set the state of the current subpanel, which determines what happens when
|
|
269
|
+
* the user tries to go back. Ignored while on the home screen, where there
|
|
270
|
+
* is no back navigation to describe
|
|
271
|
+
* @author Yuen Ler Chow
|
|
272
|
+
* @param newSubpanelState the new state of the subpanel
|
|
273
|
+
*/
|
|
274
|
+
setSubpanelState: (newSubpanelState: BackState) => {
|
|
275
|
+
if (!state.subpanel) {
|
|
276
|
+
return;
|
|
277
|
+
}
|
|
278
|
+
state.subpanel.backState = newSubpanelState;
|
|
279
|
+
},
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Set the confirmation message shown if the user tries to go back while there
|
|
283
|
+
* are unsaved changes. Cleared upon returning to the home screen
|
|
284
|
+
* @author Yuen Ler Chow
|
|
285
|
+
* @param message the message to show
|
|
286
|
+
*/
|
|
287
|
+
setCustomUnsavedChangesMessage: (message: string) => {
|
|
288
|
+
if (!state.subpanel) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
state.subpanel.customUnsavedChangesMessage = message;
|
|
292
|
+
},
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Set the message shown if the user tries to go back while blocked. Cleared
|
|
296
|
+
* upon returning to the home screen
|
|
297
|
+
* @author Yuen Ler Chow
|
|
298
|
+
* @param message the message to show
|
|
299
|
+
*/
|
|
300
|
+
setCustomBlockedMessage: (message: string) => {
|
|
301
|
+
if (!state.subpanel) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
state.subpanel.customBlockedMessage = message;
|
|
305
|
+
},
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
/*------------------------------------------------------------------------*/
|
|
309
|
+
/* --------------------------------- Hook ------------------------------- */
|
|
310
|
+
/*------------------------------------------------------------------------*/
|
|
311
|
+
|
|
312
|
+
/**
|
|
313
|
+
* Hook that makes the browser's back button navigate within the app instead of
|
|
314
|
+
* leaving it. Call this once in your top-level app, then use
|
|
315
|
+
* backButtonController to enter subpanels and describe their state.
|
|
316
|
+
*
|
|
317
|
+
* Assumes a single level of navigation: one home screen plus subpanels that the
|
|
318
|
+
* user returns home from.
|
|
319
|
+
* @author Yuen Ler Chow
|
|
320
|
+
* @param handleGoHomeFunc handler that performs the app state changes required
|
|
321
|
+
* to return to the home screen
|
|
322
|
+
*/
|
|
323
|
+
const useBackButton = (handleGoHomeFunc: () => void) => {
|
|
324
|
+
// Store the handler on every render so that the listener below, which is only
|
|
325
|
+
// registered once, always calls the app's current version of it
|
|
326
|
+
state.handleGoHome = handleGoHomeFunc;
|
|
327
|
+
|
|
328
|
+
// The popstate listener has to be attached to the window, which is outside of
|
|
329
|
+
// React, so an effect is used to add it when the app mounts and remove it if
|
|
330
|
+
// the app ever unmounts. The empty dependency array keeps this to a single
|
|
331
|
+
// listener for the lifetime of the app instead of one per render
|
|
332
|
+
useEffect(
|
|
333
|
+
() => {
|
|
334
|
+
// Mark the current entry as the home entry
|
|
335
|
+
window.history.replaceState(HISTORY_STATE_MARKER, '');
|
|
336
|
+
|
|
337
|
+
window.addEventListener('popstate', handlePopState);
|
|
338
|
+
return () => {
|
|
339
|
+
window.removeEventListener('popstate', handlePopState);
|
|
340
|
+
|
|
341
|
+
// Clear the static state so that a remount starts from scratch
|
|
342
|
+
state = {
|
|
343
|
+
promptVisible: false,
|
|
344
|
+
bypassNextPop: false,
|
|
345
|
+
};
|
|
346
|
+
};
|
|
347
|
+
},
|
|
348
|
+
[],
|
|
349
|
+
);
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
export default useBackButton;
|