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
package/dist/cjs/index.js
CHANGED
|
@@ -2886,22 +2886,23 @@ const NestableItemList = (props) => {
|
|
|
2886
2886
|
onChanged(changeChecked(item.id, checked, items));
|
|
2887
2887
|
}, ariaLabel: `Select ${accessibleName}`, checkedVariant: Variant$1.Light, uncheckedVariant: Variant$1.Light }));
|
|
2888
2888
|
return (React__default["default"].createElement("div", { key: item.id },
|
|
2889
|
-
React__default["default"].createElement("
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
|
|
2893
|
-
|
|
2894
|
-
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
React__default["default"].createElement(
|
|
2904
|
-
|
|
2889
|
+
React__default["default"].createElement("div", { className: "NestableItemList-item d-flex align-items-center" },
|
|
2890
|
+
React__default["default"].createElement("span", { className: "NestableItemList-dropdown-button-container", style: {
|
|
2891
|
+
minWidth: '2rem',
|
|
2892
|
+
} }, item.isGroup && (React__default["default"].createElement("button", { className: `NestableItemList-dropdown-button NestableItemList-dropdown-button-${item.id}`, style: {
|
|
2893
|
+
border: 0,
|
|
2894
|
+
backgroundColor: 'transparent',
|
|
2895
|
+
}, type: "button", onClick: () => {
|
|
2896
|
+
dispatch({
|
|
2897
|
+
type: ActionType$9.ToggleChild,
|
|
2898
|
+
id: item.id,
|
|
2899
|
+
});
|
|
2900
|
+
}, "aria-label": `${childExpanded[item.id] ? 'Hide' : 'Show'} items in ${accessibleName}` },
|
|
2901
|
+
React__default["default"].createElement(reactFontawesome.FontAwesomeIcon, { icon: childExpanded[item.id] ? freeSolidSvgIcons.faChevronDown : freeSolidSvgIcons.faChevronRight })))),
|
|
2902
|
+
item.tooltip
|
|
2903
|
+
? (React__default["default"].createElement(Tooltip, { text: item.tooltip },
|
|
2904
|
+
React__default["default"].createElement("span", { className: "d-inline-block" }, checkbox)))
|
|
2905
|
+
: checkbox),
|
|
2905
2906
|
(item.isGroup && childExpanded[item.id]) && (React__default["default"].createElement("div", { className: "NestableItemList-children-container", style: {
|
|
2906
2907
|
paddingLeft: '2.2rem',
|
|
2907
2908
|
} },
|
|
@@ -14777,6 +14778,286 @@ const useForceRender = (useReducer) => {
|
|
|
14777
14778
|
};
|
|
14778
14779
|
};
|
|
14779
14780
|
|
|
14781
|
+
/**
|
|
14782
|
+
* State of the current subpanel, determining what happens when the user tries
|
|
14783
|
+
* to go back to the home screen
|
|
14784
|
+
* @author Yuen Ler Chow
|
|
14785
|
+
*/
|
|
14786
|
+
var BackState;
|
|
14787
|
+
(function (BackState) {
|
|
14788
|
+
// The user can go back immediately, no confirmation required
|
|
14789
|
+
BackState["Normal"] = "Normal";
|
|
14790
|
+
// The user must confirm before going back because progress will be lost
|
|
14791
|
+
BackState["UnsavedChanges"] = "UnsavedChanges";
|
|
14792
|
+
// The user cannot go back right now because work is in progress
|
|
14793
|
+
BackState["Blocked"] = "Blocked";
|
|
14794
|
+
})(BackState || (BackState = {}));
|
|
14795
|
+
var BackState$1 = BackState;
|
|
14796
|
+
|
|
14797
|
+
/*------------------------------------------------------------------------*/
|
|
14798
|
+
/* ------------------------------ Constants ----------------------------- */
|
|
14799
|
+
/*------------------------------------------------------------------------*/
|
|
14800
|
+
// Marker stored on history entries owned by this hook
|
|
14801
|
+
const HISTORY_STATE_MARKER = { dceReactKitBackButton: true };
|
|
14802
|
+
// Default message shown when the user tries to go back while blocked
|
|
14803
|
+
const BLOCKED_TITLE = 'Cannot Go Back';
|
|
14804
|
+
const BLOCKED_MESSAGE = 'A task is currently in progress. Please try again once it finishes.';
|
|
14805
|
+
// Default message shown when the user tries to go back with unsaved changes
|
|
14806
|
+
const UNSAVED_CHANGES_TITLE = 'Abandon Changes?';
|
|
14807
|
+
const UNSAVED_CHANGES_MESSAGE = 'Any unsaved changes may be lost.';
|
|
14808
|
+
/*------------------------------------------------------------------------*/
|
|
14809
|
+
/* --------------------------- Static State ----------------------------- */
|
|
14810
|
+
/*------------------------------------------------------------------------*/
|
|
14811
|
+
// Current state, stored statically so that any subpanel can drive the back
|
|
14812
|
+
// button without prop drilling or context
|
|
14813
|
+
let state = {
|
|
14814
|
+
promptVisible: false,
|
|
14815
|
+
bypassNextPop: false,
|
|
14816
|
+
};
|
|
14817
|
+
/*------------------------------------------------------------------------*/
|
|
14818
|
+
/* ------------------------- Helper Functions --------------------------- */
|
|
14819
|
+
/*------------------------------------------------------------------------*/
|
|
14820
|
+
/**
|
|
14821
|
+
* Get the app's go-home handler, throwing if the hook has not been set up yet
|
|
14822
|
+
* @author Yuen Ler Chow
|
|
14823
|
+
* @returns handler that returns the app to its home screen
|
|
14824
|
+
*/
|
|
14825
|
+
const getHandleGoHome = () => {
|
|
14826
|
+
if (!state.handleGoHome) {
|
|
14827
|
+
throw new Error('Cannot use the back button: call useBackButton in your top-level app before using backButtonController.');
|
|
14828
|
+
}
|
|
14829
|
+
return state.handleGoHome;
|
|
14830
|
+
};
|
|
14831
|
+
/**
|
|
14832
|
+
* Return to the home screen: clear the current subpanel and run the app's
|
|
14833
|
+
* go-home handler
|
|
14834
|
+
* @author Yuen Ler Chow
|
|
14835
|
+
* @param consumeHistoryEntry if true, also step back over the history entry
|
|
14836
|
+
* that was added when the subpanel was entered
|
|
14837
|
+
*/
|
|
14838
|
+
const returnHome = (consumeHistoryEntry) => {
|
|
14839
|
+
const handleGoHome = getHandleGoHome();
|
|
14840
|
+
const wasInSubpanel = !!state.subpanel;
|
|
14841
|
+
// Dropping the subpanel clears its back state and custom messages at once
|
|
14842
|
+
state.subpanel = undefined;
|
|
14843
|
+
// Update the app
|
|
14844
|
+
handleGoHome();
|
|
14845
|
+
// Entering a subpanel added a history entry. When the user leaves via an
|
|
14846
|
+
// in-app control, that entry is still on the stack, so we step over it to keep
|
|
14847
|
+
// the browser history in sync with the app. When the browser's back button is
|
|
14848
|
+
// what brought us here, that entry has already been consumed by the browser,
|
|
14849
|
+
// and stepping back again would take the user out of the app entirely
|
|
14850
|
+
if (consumeHistoryEntry && wasInSubpanel) {
|
|
14851
|
+
state.bypassNextPop = true;
|
|
14852
|
+
window.history.back();
|
|
14853
|
+
}
|
|
14854
|
+
};
|
|
14855
|
+
/**
|
|
14856
|
+
* Keep the user in place after a back navigation that should not be allowed.
|
|
14857
|
+
* The browser's popstate event is not cancelable, so the only way to stay put
|
|
14858
|
+
* is to immediately push a new entry to replace the one that was just popped.
|
|
14859
|
+
* This has to happen synchronously while handling the pop, before awaiting
|
|
14860
|
+
* anything, otherwise the navigation has already taken effect
|
|
14861
|
+
* @author Yuen Ler Chow
|
|
14862
|
+
*/
|
|
14863
|
+
const undoPop = () => {
|
|
14864
|
+
window.history.pushState(HISTORY_STATE_MARKER, '');
|
|
14865
|
+
};
|
|
14866
|
+
/**
|
|
14867
|
+
* Tell the user that they cannot go back right now
|
|
14868
|
+
* @author Yuen Ler Chow
|
|
14869
|
+
*/
|
|
14870
|
+
const showBlockedMessage = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
14871
|
+
var _a, _b;
|
|
14872
|
+
state.promptVisible = true;
|
|
14873
|
+
yield alert(BLOCKED_TITLE, (_b = (_a = state.subpanel) === null || _a === void 0 ? void 0 : _a.customBlockedMessage) !== null && _b !== void 0 ? _b : BLOCKED_MESSAGE);
|
|
14874
|
+
state.promptVisible = false;
|
|
14875
|
+
});
|
|
14876
|
+
/**
|
|
14877
|
+
* Ask the user whether they want to leave despite having unsaved changes
|
|
14878
|
+
* @author Yuen Ler Chow
|
|
14879
|
+
* @returns true if the user wants to leave
|
|
14880
|
+
*/
|
|
14881
|
+
const askToAbandonChanges = () => __awaiter(void 0, void 0, void 0, function* () {
|
|
14882
|
+
var _c, _d;
|
|
14883
|
+
state.promptVisible = true;
|
|
14884
|
+
const confirmed = yield confirm(UNSAVED_CHANGES_TITLE, (_d = (_c = state.subpanel) === null || _c === void 0 ? void 0 : _c.customUnsavedChangesMessage) !== null && _d !== void 0 ? _d : UNSAVED_CHANGES_MESSAGE, {
|
|
14885
|
+
confirmButtonText: 'Abandon Changes',
|
|
14886
|
+
cancelButtonText: 'Stay Here',
|
|
14887
|
+
});
|
|
14888
|
+
state.promptVisible = false;
|
|
14889
|
+
return confirmed;
|
|
14890
|
+
});
|
|
14891
|
+
/**
|
|
14892
|
+
* Handle a back navigation performed by the browser
|
|
14893
|
+
* @author Yuen Ler Chow
|
|
14894
|
+
*/
|
|
14895
|
+
const handlePopState = () => {
|
|
14896
|
+
// Back navigation that we triggered ourselves: let it through
|
|
14897
|
+
if (state.bypassNextPop) {
|
|
14898
|
+
state.bypassNextPop = false;
|
|
14899
|
+
return;
|
|
14900
|
+
}
|
|
14901
|
+
// Already on the home screen: nothing for us to intercept
|
|
14902
|
+
if (!state.subpanel) {
|
|
14903
|
+
return;
|
|
14904
|
+
}
|
|
14905
|
+
// A prompt is already on screen: stay put instead of stacking another one
|
|
14906
|
+
if (state.promptVisible) {
|
|
14907
|
+
undoPop();
|
|
14908
|
+
return;
|
|
14909
|
+
}
|
|
14910
|
+
// Blocked: stay put and explain why
|
|
14911
|
+
if (state.subpanel.backState === BackState$1.Blocked) {
|
|
14912
|
+
undoPop();
|
|
14913
|
+
showBlockedMessage();
|
|
14914
|
+
return;
|
|
14915
|
+
}
|
|
14916
|
+
// Unsaved changes: stay put until the user confirms. The pop is undone
|
|
14917
|
+
// asynchronously here, before awaiting the confirmation, so that the user
|
|
14918
|
+
// remains in the subpanel while they decide
|
|
14919
|
+
if (state.subpanel.backState === BackState$1.UnsavedChanges) {
|
|
14920
|
+
undoPop();
|
|
14921
|
+
(() => __awaiter(void 0, void 0, void 0, function* () {
|
|
14922
|
+
if (yield askToAbandonChanges()) {
|
|
14923
|
+
returnHome(true);
|
|
14924
|
+
}
|
|
14925
|
+
}))();
|
|
14926
|
+
return;
|
|
14927
|
+
}
|
|
14928
|
+
// Normal: allow it. The browser already consumed the history entry, so there
|
|
14929
|
+
// is nothing left for us to step over
|
|
14930
|
+
returnHome(false);
|
|
14931
|
+
};
|
|
14932
|
+
/*------------------------------------------------------------------------*/
|
|
14933
|
+
/* ------------------------------ Controller ---------------------------- */
|
|
14934
|
+
/*------------------------------------------------------------------------*/
|
|
14935
|
+
/**
|
|
14936
|
+
* Controller for driving back navigation from anywhere in the app. Requires
|
|
14937
|
+
* useBackButton to have been called in the top-level app
|
|
14938
|
+
* @author Yuen Ler Chow
|
|
14939
|
+
*/
|
|
14940
|
+
const backButtonController = {
|
|
14941
|
+
/**
|
|
14942
|
+
* Call this when the user navigates to a child of the home screen (something
|
|
14943
|
+
* they can come back from)
|
|
14944
|
+
* @author Yuen Ler Chow
|
|
14945
|
+
*/
|
|
14946
|
+
onSubpanelEntered: () => {
|
|
14947
|
+
getHandleGoHome();
|
|
14948
|
+
state.subpanel = {
|
|
14949
|
+
backState: BackState$1.Normal,
|
|
14950
|
+
};
|
|
14951
|
+
// Add a history entry to come back to, so that the next back navigation is
|
|
14952
|
+
// intercepted instead of leaving the app
|
|
14953
|
+
window.history.pushState(HISTORY_STATE_MARKER, '');
|
|
14954
|
+
},
|
|
14955
|
+
/**
|
|
14956
|
+
* Send the user back to the home screen
|
|
14957
|
+
* @author Yuen Ler Chow
|
|
14958
|
+
* @param [force] if true, go home immediately without checking the subpanel
|
|
14959
|
+
* state. If falsy, nothing happens while blocked and confirmation is
|
|
14960
|
+
* required when there are unsaved changes
|
|
14961
|
+
*/
|
|
14962
|
+
goHome: (force) => __awaiter(void 0, void 0, void 0, function* () {
|
|
14963
|
+
var _e, _f;
|
|
14964
|
+
getHandleGoHome();
|
|
14965
|
+
// Unless the caller is forcing the navigation, the subpanel's state decides
|
|
14966
|
+
// whether the user may leave: a blocked subpanel refuses and explains why,
|
|
14967
|
+
// and one with unsaved changes leaves only if the user confirms
|
|
14968
|
+
if (!force) {
|
|
14969
|
+
// A prompt is already asking the user this same question
|
|
14970
|
+
if (state.promptVisible) {
|
|
14971
|
+
return;
|
|
14972
|
+
}
|
|
14973
|
+
if (((_e = state.subpanel) === null || _e === void 0 ? void 0 : _e.backState) === BackState$1.Blocked) {
|
|
14974
|
+
yield showBlockedMessage();
|
|
14975
|
+
return;
|
|
14976
|
+
}
|
|
14977
|
+
if (((_f = state.subpanel) === null || _f === void 0 ? void 0 : _f.backState) === BackState$1.UnsavedChanges) {
|
|
14978
|
+
const confirmed = yield askToAbandonChanges();
|
|
14979
|
+
if (!confirmed) {
|
|
14980
|
+
return;
|
|
14981
|
+
}
|
|
14982
|
+
}
|
|
14983
|
+
}
|
|
14984
|
+
returnHome(true);
|
|
14985
|
+
}),
|
|
14986
|
+
/**
|
|
14987
|
+
* Set the state of the current subpanel, which determines what happens when
|
|
14988
|
+
* the user tries to go back. Ignored while on the home screen, where there
|
|
14989
|
+
* is no back navigation to describe
|
|
14990
|
+
* @author Yuen Ler Chow
|
|
14991
|
+
* @param newSubpanelState the new state of the subpanel
|
|
14992
|
+
*/
|
|
14993
|
+
setSubpanelState: (newSubpanelState) => {
|
|
14994
|
+
if (!state.subpanel) {
|
|
14995
|
+
return;
|
|
14996
|
+
}
|
|
14997
|
+
state.subpanel.backState = newSubpanelState;
|
|
14998
|
+
},
|
|
14999
|
+
/**
|
|
15000
|
+
* Set the confirmation message shown if the user tries to go back while there
|
|
15001
|
+
* are unsaved changes. Cleared upon returning to the home screen
|
|
15002
|
+
* @author Yuen Ler Chow
|
|
15003
|
+
* @param message the message to show
|
|
15004
|
+
*/
|
|
15005
|
+
setCustomUnsavedChangesMessage: (message) => {
|
|
15006
|
+
if (!state.subpanel) {
|
|
15007
|
+
return;
|
|
15008
|
+
}
|
|
15009
|
+
state.subpanel.customUnsavedChangesMessage = message;
|
|
15010
|
+
},
|
|
15011
|
+
/**
|
|
15012
|
+
* Set the message shown if the user tries to go back while blocked. Cleared
|
|
15013
|
+
* upon returning to the home screen
|
|
15014
|
+
* @author Yuen Ler Chow
|
|
15015
|
+
* @param message the message to show
|
|
15016
|
+
*/
|
|
15017
|
+
setCustomBlockedMessage: (message) => {
|
|
15018
|
+
if (!state.subpanel) {
|
|
15019
|
+
return;
|
|
15020
|
+
}
|
|
15021
|
+
state.subpanel.customBlockedMessage = message;
|
|
15022
|
+
},
|
|
15023
|
+
};
|
|
15024
|
+
/*------------------------------------------------------------------------*/
|
|
15025
|
+
/* --------------------------------- Hook ------------------------------- */
|
|
15026
|
+
/*------------------------------------------------------------------------*/
|
|
15027
|
+
/**
|
|
15028
|
+
* Hook that makes the browser's back button navigate within the app instead of
|
|
15029
|
+
* leaving it. Call this once in your top-level app, then use
|
|
15030
|
+
* backButtonController to enter subpanels and describe their state.
|
|
15031
|
+
*
|
|
15032
|
+
* Assumes a single level of navigation: one home screen plus subpanels that the
|
|
15033
|
+
* user returns home from.
|
|
15034
|
+
* @author Yuen Ler Chow
|
|
15035
|
+
* @param handleGoHomeFunc handler that performs the app state changes required
|
|
15036
|
+
* to return to the home screen
|
|
15037
|
+
*/
|
|
15038
|
+
const useBackButton = (handleGoHomeFunc) => {
|
|
15039
|
+
// Store the handler on every render so that the listener below, which is only
|
|
15040
|
+
// registered once, always calls the app's current version of it
|
|
15041
|
+
state.handleGoHome = handleGoHomeFunc;
|
|
15042
|
+
// The popstate listener has to be attached to the window, which is outside of
|
|
15043
|
+
// React, so an effect is used to add it when the app mounts and remove it if
|
|
15044
|
+
// the app ever unmounts. The empty dependency array keeps this to a single
|
|
15045
|
+
// listener for the lifetime of the app instead of one per render
|
|
15046
|
+
React.useEffect(() => {
|
|
15047
|
+
// Mark the current entry as the home entry
|
|
15048
|
+
window.history.replaceState(HISTORY_STATE_MARKER, '');
|
|
15049
|
+
window.addEventListener('popstate', handlePopState);
|
|
15050
|
+
return () => {
|
|
15051
|
+
window.removeEventListener('popstate', handlePopState);
|
|
15052
|
+
// Clear the static state so that a remount starts from scratch
|
|
15053
|
+
state = {
|
|
15054
|
+
promptVisible: false,
|
|
15055
|
+
bypassNextPop: false,
|
|
15056
|
+
};
|
|
15057
|
+
};
|
|
15058
|
+
}, []);
|
|
15059
|
+
};
|
|
15060
|
+
|
|
14780
15061
|
/*------------------------------------------------------------------------*/
|
|
14781
15062
|
/* ------------------------------- Caching ------------------------------ */
|
|
14782
15063
|
/*------------------------------------------------------------------------*/
|
|
@@ -14905,6 +15186,10 @@ Object.defineProperty(exports, 'compareArraysByProp', {
|
|
|
14905
15186
|
enumerable: true,
|
|
14906
15187
|
get: function () { return dceCommonkit.compareArraysByProp; }
|
|
14907
15188
|
});
|
|
15189
|
+
Object.defineProperty(exports, 'convertDateKeyToDateInfo', {
|
|
15190
|
+
enumerable: true,
|
|
15191
|
+
get: function () { return dceCommonkit.convertDateKeyToDateInfo; }
|
|
15192
|
+
});
|
|
14908
15193
|
Object.defineProperty(exports, 'everyAsync', {
|
|
14909
15194
|
enumerable: true,
|
|
14910
15195
|
get: function () { return dceCommonkit.everyAsync; }
|
|
@@ -15047,6 +15332,7 @@ Object.defineProperty(exports, 'waitMs', {
|
|
|
15047
15332
|
});
|
|
15048
15333
|
exports.AppWrapper = AppWrapper;
|
|
15049
15334
|
exports.AutoscrollToBottomContainer = AutoscrollToBottomContainer;
|
|
15335
|
+
exports.BackState = BackState$1;
|
|
15050
15336
|
exports.ButtonInputGroup = ButtonInputGroup;
|
|
15051
15337
|
exports.CSVDownloadButton = CSVDownloadButton;
|
|
15052
15338
|
exports.CheckboxButton = CheckboxButton;
|
|
@@ -15083,6 +15369,7 @@ exports.Tooltip = Tooltip;
|
|
|
15083
15369
|
exports.Variant = Variant$1;
|
|
15084
15370
|
exports.addFatalErrorHandler = addFatalErrorHandler;
|
|
15085
15371
|
exports.alert = alert;
|
|
15372
|
+
exports.backButtonController = backButtonController;
|
|
15086
15373
|
exports.canReviewLogs = canReviewLogs;
|
|
15087
15374
|
exports.combineClassNames = combineClassNames;
|
|
15088
15375
|
exports.confirm = confirm;
|
|
@@ -15096,6 +15383,7 @@ exports.prompt = prompt;
|
|
|
15096
15383
|
exports.setClientEventMetadataPopulator = setClientEventMetadataPopulator;
|
|
15097
15384
|
exports.showFatalError = showFatalError;
|
|
15098
15385
|
exports.stubServerEndpoint = stubServerEndpoint;
|
|
15386
|
+
exports.useBackButton = useBackButton;
|
|
15099
15387
|
exports.useForceRender = useForceRender;
|
|
15100
15388
|
exports.visitServerEndpoint = visitServerEndpoint;
|
|
15101
15389
|
//# sourceMappingURL=index.js.map
|