feat: add data hook and component

This commit is contained in:
Daniel Lautzenheiser 2023-07-19 12:20:13 -04:00
parent 7f228ebbcb
commit 2834f3535b
11 changed files with 103 additions and 2 deletions

View File

@ -0,0 +1 @@
export * from './live';

View File

@ -0,0 +1,19 @@
import React from 'react';
import { useData } from '@staticcms/core/lib';
import type { ValueOrNestedValue } from '@staticcms/core/interface';
import type { FC } from 'react';
export interface DataProps {
path: string;
value: ValueOrNestedValue;
}
const Data: FC<DataProps> = ({ path, value }) => {
const data = useData(value, path);
return <>{data}</>;
};
export default Data;

View File

@ -0,0 +1 @@
export { default as Data, type DataProps } from './Data';

View File

@ -3,6 +3,7 @@ import { useNavigate } from 'react-router-dom';
import { currentBackend } from './backend';
import bootstrap from './bootstrap';
import useData from './lib/hooks/useData';
import useEntries from './lib/hooks/useEntries';
import useFolderSupport from './lib/hooks/useFolderSupport';
import useHasChildErrors from './lib/hooks/useHasChildErrors';
@ -15,6 +16,7 @@ import Registry from './lib/registry';
export * from './backends';
export * from './interface';
export * from './components';
export * from './lib';
export { default as locales } from './locales';
export * from './widgets';
@ -41,6 +43,7 @@ if (typeof window !== 'undefined') {
window.useMediaFiles = window.useMediaFiles || useMediaFiles;
window.useMediaInsert = window.useMediaInsert || useMediaInsert;
window.useUUID = window.useUUID || useUUID;
window.useData = window.useData || useData;
window.useNavigate = window.useNavigate || useNavigate;
}

View File

@ -1,8 +1,9 @@
export { default as useData } from './useData';
export { default as useEntries } from './useEntries';
export { default as useFolderSupport } from './useFolderSupport';
export { default as useHasChildErrors } from './useHasChildErrors';
export { default as useIsMediaAsset } from './useIsMediaAsset';
export { default as useMediaAsset, useGetMediaAsset } from './useMediaAsset';
export { useGetMediaAsset, default as useMediaAsset } from './useMediaAsset';
export { default as useMediaFiles } from './useMediaFiles';
export { default as useMediaInsert } from './useMediaInsert';
export { default as useMediaPersist } from './useMediaPersist';

View File

@ -0,0 +1,39 @@
import { useCallback, useEffect, useState } from 'react';
import { useSearchParams } from 'react-router-dom';
import type { ValueOrNestedValue } from '@staticcms/core/interface';
import type DataUpdateEvent from '../util/events/DataEvent';
export default function useData(value: ValueOrNestedValue, path: string) {
const [data, setData] = useState(value);
const [searchParams] = useSearchParams();
const isCms = searchParams.get('useCmsData') === 'true';
const onDataChange = useCallback(
(event: DataUpdateEvent) => {
if (!isCms) {
return;
}
if (event.detail.fieldPath === path) {
setData(event.detail.value);
}
},
[isCms, path],
) as EventListenerOrEventListenerObject;
useEffect(() => {
if (!isCms) {
return;
}
window.addEventListener('data:update', onDataChange);
return () => {
window.removeEventListener('data:update', onDataChange);
};
}, [isCms, onDataChange]);
return data ?? null;
}

View File

@ -9,6 +9,7 @@ import { useAppDispatch, useAppSelector } from '@staticcms/core/store/hooks';
import { invokeEvent } from '../registry';
import { fileForEntry } from '../util/collection.util';
import useDebouncedCallback from './useDebouncedCallback';
import DataUpdateEvent from '../util/events/DataEvent';
import type { Collection, EntryData, Field } from '@staticcms/core/interface';
@ -25,7 +26,25 @@ async function handleChange(
let newEntry = cloneDeep(entry);
if (!isEqual(oldValue, newValue)) {
newEntry = await invokeEvent({ name: 'change', collection, field: field.name, data: newEntry });
const fieldPath = path.join('.');
newEntry = await invokeEvent({
name: 'change',
collection,
field: field.name,
fieldPath,
data: newEntry,
});
const updatedValue = get(newEntry, path);
window.dispatchEvent(
new DataUpdateEvent({
field: field.name,
fieldPath,
value: updatedValue,
}),
);
}
if ('fields' in field && field.fields) {

View File

@ -507,6 +507,7 @@ export async function invokeEvent(event: {
collection: string;
file?: string;
field: string;
fieldPath: string;
}): Promise<EntryData>;
export async function invokeEvent(event: {
name: AllowedEvent;

View File

@ -0,0 +1,13 @@
import type { ValueOrNestedValue } from '@staticcms/core/interface';
export interface DataUpdateEventProps {
field: string;
fieldPath: string;
value: ValueOrNestedValue;
}
export default class DataUpdateEvent extends CustomEvent<DataUpdateEventProps> {
constructor(detail: DataUpdateEventProps) {
super('data:update', { detail });
}
}

View File

@ -2,12 +2,14 @@ import { useEffect } from 'react';
import type AlertEvent from './events/AlertEvent';
import type ConfirmEvent from './events/ConfirmEvent';
import type DataUpdateEvent from './events/DataEvent';
import type MediaLibraryCloseEvent from './events/MediaLibraryCloseEvent';
interface EventMap {
alert: AlertEvent;
confirm: ConfirmEvent;
mediaLibraryClose: MediaLibraryCloseEvent;
'data:update': DataUpdateEvent;
}
export function useWindowEvent<K extends keyof WindowEventMap>(

View File

@ -13,6 +13,7 @@ import type {
useMediaFiles,
useMediaInsert,
useUUID,
useData,
} from '../lib/hooks';
declare global {
@ -34,6 +35,7 @@ declare global {
useMediaFiles: useMediaFiles;
useMediaInsert: useMediaInsert;
useUUID: useUUID;
useData: useData;
useNavigate: useNavigate;
}
}