diff --git a/packages/react-core/src/components/DataList/DataList.tsx b/packages/react-core/src/components/DataList/DataList.tsx index 04e6bc070b3..0205da4e6ca 100644 --- a/packages/react-core/src/components/DataList/DataList.tsx +++ b/packages/react-core/src/components/DataList/DataList.tsx @@ -25,7 +25,7 @@ export interface SelectableRowObject { onChange: (id: string, event: React.FormEvent) => void; } -export interface DataListProps extends Omit, 'onDragStart' | 'ref'> { +export interface DataListProps extends Omit, 'ref'> { /** Content rendered inside the DataList list */ children?: React.ReactNode; /** Additional classes added to the DataList list */ @@ -34,14 +34,6 @@ export interface DataListProps extends Omit, ' 'aria-label': string; /** Optional callback to make DataList selectable, fired when DataListItem selected */ onSelectDataListItem?: (id: string) => void; - /** @deprecated Optional callback to make DataList draggable, fired when dragging ends */ - onDragFinish?: (newItemOrder: string[]) => void; - /** @deprecated Optional informational callback for dragging, fired when dragging starts */ - onDragStart?: (id: string) => void; - /** @deprecated Optional informational callback for dragging, fired when an item moves */ - onDragMove?: (oldIndex: number, newIndex: number) => void; - /** @deprecated Optional informational callback for dragging, fired when dragging is cancelled */ - onDragCancel?: () => void; /** Id of DataList item currently selected */ selectedDataListItemId?: string; /** Flag indicating if DataList should have compact styling */ @@ -50,47 +42,22 @@ export interface DataListProps extends Omit, ' gridBreakpoint?: 'none' | 'always' | 'sm' | 'md' | 'lg' | 'xl' | '2xl'; /** Determines which wrapping modifier to apply to the DataList */ wrapModifier?: DataListWrapModifier | 'nowrap' | 'truncate' | 'breakWord'; - /** @deprecated Order of items in a draggable DataList */ - itemOrder?: string[]; /** Object that causes the data list to render hidden inputs which improve selectable item a11y */ selectableRow?: SelectableRowObject; } -interface DataListState { - draggedItemId: string; - draggingToItemIndex: number; - dragging: boolean; - tempItemOrder: string[]; -} - interface DataListContextProps { isSelectable: boolean; selectedDataListItemId: string; updateSelectedDataListItem: (id: string) => void; selectableRow?: SelectableRowObject; - isDraggable: boolean; - dragStart: (e: React.DragEvent) => void; - dragEnd: (e: React.DragEvent) => void; - drop: (e: React.DragEvent) => void; - dragKeyHandler: (e: React.KeyboardEvent) => void; } export const DataListContext = React.createContext>({ isSelectable: false }); -const moveItem = (arr: string[], i1: string, toIndex: number) => { - const fromIndex = arr.indexOf(i1); - if (fromIndex === toIndex) { - return arr; - } - const temp = arr.splice(fromIndex, 1); - arr.splice(toIndex, 0, temp[0]); - - return arr; -}; - -export class DataList extends React.Component { +export class DataList extends React.Component { static displayName = 'DataList'; static defaultProps: PickOptional = { children: null, @@ -100,207 +67,14 @@ export class DataList extends React.Component { gridBreakpoint: 'md', wrapModifier: null }; - dragFinished: boolean = false; - html5DragDrop: boolean = false; - arrayCopy: React.ReactElement[] = React.Children.toArray(this.props.children) as React.ReactElement[]; ref = React.createRef(); - state: DataListState = { - tempItemOrder: [], - draggedItemId: null, - draggingToItemIndex: null, - dragging: false - }; - constructor(props: DataListProps) { super(props); - - this.html5DragDrop = Boolean(props.onDragFinish || props.onDragStart || props.onDragMove || props.onDragCancel); - if (this.html5DragDrop) { - // eslint-disable-next-line no-console - console.warn("DataList's onDrag API is deprecated. Use DragDrop instead."); - } - } - - componentDidUpdate(oldProps: DataListProps) { - if (this.dragFinished) { - this.dragFinished = false; - - this.setState({ - tempItemOrder: [...this.props.itemOrder], - draggedItemId: null, - dragging: false - }); - } - if (oldProps.itemOrder !== this.props.itemOrder) { - this.move(this.props.itemOrder); - } } getIndex = (id: string) => Array.from(this.ref.current.children).findIndex(item => item.id === id); - move = (itemOrder: string[]) => { - const ulNode = this.ref.current; - const nodes = Array.from(ulNode.children); - if (nodes.map(node => node.id).every((id, i) => id === itemOrder[i])) { - return; - } - while (ulNode.firstChild) { - ulNode.removeChild(ulNode.lastChild); - } - - itemOrder.forEach(id => { - ulNode.appendChild(nodes.find(n => n.id === id)); - }); - }; - - dragStart0 = (el: HTMLElement) => { - const { onDragStart } = this.props; - const draggedItemId = el.id; - - el.classList.add(styles.modifiers.ghostRow); - el.setAttribute('aria-pressed', 'true'); - this.setState({ - draggedItemId, - dragging: true - }); - onDragStart && onDragStart(draggedItemId); - }; - - dragStart = (evt: React.DragEvent) => { - evt.dataTransfer.effectAllowed = 'move'; - evt.dataTransfer.setData('text/plain', evt.currentTarget.id); - this.dragStart0(evt.currentTarget as HTMLElement); - }; - - onDragCancel = () => { - this.move(this.props.itemOrder); - Array.from(this.ref.current.children).forEach(el => { - el.classList.remove(styles.modifiers.ghostRow); - el.classList.remove(styles.modifiers.dragOver); - el.setAttribute('aria-pressed', 'false'); - }); - this.setState({ - draggedItemId: null, - draggingToItemIndex: null, - dragging: false - }); - - if (this.props.onDragCancel) { - this.props.onDragCancel(); - } - }; - - dragLeave = (evt: React.DragEvent) => { - // This event false fires when we call `this.move()`, so double check we're out of zone - if (!this.isValidDrop(evt)) { - this.move(this.props.itemOrder); - this.setState({ - draggingToItemIndex: null - }); - } - }; - - dragEnd0 = (el: HTMLElement) => { - el.classList.remove(styles.modifiers.ghostRow); - el.classList.remove(styles.modifiers.dragOver); - el.setAttribute('aria-pressed', 'false'); - this.setState({ - draggedItemId: null, - draggingToItemIndex: null, - dragging: false - }); - }; - - dragEnd = (evt: React.DragEvent) => { - this.dragEnd0(evt.target as HTMLElement); - }; - - isValidDrop = (evt: React.DragEvent) => { - const ulRect = this.ref.current.getBoundingClientRect(); - return ( - evt.clientX > ulRect.x && - evt.clientX < ulRect.x + ulRect.width && - evt.clientY > ulRect.y && - evt.clientY < ulRect.y + ulRect.height - ); - }; - - drop = (evt: React.DragEvent) => { - if (this.isValidDrop(evt)) { - this.props.onDragFinish(this.state.tempItemOrder); - } else { - this.onDragCancel(); - } - }; - - dragOver0 = (id: string) => { - const draggingToItemIndex = Array.from(this.ref.current.children).findIndex(item => item.id === id); - if (draggingToItemIndex !== this.state.draggingToItemIndex) { - const tempItemOrder = moveItem([...this.props.itemOrder], this.state.draggedItemId, draggingToItemIndex); - this.move(tempItemOrder); - - this.setState({ - draggingToItemIndex, - tempItemOrder - }); - } - }; - - dragOver = (evt: React.DragEvent): string | null => { - evt.preventDefault(); - - const curListItem = (evt.target as HTMLElement).closest('li'); - if (!curListItem || !this.ref.current.contains(curListItem) || curListItem.id === this.state.draggedItemId) { - // We're going nowhere, don't bother calling `dragOver0` - return null; - } else { - this.dragOver0(curListItem.id); - } - }; - - handleDragButtonKeys = (evt: React.KeyboardEvent) => { - const { dragging } = this.state; - if (![' ', 'Escape', 'Enter', 'ArrowUp', 'ArrowDown'].includes(evt.key) || !this.html5DragDrop) { - if (dragging) { - evt.preventDefault(); - } - return; - } - evt.preventDefault(); - - const dragItem = (evt.target as Element).closest('li'); - - if (evt.key === ' ' || (evt.key === 'Enter' && !dragging)) { - this.dragStart0(dragItem); - } else if (dragging) { - if (evt.key === 'Escape' || evt.key === 'Enter') { - this.setState({ - dragging: false - }); - this.dragFinished = true; - if (evt.key === 'Enter') { - this.dragEnd0(dragItem); - this.props.onDragFinish(this.state.tempItemOrder); - } else { - this.onDragCancel(); - } - } else if (evt.key === 'ArrowUp') { - const nextSelection = dragItem.previousSibling as HTMLElement; - if (nextSelection) { - this.dragOver0(nextSelection.id); - (dragItem.querySelector(`.${styles.dataListItemDraggableButton}`) as HTMLElement).focus(); - } - } else if (evt.key === 'ArrowDown') { - const nextSelection = dragItem.nextSibling as HTMLElement; - if (nextSelection) { - this.dragOver0(nextSelection.id); - (dragItem.querySelector(`.${styles.dataListItemDraggableButton}`) as HTMLElement).focus(); - } - } - } - }; - render() { const { className, @@ -309,42 +83,23 @@ export class DataList extends React.Component { selectedDataListItemId, isCompact, wrapModifier, - /* eslint-disable @typescript-eslint/no-unused-vars */ - onDragStart, - onDragMove, - onDragCancel, - onDragFinish, gridBreakpoint, - itemOrder, selectableRow, - /* eslint-enable @typescript-eslint/no-unused-vars */ ...props } = this.props; - const { dragging } = this.state; const isSelectable = onSelectDataListItem !== undefined; const updateSelectedDataListItem = (id: string) => { onSelectDataListItem(id); }; - const dragProps = this.html5DragDrop && { - onDragOver: this.dragOver, - onDrop: this.dragOver, - onDragLeave: this.dragLeave - }; - return (
    { isCompact && styles.modifiers.compact, gridBreakpointClasses[gridBreakpoint], wrapModifier && styles.modifiers[wrapModifier], - dragging && styles.modifiers.dragOver, className )} style={props.style} {...props} - {...dragProps} ref={this.ref} > {children} diff --git a/packages/react-core/src/components/DataList/DataListDragButton.tsx b/packages/react-core/src/components/DataList/DataListDragButton.tsx index d1b00bf1baf..55000ed0d46 100644 --- a/packages/react-core/src/components/DataList/DataListDragButton.tsx +++ b/packages/react-core/src/components/DataList/DataListDragButton.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/DataList/data-list'; import GripVerticalIcon from '@patternfly/react-icons/dist/esm/icons/grip-vertical-icon'; -import { DataListContext } from './DataList'; export interface DataListDragButtonProps extends React.HTMLProps { /** Additional classes added to the drag button */ @@ -18,20 +17,15 @@ export const DataListDragButton: React.FunctionComponent ( - - {({ dragKeyHandler }) => ( - - )} - + ); DataListDragButton.displayName = 'DataListDragButton'; diff --git a/packages/react-core/src/components/DataList/DataListItem.tsx b/packages/react-core/src/components/DataList/DataListItem.tsx index b823319f6c5..b9a320cb847 100644 --- a/packages/react-core/src/components/DataList/DataListItem.tsx +++ b/packages/react-core/src/components/DataList/DataListItem.tsx @@ -3,7 +3,6 @@ import { css } from '@patternfly/react-styles'; import styles from '@patternfly/react-styles/css/components/DataList/data-list'; import { DataListContext } from './DataList'; import { KeyTypes } from '../../helpers/constants'; -import { DataListDragButton, DataListDragButtonProps } from './DataListDragButton'; export interface DataListItemProps extends Omit, 'children' | 'ref'> { /** Flag to show if the expanded content of the DataList item is visible */ @@ -25,25 +24,6 @@ export interface DataListItemChildProps { rowid: string; } -function findDataListDragButton(node: React.ReactNode): React.ReactElement | null { - if (!React.isValidElement(node)) { - return null; - } - if (node.type === DataListDragButton) { - return node as React.ReactElement; - } - if (node.props.children) { - for (const child of React.Children.toArray(node.props.children)) { - const button = findDataListDragButton(child); - if (button) { - return button; - } - } - } - - return null; -} - export class DataListItem extends React.Component { static displayName = 'DataListItem'; static defaultProps: DataListItemProps = { @@ -65,16 +45,7 @@ export class DataListItem extends React.Component { } = this.props; return ( - {({ - isSelectable, - selectedDataListItemId, - updateSelectedDataListItem, - selectableRow, - isDraggable, - dragStart, - dragEnd, - drop - }) => { + {({ isSelectable, selectedDataListItemId, updateSelectedDataListItem, selectableRow }) => { const selectDataListItem = (event: React.MouseEvent) => { let target: any = event.target; while (event.currentTarget !== target) { @@ -98,17 +69,6 @@ export class DataListItem extends React.Component { } }; - // We made the DataListDragButton determine if the entire item is draggable instead of - // DataListItem like we should have. - // Recursively search children for the DataListDragButton and see if it's disabled... - const dragButton = findDataListDragButton(children); - const dragProps = isDraggable && { - draggable: dragButton ? !dragButton.props.isDisabled : true, - onDrop: drop, - onDragEnd: dragEnd, - onDragStart: dragStart - }; - const isSelected = selectedDataListItemId === id; const selectableInputAriaProps = selectableInputAriaLabel @@ -129,7 +89,6 @@ export class DataListItem extends React.Component { {...(isSelectable && { tabIndex: 0, onClick: selectDataListItem, onKeyDown })} {...(isSelectable && isSelected && { 'aria-selected': true })} {...props} - {...dragProps} > {selectableRow && ( { }); }); -describe('Data List Draggable Demo Test', () => { +// Skip this test until a new drag and drop is implemented +describe.skip('Data List Draggable Demo Test', () => { it('Navigate to demo section', () => { cy.visit('http://localhost:3000/data-list-draggable-demo-nav-link'); }); diff --git a/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx b/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx index 23761a9b7e7..921a41b6d26 100644 --- a/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx +++ b/packages/react-integration/demo-app-ts/src/components/demos/DataListDemo/DataListDraggableDemo.tsx @@ -4,146 +4,107 @@ import { DataListItem, DataListCell, DataListItemRow, - DataListCheck, DataListControl, DataListDragButton, - DataListItemCells + DataListItemCells, + DragDrop, + Draggable, + Droppable } from '@patternfly/react-core'; +interface ItemType { + id: string; + content: string; +} + +const getItems = (count: number) => + Array.from({ length: count }, (_, idx) => idx).map(idx => ({ + id: `draggable-item-${idx}`, + content: `item ${idx} ` + })); + +const reorder = (list: ItemType[], startIndex: number, endIndex: number) => { + const result = list; + const [removed] = result.splice(startIndex, 1); + result.splice(endIndex, 0, removed); + return result; +}; + export class DataListDraggableDemo extends React.Component { static displayName = 'DataListDraggableDemo'; state = { liveText: '', - id: '', - itemOrder: ['data1', 'data2', 'data3', 'data4'] - }; - - onDragStart = (id: string) => { - this.setState({ - id, - liveText: `Dragging started for item id: ${id}.` - }); + items: getItems(5) }; - onDragMove = (_oldIndex: number, _newIndex: number) => { - const { id } = this.state; + onDrag = (source: any) => { this.setState({ - liveText: `Dragging item ${id}.` + liveText: `Started dragging ${this.state.items[source.index].content}` }); + // Return true to allow drag + return true; }; - onDragCancel = () => { - this.setState({ - liveText: `Dragging cancelled. List is unchanged.` - }); + onDragMove = (source: any, dest: any) => { + const { liveText, items } = this.state; + const newText = dest ? `Move ${items[source.index].content} to ${items[dest.index].content}` : 'Invalid drop zone'; + if (newText !== liveText) { + this.setState({ + liveText: newText + }); + } }; - onDragFinish = (itemOrder: string[]) => { - this.setState({ - liveText: `Dragging finished`, - itemOrder - }); + onDrop = (source: any, dest: any) => { + if (dest) { + const newItems = reorder(this.state.items, source.index, dest.index); + this.setState({ + items: newItems, + liveText: 'Dragging finished.' + }); + return true; // Signal that this is a valid drop and not to animate the item returning home. + } else { + this.setState({ + liveText: 'Dragging cancelled. List unchanged.' + }); + } }; render() { - const { liveText, itemOrder } = this.state; + const { liveText, items } = this.state; return ( - - - - - - - - - - Item 1 - - ]} - /> - - - - - - - - - - Item 2 - - ]} - /> - - - - - - - - - - Item 3 - - ]} - /> - - - - - - - - - - Item 4 - - ]} - /> - - - + + + + {items.map(({ id, content }) => ( + + + + + + + + {content} + + ]} + /> + + + + ))} + +
    {liveText}
    -
    + ); } }