Feature/update dnd (#190)

* Add prop types
* Remove react-sortable-hoc
This commit is contained in:
Daniel Lautzenheiser 2022-12-05 12:06:19 -05:00 committed by GitHub
parent b6c5791d6c
commit 0655f5c382
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 262 additions and 202 deletions

View File

@ -54,6 +54,9 @@
"@codemirror/state": "6.1.4",
"@codemirror/theme-one-dark": "6.1.0",
"@codemirror/view": "6.6.0",
"@dnd-kit/core": "6.0.5",
"@dnd-kit/sortable": "7.0.1",
"@dnd-kit/utilities": "3.2.0",
"@emotion/babel-preset-css-prop": "11.10.0",
"@emotion/css": "11.10.5",
"@emotion/react": "11.10.5",
@ -121,7 +124,6 @@
"react-redux": "8.0.5",
"react-router-dom": "6.4.4",
"react-scroll-sync": "0.11.0",
"react-sortable-hoc": "2.0.0",
"react-textarea-autosize": "8.4.0",
"react-topbar-progress-indicator": "4.1.1",
"react-virtualized-auto-sizer": "1.0.7",
@ -235,6 +237,7 @@
"postcss-scss": "4.0.6",
"prettier": "2.8.0",
"process": "0.11.10",
"prop-types": "15.8.1",
"react-refresh": "0.14.0",
"react-svg-loader": "3.0.3",
"rimraf": "3.0.2",

View File

@ -5,7 +5,6 @@ import { connect } from 'react-redux';
import { logoutUser as logoutUserAction } from '@staticcms/core/actions/auth';
import {
changeDraftFieldValidation as changeDraftFieldValidationAction,
createDraftDuplicateFromEntry as createDraftDuplicateFromEntryAction,
createEmptyDraft as createEmptyDraftAction,
deleteDraftLocalBackup as deleteDraftLocalBackupAction,
@ -385,7 +384,6 @@ const mapDispatchToProps = {
retrieveLocalBackup: retrieveLocalBackupAction,
persistLocalBackup: persistLocalBackupAction,
deleteLocalBackup: deleteLocalBackupAction,
changeDraftFieldValidation: changeDraftFieldValidationAction,
createDraftDuplicateFromEntry: createDraftDuplicateFromEntryAction,
createEmptyDraft: createEmptyDraftAction,
discardDraft: discardDraftAction,

View File

@ -7,7 +7,7 @@ import { connect } from 'react-redux';
import {
changeDraftField as changeDraftFieldAction,
changeDraftFieldValidation as changeDraftFieldValidationAction,
changeDraftFieldValidation,
} from '@staticcms/core/actions/entries';
import { getAsset as getAssetAction } from '@staticcms/core/actions/media';
import {
@ -20,10 +20,13 @@ import { query as queryAction } from '@staticcms/core/actions/search';
import { borders, colors, lengths, transitions } from '@staticcms/core/components/UI/styles';
import { transientOptions } from '@staticcms/core/lib';
import useMemoCompare from '@staticcms/core/lib/hooks/useMemoCompare';
import useUUID from '@staticcms/core/lib/hooks/useUUID';
import { resolveWidget } from '@staticcms/core/lib/registry';
import { getFieldLabel } from '@staticcms/core/lib/util/field.util';
import { validate } from '@staticcms/core/lib/util/validation.util';
import { selectFieldErrors } from '@staticcms/core/reducers/entryDraft';
import { selectIsLoadingAsset } from '@staticcms/core/reducers/medias';
import { useAppDispatch, useAppSelector } from '@staticcms/core/store/hooks';
import type {
Field,
@ -146,7 +149,6 @@ const EditorControl = ({
isHidden = false,
locale,
mediaPaths,
changeDraftFieldValidation,
openMediaLibrary,
parentPath,
query,
@ -158,6 +160,10 @@ const EditorControl = ({
changeDraftField,
i18n,
}: TranslatedProps<EditorControlProps>) => {
const dispatch = useAppDispatch();
const id = useUUID();
const widgetName = field.widget;
const widget = resolveWidget(widgetName) as Widget<ValueOrNestedValue>;
const fieldHint = field.hint;
@ -168,8 +174,10 @@ const EditorControl = ({
);
const [dirty, setDirty] = useState(!isEmpty(value));
// eslint-disable-next-line react-hooks/exhaustive-deps
const errors = useMemo(() => fieldsErrors[path] ?? [], [fieldsErrors[path]]);
const fieldErrorsSelector = useMemo(() => selectFieldErrors(path), [path]);
const errors = useAppSelector(fieldErrorsSelector);
const hasErrors = (submitted || dirty) && Boolean(errors.length);
const handleGetAsset: GetAssetFunction = useMemo(
@ -181,12 +189,17 @@ const EditorControl = ({
);
useEffect(() => {
if (!dirty && !submitted) {
return;
}
const validateValue = async () => {
await validate(path, field, value, widget, changeDraftFieldValidation, t);
const errors = await validate(field, value, widget, t);
dispatch(changeDraftFieldValidation(path, errors));
};
validateValue();
}, [field, value, changeDraftFieldValidation, path, t, widget, dirty]);
}, [dispatch, field, path, t, value, widget, dirty, submitted]);
const handleChangeDraftField = useCallback(
(value: ValueOrNestedValue) => {
@ -209,7 +222,7 @@ const EditorControl = ({
<ControlContainer $isHidden={isHidden}>
<>
{createElement(widget.control, {
key: `field_${path}`,
key: id,
collection,
config,
entry,
@ -318,7 +331,6 @@ function mapStateToProps(state: RootState, ownProps: EditorControlOwnProps) {
const mapDispatchToProps = {
changeDraftField: changeDraftFieldAction,
changeDraftFieldValidation: changeDraftFieldValidationAction,
openMediaLibrary: openMediaLibraryAction,
clearMediaControl: clearMediaControlAction,
removeMediaControl: removeMediaControlAction,

View File

@ -1,14 +1,15 @@
import { styled } from '@mui/material/styles';
import CloseIcon from '@mui/icons-material/Close';
import DragHandleIcon from '@mui/icons-material/DragHandle';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import IconButton from '@mui/material/IconButton';
import { styled } from '@mui/material/styles';
import React from 'react';
import { transientOptions } from '@staticcms/core/lib/util';
import { buttons, colors, lengths, transitions } from './styles';
import type { ComponentClass, MouseEvent, ReactNode } from 'react';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import type { MouseEvent, ReactNode } from 'react';
interface TopBarProps {
$isVariableTypesList: boolean;
@ -74,16 +75,15 @@ const DragIconContainer = styled(TopBarButtonSpan)`
`;
export interface DragHandleProps {
dragHandleHOC: (render: () => ReactNode) => ComponentClass;
listeners: SyntheticListenerMap | undefined;
}
const DragHandle = ({ dragHandleHOC }: DragHandleProps) => {
const Handle = dragHandleHOC(() => (
<DragIconContainer>
const DragHandle = ({ listeners }: DragHandleProps) => {
return (
<DragIconContainer {...listeners}>
<DragHandleIcon />
</DragIconContainer>
));
return <Handle />;
);
};
export interface ListItemTopBarProps {
@ -92,8 +92,8 @@ export interface ListItemTopBarProps {
collapsed?: boolean;
onCollapseToggle?: (event: MouseEvent) => void;
onRemove: (event: MouseEvent) => void;
dragHandleHOC: (render: () => ReactNode) => ComponentClass;
isVariableTypesList?: boolean;
listeners: SyntheticListenerMap | undefined;
}
const ListItemTopBar = ({
@ -102,8 +102,8 @@ const ListItemTopBar = ({
collapsed = false,
onCollapseToggle,
onRemove,
dragHandleHOC,
isVariableTypesList = false,
listeners,
}: ListItemTopBarProps) => {
return (
<TopBar className={className} $collapsed={collapsed} $isVariableTypesList={isVariableTypesList}>
@ -120,7 +120,7 @@ const ListItemTopBar = ({
<StyledTitle key="title" onClick={onCollapseToggle}>
{title}
</StyledTitle>
{dragHandleHOC ? <DragHandle dragHandleHOC={dragHandleHOC} /> : null}
{listeners ? <DragHandle listeners={listeners} /> : null}
{onRemove ? (
<TopBarButton onClick={onRemove}>
<CloseIcon />

View File

@ -0,0 +1,6 @@
import { useMemo } from 'react';
import { v4 as uuid } from 'uuid';
export default function useUUID() {
return useMemo(() => uuid(), []);
}

View File

@ -78,11 +78,9 @@ export function validatePattern({
}
export async function validate(
path: string,
field: Field,
value: ValueOrNestedValue,
widget: Widget<any, any>,
onValidate: (path: string, errors: FieldError[]) => void,
t: t,
): Promise<FieldError[]> {
const validValue = widget.getValidValue(value);
@ -100,6 +98,5 @@ export async function validate(
}
}
onValidate(path, errors);
return errors;
}

View File

@ -24,6 +24,7 @@ import { set } from '../lib/util/object.util';
import type { EntriesAction } from '../actions/entries';
import type { Entry, FieldsErrors } from '../interface';
import type { RootState } from '../store';
export interface EntryDraftState {
original?: Entry;
@ -300,3 +301,7 @@ function entryDraftReducer(
}
export default entryDraftReducer;
export const selectFieldErrors = (path: string) => (state: RootState) => {
return state.entryDraft.fieldsErrors[path] ?? [];
};

View File

@ -2,10 +2,10 @@ import { styled } from '@mui/material/styles';
import { loadLanguage } from '@uiw/codemirror-extensions-langs';
import CodeMirror from '@uiw/react-codemirror';
import React, { useCallback, useMemo, useState } from 'react';
import { v4 as uuid } from 'uuid';
import ObjectWidgetTopBar from '@staticcms/core/components/UI/ObjectWidgetTopBar';
import Outline from '@staticcms/core/components/UI/Outline';
import useUUID from '@staticcms/core/lib/hooks/useUUID';
import transientOptions from '@staticcms/core/lib/util/transientOptions';
import languages from './data/languages';
import SettingsButton from './SettingsButton';
@ -146,7 +146,7 @@ const CodeControl: FC<WidgetControlProps<string | { [key: string]: string }, Cod
setSettingsVisible(false);
}, []);
const uniqueId = useMemo(() => uuid(), []);
const uniqueId = useUUID();
// If `allow_language_selection` is not set, default to true. Otherwise, use its value.
const allowLanguageSelection = useMemo(

View File

@ -3,15 +3,13 @@ import PhotoIcon from '@mui/icons-material/Photo';
import Button from '@mui/material/Button';
import IconButton from '@mui/material/IconButton';
import { styled } from '@mui/material/styles';
import { arrayMoveImmutable } from 'array-move';
import React, { memo, useCallback, useEffect, useMemo, useState } from 'react';
import { SortableContainer, SortableElement } from 'react-sortable-hoc';
import { v4 as uuid } from 'uuid';
import ObjectWidgetTopBar from '@staticcms/core/components/UI/ObjectWidgetTopBar';
import Outline from '@staticcms/core/components/UI/Outline';
import { borders, effects, lengths, shadows } from '@staticcms/core/components/UI/styles';
import useMediaInsert from '@staticcms/core/lib/hooks/useMediaInsert';
import useUUID from '@staticcms/core/lib/hooks/useUUID';
import { basename, transientOptions } from '@staticcms/core/lib/util';
import { isEmpty } from '@staticcms/core/lib/util/string.util';
@ -137,67 +135,43 @@ interface SortableImageProps {
onReplace: MouseEventHandler;
}
const SortableImage = SortableElement<SortableImageProps>(
({ itemValue, getAsset, field, onRemove, onReplace }: SortableImageProps) => {
const [assetSource, setAssetSource] = useState('');
useEffect(() => {
const getImage = async () => {
const asset = (await getAsset(itemValue, field))?.toString() ?? '';
setAssetSource(asset);
};
const SortableImage: FC<SortableImageProps> = ({
itemValue,
getAsset,
field,
onRemove,
onReplace,
}: SortableImageProps) => {
const [assetSource, setAssetSource] = useState('');
useEffect(() => {
const getImage = async () => {
const asset = (await getAsset(itemValue, field))?.toString() ?? '';
setAssetSource(asset);
};
getImage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemValue]);
getImage();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [itemValue]);
return (
<div>
<ImageWrapper key="image-wrapper" $sortable>
<Image key="image" src={assetSource} />
</ImageWrapper>
<SortableImageButtons
key="image-buttons"
onRemove={onRemove}
onReplace={onReplace}
></SortableImageButtons>
</div>
);
},
);
return (
<div>
<ImageWrapper key="image-wrapper" $sortable>
<Image key="image" src={assetSource} />
</ImageWrapper>
<SortableImageButtons
key="image-buttons"
onRemove={onRemove}
onReplace={onReplace}
></SortableImageButtons>
</div>
);
};
const StyledSortableMultiImageWrapper = styled('div')`
const StyledMultiImageWrapper = styled('div')`
display: flex;
flex-wrap: wrap;
`;
interface SortableMultiImageWrapperProps {
items: string[];
getAsset: GetAssetFunction<FileOrImageField>;
field: FileOrImageField;
onRemoveOne: (index: number) => MouseEventHandler;
onReplaceOne: (index: number) => MouseEventHandler;
}
const SortableMultiImageWrapper = SortableContainer<SortableMultiImageWrapperProps>(
({ items, getAsset, field, onRemoveOne, onReplaceOne }: SortableMultiImageWrapperProps) => {
return (
<StyledSortableMultiImageWrapper key="multi-image-wrapper">
{items.map((itemValue, index) => (
<SortableImage
key={`item-${itemValue}`}
index={index}
itemValue={itemValue}
getAsset={getAsset}
field={field}
onRemove={onRemoveOne(index)}
onReplace={onReplaceOne(index)}
/>
))}
</StyledSortableMultiImageWrapper>
);
},
);
const FileLink = styled('a')`
margin-bottom: 20px;
font-weight: normal;
@ -247,7 +221,7 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
hasErrors,
t,
}) => {
const controlID = useMemo(() => uuid(), []);
const controlID = useUUID();
const [collapsed, setCollapsed] = useState(false);
const [internalValue, setInternalValue] = useState(value ?? '');
@ -345,15 +319,16 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
[config, controlID, field, openMediaLibrary, internalValue],
);
const onSortEnd = useCallback(
({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
if (Array.isArray(internalValue)) {
const newValue = arrayMoveImmutable(internalValue, oldIndex, newIndex);
handleOnChange(newValue);
}
},
[handleOnChange, internalValue],
);
// TODO Readd when multiple uploads is supported
// const onSortEnd = useCallback(
// ({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
// if (Array.isArray(internalValue)) {
// const newValue = arrayMoveImmutable(internalValue, oldIndex, newIndex);
// handleOnChange(newValue);
// }
// },
// [handleOnChange, internalValue],
// );
const renderFileLink = useCallback((link: string | undefined | null) => {
const size = MAX_DISPLAY_LENGTH;
@ -393,18 +368,18 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
if (isMultiple(internalValue)) {
return (
<SortableMultiImageWrapper
key="mulitple-image-wrapper"
items={internalValue}
onSortEnd={onSortEnd}
onRemoveOne={onRemoveOne}
onReplaceOne={onReplaceOne}
distance={4}
getAsset={getAsset}
field={field}
axis="xy"
lockToContainerEdges={true}
></SortableMultiImageWrapper>
<StyledMultiImageWrapper key="multi-image-wrapper">
{internalValue.map((itemValue, index) => (
<SortableImage
key={`item-${itemValue}`}
itemValue={itemValue}
getAsset={getAsset}
field={field}
onRemove={onRemoveOne(index)}
onReplace={onReplaceOne(index)}
/>
))}
</StyledMultiImageWrapper>
);
}
@ -428,16 +403,7 @@ const withFileControl = ({ forImage = false }: WithFileControlProps = {}) => {
}
return <FileLinks key="single-file-links">{renderFileLink(internalValue)}</FileLinks>;
}, [
assetSource,
field,
getAsset,
internalValue,
onRemoveOne,
onReplaceOne,
onSortEnd,
renderFileLink,
]);
}, [assetSource, field, getAsset, internalValue, onRemoveOne, onReplaceOne, renderFileLink]);
const content = useMemo(() => {
const subject = forImage ? 'image' : 'file';

View File

@ -1,8 +1,10 @@
import { DndContext } from '@dnd-kit/core';
import { SortableContext, useSortable } from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { styled } from '@mui/material/styles';
import { arrayMoveImmutable } from 'array-move';
import isEmpty from 'lodash/isEmpty';
import React, { useCallback, useMemo, useState } from 'react';
import { SortableContainer } from 'react-sortable-hoc';
import { v4 as uuid } from 'uuid';
import FieldLabel from '@staticcms/core/components/UI/FieldLabel';
@ -12,10 +14,15 @@ import transientOptions from '@staticcms/core/lib/util/transientOptions';
import ListItem from './ListItem';
import { resolveFieldKeyType, TYPES_KEY } from './typedListHelpers';
import type { DragEndEvent } from '@dnd-kit/core';
import type {
Entry,
Field,
FieldsErrors,
I18nSettings,
ListField,
ObjectValue,
UnknownField,
ValueOrNestedValue,
WidgetControlProps,
} from '@staticcms/core/interface';
@ -51,17 +58,77 @@ const StyledSortableList = styled(
`,
);
interface SortableListProps {
items: ObjectValue[];
collapsed: boolean;
renderItem: (item: ObjectValue, index: number) => JSX.Element;
interface SortableItemProps {
id: string;
item: ObjectValue;
index: number;
valueType: ListValueType;
handleRemove: (index: number, event: MouseEvent) => void;
entry: Entry<ObjectValue>;
field: ListField;
fieldsErrors: FieldsErrors;
submitted: boolean;
isFieldDuplicate: ((field: Field<UnknownField>) => boolean) | undefined;
isFieldHidden: ((field: Field<UnknownField>) => boolean) | undefined;
locale: string | undefined;
path: string;
value: Record<string, ObjectValue>;
i18n: I18nSettings | undefined;
}
const SortableList = SortableContainer<SortableListProps>(
({ items, collapsed, renderItem }: SortableListProps) => {
return <StyledSortableList $collapsed={collapsed}>{items.map(renderItem)}</StyledSortableList>;
},
);
const SortableItem: FC<SortableItemProps> = ({
id,
item,
index,
valueType,
handleRemove,
entry,
field,
fieldsErrors,
submitted,
isFieldDuplicate,
isFieldHidden,
locale,
path,
i18n,
}) => {
const { attributes, listeners, setNodeRef, transform, transition } = useSortable({
id,
});
const style = {
transform: CSS.Transform.toString(transform),
transition,
};
if (valueType === null) {
return <div key={id} />;
}
return (
<div ref={setNodeRef} style={style} {...attributes}>
<ListItem
index={index}
id={id}
key={`sortable-item-${id}`}
valueType={valueType}
handleRemove={handleRemove}
data-testid={`object-control-${index}`}
entry={entry}
field={field}
fieldsErrors={fieldsErrors}
submitted={submitted}
isFieldDuplicate={isFieldDuplicate}
isFieldHidden={isFieldHidden}
locale={locale}
path={path}
value={item as Record<string, ObjectValue>}
i18n={i18n}
listeners={listeners}
/>
</div>
);
};
export enum ListValueType {
MULTIPLE,
@ -200,8 +267,15 @@ const ListControl: FC<WidgetControlProps<ObjectValue[], ListField>> = ({
[collapsed],
);
const onSortEnd = useCallback(
({ oldIndex, newIndex }: { oldIndex: number; newIndex: number }) => {
const handleDragEnd = useCallback(
({ active, over }: DragEndEvent) => {
if (!over || active.id === over.id) {
return;
}
const oldIndex = keys.indexOf(active.id as string);
const newIndex = keys.indexOf(over.id as string);
// Update value
setKeys(arrayMoveImmutable(keys, oldIndex, newIndex));
onChange(arrayMoveImmutable(internalValue, oldIndex, newIndex));
@ -209,49 +283,6 @@ const ListControl: FC<WidgetControlProps<ObjectValue[], ListField>> = ({
[onChange, internalValue, keys],
);
const renderItem = useCallback(
(item: ObjectValue, index: number) => {
const key = keys[index];
if (valueType === null) {
return <div key={key} />;
}
return (
<ListItem
index={index}
key={key}
valueType={valueType}
handleRemove={handleRemove}
data-testid={`object-control-${index}`}
entry={entry}
field={field}
fieldsErrors={fieldsErrors}
submitted={submitted}
isFieldDuplicate={isFieldDuplicate}
isFieldHidden={isFieldHidden}
locale={locale}
path={path}
value={item as Record<string, ObjectValue>}
i18n={i18n}
/>
);
},
[
keys,
valueType,
handleRemove,
entry,
field,
fieldsErrors,
submitted,
isFieldDuplicate,
isFieldHidden,
locale,
path,
i18n,
],
);
if (valueType === null) {
return null;
}
@ -277,15 +308,33 @@ const ListControl: FC<WidgetControlProps<ObjectValue[], ListField>> = ({
t={t}
/>
{internalValue.length > 0 ? (
<SortableList
key="sortable-list"
collapsed={collapsed}
items={internalValue}
renderItem={renderItem}
onSortEnd={onSortEnd}
useDragHandle
lockAxis="y"
/>
<DndContext key="dnd-context" onDragEnd={handleDragEnd}>
<SortableContext items={keys}>
<StyledSortableList $collapsed={collapsed}>
{internalValue.map((item, index) => (
<SortableItem
index={index}
key={keys[index]}
id={keys[index]}
item={item}
valueType={valueType}
handleRemove={handleRemove}
data-testid={`object-control-${index}`}
entry={entry}
field={field}
fieldsErrors={fieldsErrors}
submitted={submitted}
isFieldDuplicate={isFieldDuplicate}
isFieldHidden={isFieldHidden}
locale={locale}
path={path}
value={item as Record<string, ObjectValue>}
i18n={i18n}
/>
))}
</StyledSortableList>
</SortableContext>
</DndContext>
) : null}
<Outline key="outline" hasLabel hasError={hasErrors} />
</StyledListWrapper>

View File

@ -1,7 +1,6 @@
import { styled } from '@mui/material/styles';
import partial from 'lodash/partial';
import React, { useCallback, useMemo, useState } from 'react';
import { SortableElement, SortableHandle } from 'react-sortable-hoc';
import EditorControl from '@staticcms/core/components/Editor/EditorControlPane/EditorControl';
import ListItemTopBar from '@staticcms/core/components/UI/ListItemTopBar';
@ -15,6 +14,7 @@ import {
import { ListValueType } from './ListControl';
import { getTypedFieldForValue } from './typedListHelpers';
import type { SyntheticListenerMap } from '@dnd-kit/core/dist/hooks/utilities';
import type {
Entry,
EntryData,
@ -29,8 +29,6 @@ const StyledListItem = styled('div')`
position: relative;
`;
const SortableStyledListItem = SortableElement<{ children: JSX.Element }>(StyledListItem);
const StyledListItemTopBar = styled(ListItemTopBar)`
background-color: ${colors.textFieldBorder};
`;
@ -95,10 +93,13 @@ interface ListItemProps
> {
valueType: ListValueType;
index: number;
id: string;
listeners: SyntheticListenerMap | undefined;
handleRemove: (index: number, event: MouseEvent) => void;
}
const ListItem: FC<ListItemProps> = ({
id,
index,
entry,
field,
@ -112,6 +113,7 @@ const ListItem: FC<ListItemProps> = ({
handleRemove,
value,
i18n,
listeners,
}) => {
const [objectLabel, objectField] = useMemo((): [string, ListField | ObjectField] => {
const childObjectField: ObjectField = {
@ -185,21 +187,21 @@ const ListItem: FC<ListItemProps> = ({
const isHidden = isFieldHidden && isFieldHidden(field);
return (
<SortableStyledListItem key="sortable-list-item" index={index}>
<StyledListItem key="sortable-list-item">
<>
<StyledListItemTopBar
key="list-item-top-bar"
collapsed={collapsed}
onCollapseToggle={handleCollapseToggle}
onRemove={partial(handleRemove, index)}
dragHandleHOC={SortableHandle}
data-testid={`styled-list-item-top-bar-${index}`}
data-testid={`styled-list-item-top-bar-${id}`}
title={objectLabel}
isVariableTypesList={valueType === ListValueType.MIXED}
listeners={listeners}
/>
<StyledObjectFieldWrapper $collapsed={collapsed}>
<EditorControl
key={index}
key={`control-${id}`}
field={objectField}
value={value}
fieldsErrors={fieldsErrors}
@ -216,7 +218,7 @@ const ListItem: FC<ListItemProps> = ({
</StyledObjectFieldWrapper>
<Outline key="outline" />
</>
</SortableStyledListItem>
</StyledListItem>
);
};

View File

@ -2,9 +2,9 @@ import { styled } from '@mui/material/styles';
import { findNodePath, setNodes } from '@udecode/plate';
import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import Frame from 'react-frame-component';
import { v4 as uuid } from 'uuid';
import Outline from '@staticcms/core/components/UI/Outline';
import useUUID from '@staticcms/core/lib/hooks/useUUID';
import { useWindowEvent } from '@staticcms/core/lib/util/window.util';
import CodeBlockFrame from './CodeBlockFrame';
@ -49,7 +49,7 @@ const CodeBlockElement: FC<PlateRenderElementProps<MdValue, MdCodeBlockElement>>
const [codeHasFocus, setCodeHasFocus] = useState(false);
const { attributes, nodeProps, element, editor, children } = props;
const id = useMemo(() => uuid(), []);
const id = useUUID();
const lang = ('lang' in element ? element.lang : '') as string | undefined;
const code = ('code' in element ? element.code ?? '' : '') as string;

View File

@ -990,7 +990,7 @@
"@babel/helper-validator-option" "^7.18.6"
"@babel/plugin-transform-typescript" "^7.18.6"
"@babel/runtime@7.20.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0", "@babel/runtime@^7.2.0", "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
"@babel/runtime@7.20.6", "@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.14.8", "@babel/runtime@^7.18.3", "@babel/runtime@^7.18.6", "@babel/runtime@^7.18.9", "@babel/runtime@^7.19.0", "@babel/runtime@^7.20.1", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
version "7.20.6"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.6.tgz#facf4879bfed9b5326326273a64220f099b0fce3"
integrity sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==
@ -1319,6 +1319,37 @@
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@dnd-kit/accessibility@^3.0.0":
version "3.0.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/accessibility/-/accessibility-3.0.1.tgz#3ccbefdfca595b0a23a5dc57d3de96bc6935641c"
integrity sha512-HXRrwS9YUYQO9lFRc/49uO/VICbM+O+ZRpFDe9Pd1rwVv2PCNkRiTZRdxrDgng/UkvdC3Re9r2vwPpXXrWeFzg==
dependencies:
tslib "^2.0.0"
"@dnd-kit/core@6.0.5":
version "6.0.5"
resolved "https://registry.yarnpkg.com/@dnd-kit/core/-/core-6.0.5.tgz#5670ad0dcc83cd51dbf2fa8c6a5c8af4ac0c1989"
integrity sha512-3nL+Zy5cT+1XwsWdlXIvGIFvbuocMyB4NBxTN74DeBaBqeWdH9JsnKwQv7buZQgAHmAH+eIENfS1ginkvW6bCw==
dependencies:
"@dnd-kit/accessibility" "^3.0.0"
"@dnd-kit/utilities" "^3.2.0"
tslib "^2.0.0"
"@dnd-kit/sortable@7.0.1":
version "7.0.1"
resolved "https://registry.yarnpkg.com/@dnd-kit/sortable/-/sortable-7.0.1.tgz#99c6012bbab4d8bb726c0eef7b921a338c404fdb"
integrity sha512-n77qAzJQtMMywu25sJzhz3gsHnDOUlEjTtnRl8A87rWIhnu32zuP+7zmFjwGgvqfXmRufqiHOSlH7JPC/tnJ8Q==
dependencies:
"@dnd-kit/utilities" "^3.2.0"
tslib "^2.0.0"
"@dnd-kit/utilities@3.2.0", "@dnd-kit/utilities@^3.2.0":
version "3.2.0"
resolved "https://registry.yarnpkg.com/@dnd-kit/utilities/-/utilities-3.2.0.tgz#b3e956ea63a1347c9d0e1316b037ddcc6140acda"
integrity sha512-h65/pn2IPCCIWwdlR2BMLqRkDxpTEONA+HQW3n765HBijLYGyrnTCLa2YQt8VVjjSQD6EfFlTE6aS2Q/b6nb2g==
dependencies:
tslib "^2.0.0"
"@emoji-mart/data@^1.0.8":
version "1.0.8"
resolved "https://registry.yarnpkg.com/@emoji-mart/data/-/data-1.0.8.tgz#07f9603878b9a813ba16a6ebbabd8515f3d1b91d"
@ -6731,7 +6762,7 @@ interpret@^3.1.1:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-3.1.1.tgz#5be0ceed67ca79c6c4bc5cf0d7ee843dcea110c4"
integrity sha512-6xwYfHbajpoF0xLW+iwLkhwgvLoZDfjYfoFNu8ftMoXINzwuymNLd9u/KmwtdT2GbR+/Cz66otEGEVVUHX9QLQ==
invariant@^2.2.2, invariant@^2.2.4:
invariant@^2.2.2:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==
@ -9270,7 +9301,7 @@ prompts@^2.0.1:
kleur "^3.0.3"
sisteransi "^1.0.5"
prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
prop-types@15.8.1, prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.2, prop-types@^15.7.2, prop-types@^15.8.1:
version "15.8.1"
resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5"
integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==
@ -9517,15 +9548,6 @@ react-scroll-sync@0.11.0:
dependencies:
prop-types "^15.5.7"
react-sortable-hoc@2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-sortable-hoc/-/react-sortable-hoc-2.0.0.tgz#f6780d8aa4b922a21f3e754af542f032677078b7"
integrity sha512-JZUw7hBsAHXK7PTyErJyI7SopSBFRcFHDjWW5SWjcugY0i6iH7f+eJkY8cJmGMlZ1C9xz1J3Vjz0plFpavVeRg==
dependencies:
"@babel/runtime" "^7.2.0"
invariant "^2.2.4"
prop-types "^15.5.7"
react-svg-core@^3.0.3:
version "3.0.3"
resolved "https://registry.yarnpkg.com/react-svg-core/-/react-svg-core-3.0.3.tgz#5d856efeaa4d089b0afeebe885b20b8c9500d162"