@redocly/theme
Version:
Shared UI components lib
183 lines • 8.77 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.useCodeWalkthroughControls = useCodeWalkthroughControls;
const react_1 = require("react");
const react_router_dom_1 = require("react-router-dom");
const utils_1 = require("../../../core/utils");
const defaultControlsValues = {
input: '',
toggle: false,
filter: '',
};
function useCodeWalkthroughControls(filters, inputs, toggles, enableDeepLink) {
const location = (0, react_router_dom_1.useLocation)();
const navigate = (0, react_router_dom_1.useNavigate)();
const searchParams = (0, react_1.useMemo)(() => new URLSearchParams(location.search), [location.search]);
const filtersRef = (0, react_1.useRef)(filters);
const inputsRef = (0, react_1.useRef)(inputs);
const togglesRef = (0, react_1.useRef)(toggles);
const getInitialState = () => {
var _a, _b, _c, _d;
const state = {};
for (const [id, toggle] of Object.entries(toggles)) {
state[id] = Object.assign(Object.assign({}, toggle), { render: true, type: 'toggle', value: enableDeepLink ? searchParams.get(id) === 'true' : false });
}
for (const [id, input] of Object.entries(inputs)) {
state[id] = Object.assign(Object.assign({}, input), { render: true, type: 'input', value: enableDeepLink ? ((_a = searchParams.get(id)) !== null && _a !== void 0 ? _a : input.value) : input.value });
}
for (const [id, filter] of Object.entries(filters)) {
const defaultValue = ((_c = (_b = filter === null || filter === void 0 ? void 0 : filter.items) === null || _b === void 0 ? void 0 : _b[0]) === null || _c === void 0 ? void 0 : _c.value) || '';
state[id] = Object.assign(Object.assign({}, filter), { render: true, type: 'filter', value: enableDeepLink ? ((_d = searchParams.get(id)) !== null && _d !== void 0 ? _d : defaultValue) : defaultValue });
}
return state;
};
const [controlsState, setControlsState] = (0, react_1.useState)(getInitialState);
(0, react_1.useEffect)(() => {
const sameProps = [
JSON.stringify(filters) === JSON.stringify(filtersRef.current),
JSON.stringify(inputs) === JSON.stringify(inputsRef.current),
JSON.stringify(toggles) === JSON.stringify(togglesRef.current),
];
if (sameProps.every(Boolean)) {
return;
}
filtersRef.current = filters;
inputsRef.current = inputs;
togglesRef.current = toggles;
const newState = getInitialState();
// Preserve existing values where control type hasn't changed
Object.entries(newState).forEach(([id, control]) => {
const existingControl = controlsState[id];
if (existingControl && existingControl.type === control.type) {
// @ts-ignore
newState[id] = Object.assign(Object.assign({}, control), { value: existingControl.value });
}
});
setControlsState(newState);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [filters, inputs, toggles, enableDeepLink]);
const changeControlState = (id, value) => {
setControlsState((prev) => {
const control = prev[id];
if (!control) {
console.error(`Control with id "${id}" not found.`);
return prev;
}
switch (control.type) {
case 'input':
if (typeof value !== 'string') {
console.error(`Invalid value type for input "${id}". Input control type requires a string value.`);
return prev;
}
break;
case 'toggle':
if (typeof value !== 'boolean') {
console.error(`Invalid value type for toggle "${id}". Toggle control type requires a boolean value.`);
return prev;
}
break;
case 'filter':
if (typeof value !== 'string') {
console.error(`Invalid value type for filter "${id}". Filter control type requires a string value.`);
return prev;
}
break;
default:
console.error(`Invalid control type "${control === null || control === void 0 ? void 0 : control.type}" for control "${id}". Allowed types are "toggle", "input", or "filter".`);
return prev;
}
return Object.assign(Object.assign({}, prev), { [id]: Object.assign(Object.assign({}, control), { value }) });
});
};
const getControlState = (id) => {
const controlState = controlsState[id];
if (controlState) {
return {
render: controlState.render,
value: controlState.value,
};
}
else {
return null;
}
};
const walkthroughContext = (0, react_1.useMemo)(() => {
const areConditionsMet = (conditions) => (0, utils_1.matchCodeWalkthroughConditions)(conditions, controlsState);
// reset controls
for (const control of Object.values(controlsState)) {
control.render = true;
control.value = control.value || defaultControlsValues[control.type];
}
for (const [id, control] of Object.entries(controlsState)) {
if (control && !areConditionsMet(control)) {
controlsState[id].render = false;
controlsState[id].value = defaultControlsValues[control.type];
}
}
const activeFilters = [];
for (const [id, filter] of Object.entries(filters)) {
if (!controlsState[id].render) {
continue;
}
// code-walk-todo: need to check if we have a default fallback
const items = Array.isArray(filter === null || filter === void 0 ? void 0 : filter.items) ? filter.items : [];
const activeItems = items.filter((item) => areConditionsMet(item));
if (activeItems.length === 0) {
controlsState[id].render = false;
controlsState[id].value = defaultControlsValues['filter'];
continue;
}
const currentValue = controlsState[id].value;
if (currentValue) {
const isValueInActiveItems = activeItems.findIndex(({ value }) => value === currentValue) !== -1;
controlsState[id].value = isValueInActiveItems ? currentValue : activeItems[0].value;
}
else {
controlsState[id].value = activeItems[0].value;
}
activeFilters.push({
id,
label: filter.label,
items: activeItems,
});
}
const inputsState = Object.fromEntries(Object.entries(controlsState).filter(([_, controlState]) => controlState.type === 'input'));
const handleDownloadCode = (files) => (0, utils_1.downloadCodeWalkthrough)(files, controlsState, inputsState);
const getFileText = (file) => (0, utils_1.getCodeWalkthroughFileText)(file, controlsState, inputsState);
const populateInputsWithValue = (node) => (0, utils_1.replaceInputsWithValue)(node, inputsState);
return {
activeFilters,
areConditionsMet,
handleDownloadCode,
getFileText,
populateInputsWithValue,
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [controlsState]);
/**
* Update the URL search params with the current state of the filters and inputs
*/
(0, react_1.useEffect)(() => {
if (!enableDeepLink) {
return;
}
const newSearchParams = new URLSearchParams(Array.from(searchParams.entries()));
for (const [id, { value }] of Object.entries(controlsState)) {
if (value) {
newSearchParams.set(id, value.toString());
}
else {
newSearchParams.delete(id);
}
}
const newSearch = newSearchParams.toString();
if (newSearch === location.search.substring(1))
return;
navigate({ search: newSearch }, { replace: true });
// Ignore searchParams in dependency array to avoid infinite re-renders
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [controlsState]);
return Object.assign({ changeControlState,
getControlState }, walkthroughContext);
}
//# sourceMappingURL=use-code-walkthrough-controls.js.map