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 { currentBackend } from './backend';
|
||||||
import bootstrap from './bootstrap';
|
import bootstrap from './bootstrap';
|
||||||
|
import useData from './lib/hooks/useData';
|
||||||
import useEntries from './lib/hooks/useEntries';
|
import useEntries from './lib/hooks/useEntries';
|
||||||
import useFolderSupport from './lib/hooks/useFolderSupport';
|
import useFolderSupport from './lib/hooks/useFolderSupport';
|
||||||
import useHasChildErrors from './lib/hooks/useHasChildErrors';
|
import useHasChildErrors from './lib/hooks/useHasChildErrors';
|
||||||
@ -15,6 +16,7 @@ import Registry from './lib/registry';
|
|||||||
|
|
||||||
export * from './backends';
|
export * from './backends';
|
||||||
export * from './interface';
|
export * from './interface';
|
||||||
|
export * from './components';
|
||||||
export * from './lib';
|
export * from './lib';
|
||||||
export { default as locales } from './locales';
|
export { default as locales } from './locales';
|
||||||
export * from './widgets';
|
export * from './widgets';
|
||||||
@ -41,6 +43,7 @@ if (typeof window !== 'undefined') {
|
|||||||
window.useMediaFiles = window.useMediaFiles || useMediaFiles;
|
window.useMediaFiles = window.useMediaFiles || useMediaFiles;
|
||||||
window.useMediaInsert = window.useMediaInsert || useMediaInsert;
|
window.useMediaInsert = window.useMediaInsert || useMediaInsert;
|
||||||
window.useUUID = window.useUUID || useUUID;
|
window.useUUID = window.useUUID || useUUID;
|
||||||
|
window.useData = window.useData || useData;
|
||||||
window.useNavigate = window.useNavigate || useNavigate;
|
window.useNavigate = window.useNavigate || useNavigate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
|
export { default as useData } from './useData';
|
||||||
export { default as useEntries } from './useEntries';
|
export { default as useEntries } from './useEntries';
|
||||||
export { default as useFolderSupport } from './useFolderSupport';
|
export { default as useFolderSupport } from './useFolderSupport';
|
||||||
export { default as useHasChildErrors } from './useHasChildErrors';
|
export { default as useHasChildErrors } from './useHasChildErrors';
|
||||||
export { default as useIsMediaAsset } from './useIsMediaAsset';
|
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 useMediaFiles } from './useMediaFiles';
|
||||||
export { default as useMediaInsert } from './useMediaInsert';
|
export { default as useMediaInsert } from './useMediaInsert';
|
||||||
export { default as useMediaPersist } from './useMediaPersist';
|
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 { invokeEvent } from '../registry';
|
||||||
import { fileForEntry } from '../util/collection.util';
|
import { fileForEntry } from '../util/collection.util';
|
||||||
import useDebouncedCallback from './useDebouncedCallback';
|
import useDebouncedCallback from './useDebouncedCallback';
|
||||||
|
import DataUpdateEvent from '../util/events/DataEvent';
|
||||||
|
|
||||||
import type { Collection, EntryData, Field } from '@staticcms/core/interface';
|
import type { Collection, EntryData, Field } from '@staticcms/core/interface';
|
||||||
|
|
||||||
@ -25,7 +26,25 @@ async function handleChange(
|
|||||||
let newEntry = cloneDeep(entry);
|
let newEntry = cloneDeep(entry);
|
||||||
|
|
||||||
if (!isEqual(oldValue, newValue)) {
|
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) {
|
if ('fields' in field && field.fields) {
|
||||||
|
@ -507,6 +507,7 @@ export async function invokeEvent(event: {
|
|||||||
collection: string;
|
collection: string;
|
||||||
file?: string;
|
file?: string;
|
||||||
field: string;
|
field: string;
|
||||||
|
fieldPath: string;
|
||||||
}): Promise<EntryData>;
|
}): Promise<EntryData>;
|
||||||
export async function invokeEvent(event: {
|
export async function invokeEvent(event: {
|
||||||
name: AllowedEvent;
|
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 AlertEvent from './events/AlertEvent';
|
||||||
import type ConfirmEvent from './events/ConfirmEvent';
|
import type ConfirmEvent from './events/ConfirmEvent';
|
||||||
|
import type DataUpdateEvent from './events/DataEvent';
|
||||||
import type MediaLibraryCloseEvent from './events/MediaLibraryCloseEvent';
|
import type MediaLibraryCloseEvent from './events/MediaLibraryCloseEvent';
|
||||||
|
|
||||||
interface EventMap {
|
interface EventMap {
|
||||||
alert: AlertEvent;
|
alert: AlertEvent;
|
||||||
confirm: ConfirmEvent;
|
confirm: ConfirmEvent;
|
||||||
mediaLibraryClose: MediaLibraryCloseEvent;
|
mediaLibraryClose: MediaLibraryCloseEvent;
|
||||||
|
'data:update': DataUpdateEvent;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useWindowEvent<K extends keyof WindowEventMap>(
|
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,
|
useMediaFiles,
|
||||||
useMediaInsert,
|
useMediaInsert,
|
||||||
useUUID,
|
useUUID,
|
||||||
|
useData,
|
||||||
} from '../lib/hooks';
|
} from '../lib/hooks';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@ -34,6 +35,7 @@ declare global {
|
|||||||
useMediaFiles: useMediaFiles;
|
useMediaFiles: useMediaFiles;
|
||||||
useMediaInsert: useMediaInsert;
|
useMediaInsert: useMediaInsert;
|
||||||
useUUID: useUUID;
|
useUUID: useUUID;
|
||||||
|
useData: useData;
|
||||||
useNavigate: useNavigate;
|
useNavigate: useNavigate;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user