diff --git a/packages/editor/src/editor/canvas/ComponentBlock.tsx b/packages/editor/src/editor/canvas/ComponentBlock.tsx index a9f81e81..2eeae3a9 100644 --- a/packages/editor/src/editor/canvas/ComponentBlock.tsx +++ b/packages/editor/src/editor/canvas/ComponentBlock.tsx @@ -20,7 +20,7 @@ type ComponentBlockProps = Omit & { }; export const ComponentBlock = ({ component, preId, ...props }: ComponentBlockProps) => ( - + ); diff --git a/packages/editor/src/editor/canvas/DropZone.tsx b/packages/editor/src/editor/canvas/DropZone.tsx index ad3809ca..ff411a30 100644 --- a/packages/editor/src/editor/canvas/DropZone.tsx +++ b/packages/editor/src/editor/canvas/DropZone.tsx @@ -3,16 +3,17 @@ import './DropZone.css'; import type { ComponentProps } from 'react'; import { isDropZoneDisabled } from './drag-data'; import { cn } from '@axonivy/ui-components'; +import type { ComponentType } from '@axonivy/form-editor-protocol'; export type DropZoneProps = ComponentProps<'div'> & { id: string; + type?: ComponentType; preId?: string; - dragHint?: boolean; }; -export const DropZone = ({ id, preId, className, children }: DropZoneProps) => { +export const DropZone = ({ id, type, preId, className, children }: DropZoneProps) => { const dnd = useDndContext(); - const { isOver, setNodeRef } = useDroppable({ id, disabled: isDropZoneDisabled(id, dnd.active, preId) }); + const { isOver, setNodeRef } = useDroppable({ id, disabled: isDropZoneDisabled(id, type, dnd.active, preId) }); return (
diff --git a/packages/editor/src/editor/canvas/drag-data.test.ts b/packages/editor/src/editor/canvas/drag-data.test.ts index b49396f2..3cf48002 100644 --- a/packages/editor/src/editor/canvas/drag-data.test.ts +++ b/packages/editor/src/editor/canvas/drag-data.test.ts @@ -2,46 +2,72 @@ import type { FormData } from '@axonivy/form-editor-protocol'; import { dragData, isDropZoneDisabled, type DragData } from './drag-data'; import type { Active } from '@dnd-kit/core'; import type { DeepPartial } from '../../types/types'; +import { STRUCTURE_DROPZONE_ID_PREFIX, TABLE_DROPZONE_ID_PREFIX } from '../../data/data'; describe('dragData', () => { test('normal', () => { const data = filledData(); - expectDragData(dragData(data.components[0]), []); + expect(dragData(data.components[0])).toEqual({ componentType: 'Input', disabledIds: [] }); }); test('layout', () => { const data = filledData(); - expectDragData(dragData(data.components[2]), ['31', '32', '33']); + expect(dragData(data.components[2])).toEqual({ componentType: 'Layout', disabledIds: ['31', '32', '33'] }); }); test('fieldset', () => { const data = filledData(); - expectDragData(dragData(data.components[3]), ['41', '42', '43']); + expect(dragData(data.components[3])).toEqual({ componentType: 'Fieldset', disabledIds: ['41', '42', '43'] }); }); test('panel', () => { const data = filledData(); - expectDragData(dragData(data.components[4]), ['51', '52', '53']); + expect(dragData(data.components[4])).toEqual({ componentType: 'Panel', disabledIds: ['51', '52', '53'] }); }); }); -describe('isDragZoneDisabled', () => { - test('false', () => { +describe('isDropZoneDisabled', () => { + test('inactive draggable', () => { + expect(isDropZoneDisabled('1', undefined, null)).toBeFalsy(); + expect(isDropZoneDisabled('1', undefined, undefined)).toBeFalsy(); + }); + + test('drop zone from draggable', () => { const active: Partial = { id: '1', data: { current: undefined } }; - expect(isDropZoneDisabled('', active as Active)).toBeFalsy(); - expect(isDropZoneDisabled('2', active as Active)).toBeFalsy(); + expect(isDropZoneDisabled('1', undefined, active as Active)).toBeTruthy(); }); - test('true', () => { + test('drop zone from pre element', () => { const active: Partial = { id: '1', data: { current: undefined } }; - expect(isDropZoneDisabled('1', active as Active)).toBeTruthy(); - expect(isDropZoneDisabled('2', active as Active, '1')).toBeTruthy(); + expect(isDropZoneDisabled('2', undefined, active as Active, '1')).toBeTruthy(); }); -}); -const expectDragData = (data: DragData, ids: Array) => { - expect(data).to.deep.equals({ disabledIds: ids }); -}; + test('empty drop zone from structure element', () => { + const active: Partial = { id: '1', data: { current: undefined } }; + expect(isDropZoneDisabled(`${STRUCTURE_DROPZONE_ID_PREFIX}1`, undefined, active as Active)).toBeTruthy(); + }); + + test('empty drop zone from table element', () => { + const active: Partial = { id: '1', data: { current: undefined } }; + expect(isDropZoneDisabled(`${TABLE_DROPZONE_ID_PREFIX}1`, undefined, active as Active)).toBeTruthy(); + }); + + test('disable all children for structure', () => { + const active: Partial = { id: '0', data: { current: { componentType: 'Layout', disabledIds: ['2', '3'] } } }; + expect(isDropZoneDisabled('1', undefined, active as Active)).toBeFalsy(); + expect(isDropZoneDisabled('2', undefined, active as Active)).toBeTruthy(); + expect(isDropZoneDisabled('3', undefined, active as Active)).toBeTruthy(); + }); + + test('disable columns', () => { + let active: Partial = { id: '0', data: { current: { componentType: 'Input', disabledIds: [] } } }; + expect(isDropZoneDisabled('1', 'Input', active as Active)).toBeFalsy(); + expect(isDropZoneDisabled('2', 'DataTableColumn', active as Active)).toBeTruthy(); + active = { id: '0', data: { current: { componentType: 'DataTableColumn', disabledIds: [] } } }; + expect(isDropZoneDisabled('1', 'Input', active as Active)).toBeTruthy(); + expect(isDropZoneDisabled('2', 'DataTableColumn', active as Active)).toBeFalsy(); + }); +}); const filledData = () => { const prefilledData: DeepPartial = { diff --git a/packages/editor/src/editor/canvas/drag-data.ts b/packages/editor/src/editor/canvas/drag-data.ts index 521df1bf..c2df1bd8 100644 --- a/packages/editor/src/editor/canvas/drag-data.ts +++ b/packages/editor/src/editor/canvas/drag-data.ts @@ -1,8 +1,11 @@ -import { isStructure, isTable, type Component, type ComponentData } from '@axonivy/form-editor-protocol'; +import { isStructure, isTable, type Component, type ComponentData, type ComponentType } from '@axonivy/form-editor-protocol'; import type { Active } from '@dnd-kit/core'; import { STRUCTURE_DROPZONE_ID_PREFIX, TABLE_DROPZONE_ID_PREFIX } from '../../data/data'; -export type DragData = { disabledIds: Array }; +export type DragData = { + componentType: ComponentType; + disabledIds: Array; +}; const disabledIds = (data: Component | ComponentData): Array => { if (isStructure(data) || isTable(data)) { @@ -17,40 +20,45 @@ const disabledIds = (data: Component | ComponentData): Array => { }; export const dragData = (data: Component | ComponentData): DragData => { - return { disabledIds: disabledIds(data) }; + return { componentType: data.type, disabledIds: disabledIds(data) }; }; export const isDragData = (data: unknown): data is DragData => { - return typeof data === 'object' && data !== null && 'disabledIds' in data; + return typeof data === 'object' && data !== null && 'componentType' in data && 'disabledIds' in data; }; -export const isDropZoneDisabled = (id: string, active: Active | null, preId?: string) => { - const dropZoneId = active?.id; - - if (disableDropZoneDataTableColumn(id, dropZoneId?.toString())) { - return true; +export const isDropZoneDisabled = (dropId: string, dropType?: ComponentType, active?: Active | null, preDropId?: string) => { + if (active === undefined || active === null) { + return false; } + const dragId = active.id; if ( - dropZoneId === id || - `${STRUCTURE_DROPZONE_ID_PREFIX}${dropZoneId}` === id || - `${TABLE_DROPZONE_ID_PREFIX}${dropZoneId}` === id || - dropZoneId === preId + dragId === dropId || + `${STRUCTURE_DROPZONE_ID_PREFIX}${dragId}` === dropId || + `${TABLE_DROPZONE_ID_PREFIX}${dragId}` === dropId || + dragId === preDropId ) { return true; } - const data = active?.data.current; + + const data = active.data.current; if (isDragData(data)) { - return data.disabledIds.includes(id); + if (disableDataTableColumnDropZone(dropType, data.componentType)) { + return true; + } + return data.disabledIds.includes(dropId); } return false; }; -const disableDropZoneDataTableColumn = (id: string, dropZoneId: string | undefined) => { - const isColumn = id.startsWith('DataTableColumn'); - const isColumnTarget = dropZoneId ? dropZoneId.includes('DataTableColumn') : false; - +const disableDataTableColumnDropZone = (dropType: ComponentType | undefined, dragType: ComponentType) => { + const isColumn = dragType === 'DataTableColumn'; + const isColumnTarget = dropType === 'DataTableColumn'; if (isColumn) { return !isColumnTarget; } - return isColumnTarget; + if (isColumnTarget) { + return !isColumn; + } + return false; };