@atlaskit/editor-common
Version:
A package that contains common classes and components for editor and renderer
115 lines (111 loc) • 4.04 kB
JavaScript
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;
}