feat: add data hook and component
This commit is contained in:
parent
7f228ebbcb
commit
2834f3535b
1
packages/core/src/components/index.tsx
Normal file
1
packages/core/src/components/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export * from './live';
|
19
packages/core/src/components/live/Data.tsx
Normal file
19
packages/core/src/components/live/Data.tsx
Normal 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;
|
1
packages/core/src/components/live/index.tsx
Normal file
1
packages/core/src/components/live/index.tsx
Normal file
@ -0,0 +1 @@
|
||||
export { default as Data, type DataProps } from './Data';
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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';
|
||||
|
39
packages/core/src/lib/hooks/useData.tsx
Normal file
39
packages/core/src/lib/hooks/useData.tsx
Normal 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;
|
||||
}
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
13
packages/core/src/lib/util/events/DataEvent.ts
Normal file
13
packages/core/src/lib/util/events/DataEvent.ts
Normal 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 });
|
||||
}
|
||||
}
|
@ -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>(
|
||||
|
2
packages/core/src/types/global.d.ts
vendored
2
packages/core/src/types/global.d.ts
vendored
@ -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;
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user