UNPKG

@redocly/theme

Version:

Shared UI components lib

183 lines 8.77 kB
"use strict"; 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