UNPKG

@atlaskit/editor-common

Version:

A package that contains common classes and components for editor and renderer

115 lines (111 loc) 4.04 kB
import { useState, useCallback, useMemo } from 'react'; import get from 'lodash/get'; import isEqual from 'lodash/isEqual'; import { usePluginStateEffect } from '../usePluginStateEffect'; // Ignored via go/ees005 // eslint-disable-next-line @typescript-eslint/no-explicit-any /** * This is designed to iterate through an object to get the path of its result * based on separation via "." * * Example: * ```typescript * type Test = { deepObject: { value: number } }; * // Type should be `"deepObject" | "deepObject.value"` * type Result = NestedKeys<Test>; * ``` */ /** * This is designed to iterate through a path of an object to get the type of its result * based on separation via "." * * Example: * ```typescript * type Test = { deepObject: { value: number } } * // Type should be `number` * type Result = Path<Test, 'deepObject.value'> * ``` */ /** * * NOTE: Generally you want to use `useSharedPluginStateWithSelector` over this which behaves similarly * but selects a slice of the state which is more performant. * * ⚠️⚠️⚠️ This is a debounced hook ⚠️⚠️⚠️ * If the plugins you are listening to generate multiple shared states while the user is typing, * your React Component will get only the last one. * * Used to return the current plugin state of input dependencies. * It will recursively retrieve a slice of the state using a "." to separate * parts of the state. * * Example: * * ```typescript * const pluginA: NextEditorPlugin< 'pluginA', { sharedState: { deepObj: { value: number | undefined } }; } > * ``` * You can use `const value = useSharedPluginStateSelector(api, 'pluginA.deepObj.value')` to retrieve the value * * Example in plugin: * * ```typescript * function ExampleContent({ api }: Props) { * const title = useSharedPluginStateSelector(api, 'dog.title') * return <p>{ title } { exampleState.description }</p> * } * * const examplePlugin: NextEditorPlugin<'example', { dependencies: [typeof pluginDog] }> = ({ api }) => { * return { * name: 'example', * contentComponent: () => <ExampleContent api={api} /> * } * } * ``` * * NOTE: If you pass an invalid path, `undefined` will be returned * * @param api * @param plugin * @param options * @returns */ export function useSharedPluginStateSelector(api, plugin, options = {}) { const transformer = useCallback(pluginState => { const [pluginName, ...properties] = plugin.split('.'); if (!pluginState || (properties === null || properties === void 0 ? void 0 : properties.length) === 0) { return undefined; } return get(pluginState === null || pluginState === void 0 ? void 0 : pluginState[`${pluginName}State`], properties); }, [plugin]); const pluginNameArray = useMemo(() => { const [pluginName] = plugin.split('.'); return [pluginName]; }, [plugin]); const initialState = useMemo(() => { var _api$pluginName; if (options.disabled) { return; } const [pluginName] = plugin.split('.'); return transformer({ [`${pluginName}State`]: api === null || api === void 0 ? void 0 : (_api$pluginName = api[pluginName]) === null || _api$pluginName === void 0 ? void 0 : _api$pluginName.sharedState.currentState() }); }, [plugin, api, options.disabled, transformer]); return useSharedPluginStateSelectorInternal(api, pluginNameArray, transformer, initialState, options); } function useSharedPluginStateSelectorInternal(api, plugins, transformer, initialState, options = {}) { const [selectedPluginState, setSelectedPluginState] = useState(() => initialState); usePluginStateEffect(api, plugins, pluginStates => { // `pluginStates`: This is the same type through inference - but typescript doesn't recognise them as they are computed slightly differently const transformedValue = transformer(pluginStates); if (!isEqual(transformedValue, selectedPluginState)) { setSelectedPluginState(() => transformedValue); } }, options); return selectedPluginState; }