From 002ca5c424c2e5c37adca880a21b26c6764560f8 Mon Sep 17 00:00:00 2001 From: elisabethcvs <64664025+elisabethcvs@users.noreply.github.com> Date: Thu, 22 Oct 2020 10:45:00 -0700 Subject: [PATCH] 7.0: Fix UPP drop bugs, add drop to the end of the list (#15554) * add drop at end of list, ability to disable drag drop * update snapshots * Change files * add onKeyDown prop * fix backspace check * fix preventdefault * regenerate snapshots after merge --- ...s-2020-10-16-10-38-09-uppdropatend7.0.json | 8 ++ ...s-2020-10-16-10-38-09-uppdropatend7.0.json | 8 ++ .../SelectedItemsList.types.ts | 6 ++ .../Items/SelectedPersona.tsx | 2 +- .../UnifiedPeoplePicker.test.tsx.snap | 4 + .../UnifiedPicker/UnifiedPicker.tsx | 73 +++++++++++++++---- .../UnifiedPicker/UnifiedPicker.types.ts | 24 ++++++ .../__snapshots__/UnifiedPicker.test.tsx.snap | 4 + .../UnifiedPicker/hooks/useSelectedItems.ts | 6 ++ .../DoubleUnifiedPeoplePicker.Example.tsx | 10 +++ .../UnifiedPeoplePicker.Example.tsx | 1 + .../UnifiedPeoplePicker.WithEdit.Example.tsx | 25 +------ 12 files changed, 133 insertions(+), 38 deletions(-) create mode 100644 change/@fluentui-react-examples-2020-10-16-10-38-09-uppdropatend7.0.json create mode 100644 change/@uifabric-experiments-2020-10-16-10-38-09-uppdropatend7.0.json diff --git a/change/@fluentui-react-examples-2020-10-16-10-38-09-uppdropatend7.0.json b/change/@fluentui-react-examples-2020-10-16-10-38-09-uppdropatend7.0.json new file mode 100644 index 0000000000000..0fd0e9cf15612 --- /dev/null +++ b/change/@fluentui-react-examples-2020-10-16-10-38-09-uppdropatend7.0.json @@ -0,0 +1,8 @@ +{ + "type": "patch", + "comment": "Modify UPP examples dropping abilities", + "packageName": "@fluentui/react-examples", + "email": "elvonspa@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-16T17:38:09.331Z" +} diff --git a/change/@uifabric-experiments-2020-10-16-10-38-09-uppdropatend7.0.json b/change/@uifabric-experiments-2020-10-16-10-38-09-uppdropatend7.0.json new file mode 100644 index 0000000000000..1b934164a4c62 --- /dev/null +++ b/change/@uifabric-experiments-2020-10-16-10-38-09-uppdropatend7.0.json @@ -0,0 +1,8 @@ +{ + "type": "minor", + "comment": "Add drop to end of UPP, ability to disable drag drop", + "packageName": "@uifabric/experiments", + "email": "elvonspa@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-10-16T17:37:49.336Z" +} diff --git a/packages/experiments/src/components/SelectedItemsList/SelectedItemsList.types.ts b/packages/experiments/src/components/SelectedItemsList/SelectedItemsList.types.ts index dded16bd4eabb..d9d5619968930 100644 --- a/packages/experiments/src/components/SelectedItemsList/SelectedItemsList.types.ts +++ b/packages/experiments/src/components/SelectedItemsList/SelectedItemsList.types.ts @@ -134,4 +134,10 @@ export interface ISelectedItemsListProps extends React.ClassAttributes { * Callback for when an item needs to be replaced with another item or items */ replaceItem?: (newItem: T | T[], index: number) => void; + + /** + * Callback to check to see if two items are equal + * Should be used if it's possible to change some properties on items so a strict compare will fail + */ + itemsAreEqual?: (item1?: any, item2?: any) => boolean; } diff --git a/packages/experiments/src/components/SelectedItemsList/SelectedPeopleList/Items/SelectedPersona.tsx b/packages/experiments/src/components/SelectedItemsList/SelectedPeopleList/Items/SelectedPersona.tsx index ce9dc350c61f8..52cc658fcbe32 100644 --- a/packages/experiments/src/components/SelectedItemsList/SelectedPeopleList/Items/SelectedPersona.tsx +++ b/packages/experiments/src/components/SelectedItemsList/SelectedPeopleList/Items/SelectedPersona.tsx @@ -103,7 +103,7 @@ const SelectedPersonaInner = React.memo( ); const isDraggable = React.useMemo( - () => (dragDropEvents ? !!(dragDropEvents.canDrag && dragDropEvents.canDrop) : undefined), + () => (dragDropEvents && dragDropEvents.canDrag ? !!dragDropEvents.canDrag!() : undefined), [dragDropEvents], ); diff --git a/packages/experiments/src/components/UnifiedPicker/UnifiedPeoplePicker/__snapshots__/UnifiedPeoplePicker.test.tsx.snap b/packages/experiments/src/components/UnifiedPicker/UnifiedPeoplePicker/__snapshots__/UnifiedPeoplePicker.test.tsx.snap index fe595be8eba02..967213ae426b3 100644 --- a/packages/experiments/src/components/UnifiedPicker/UnifiedPeoplePicker/__snapshots__/UnifiedPeoplePicker.test.tsx.snap +++ b/packages/experiments/src/components/UnifiedPicker/UnifiedPeoplePicker/__snapshots__/UnifiedPeoplePicker.test.tsx.snap @@ -61,6 +61,8 @@ exports[`UnifiedPeoplePicker renders correctly with no items 1`] = ` display: flex; flex: 1 1 auto; } + onDragOver={[Function]} + onDrop={[Function]} role="combobox" > (props: IUnifiedPickerProps): JSX. onInputChange, } = props; + const defaultDragDropEnabled = React.useMemo( + () => (props.defaultDragDropEnabled !== undefined ? props.defaultDragDropEnabled : true), + [props.defaultDragDropEnabled], + ); + + const autofillDragDropEnabled = React.useMemo( + () => (props.autofillDragDropEnabled !== undefined ? props.autofillDragDropEnabled : defaultDragDropEnabled), + [props.autofillDragDropEnabled, defaultDragDropEnabled], + ); + React.useImperativeHandle(props.componentRef, () => ({ clearInput: () => { if (input.current) { @@ -113,16 +123,49 @@ export const UnifiedPicker = (props: IUnifiedPickerProps): JSX. insertIndex = -1; }; + const _onDragOverAutofill = (event?: React.DragEvent) => { + if (autofillDragDropEnabled) { + event?.preventDefault(); + } + }; + + const _onDropAutoFill = (event?: React.DragEvent) => { + event?.preventDefault(); + if (props.onDropAutoFill) { + props.onDropAutoFill(event); + } else { + insertIndex = selectedItems.length; + _onDropInner(event?.dataTransfer); + } + }; + const _canDrop = (dropContext?: IDragDropContext, dragContext?: IDragDropContext): boolean => { - return !focusedItemIndices.includes(dropContext!.index); + return defaultDragDropEnabled && !focusedItemIndices.includes(dropContext!.index); + }; + + const _onDropList = (item?: any, event?: DragEvent): void => { + /* indexOf compares using strict equality + if the item is something where properties can change frequently, then the + itemsAreEqual prop should be overloaded + Otherwise it's possible for the indexOf check to fail and return -1 */ + if (props.selectedItemsListProps.itemsAreEqual) { + insertIndex = selectedItems.findIndex(currentItem => + props.selectedItemsListProps.itemsAreEqual + ? props.selectedItemsListProps.itemsAreEqual(currentItem, item) + : false, + ); + } else { + insertIndex = selectedItems.indexOf(item); + } + + event?.preventDefault(); + _onDropInner(event?.dataTransfer !== null ? event?.dataTransfer : undefined); }; - const _onDrop = (item?: any, event?: DragEvent): void => { - insertIndex = selectedItems.indexOf(item); + const _onDropInner = (dataTransfer?: DataTransfer): void => { let isDropHandled = false; - if (event?.dataTransfer) { - event.preventDefault(); - const data = event.dataTransfer.items; + if (dataTransfer) { + const data = dataTransfer.items; for (let i = 0; i < data.length; i++) { if (data[i].kind === 'string' && data[i].type === props.customClipboardType) { isDropHandled = true; @@ -176,20 +219,22 @@ export const UnifiedPicker = (props: IUnifiedPickerProps): JSX. const defaultDragDropEvents: IDragDropEvents = { canDrop: _canDrop, - canDrag: () => true, + canDrag: () => defaultDragDropEnabled, onDragEnter: _onDragEnter, onDragLeave: () => undefined, - onDrop: _onDrop, + onDrop: _onDropList, onDragStart: _onDragStart, onDragEnd: _onDragEnd, }; - const _onBackspace = (ev: React.KeyboardEvent) => { - if (ev.which !== KeyCodes.backspace) { - return; + const _onKeyDown = (ev: React.KeyboardEvent) => { + // Allow the caller to handle the key down + if (props.onKeyDown) { + props.onKeyDown(ev); } - if (selectedItems.length) { + // Handle delete of items via backspace + if (ev.which === KeyCodes.backspace && selectedItems.length) { if ( focusedItemIndices.length === 0 && input && @@ -347,7 +392,7 @@ export const UnifiedPicker = (props: IUnifiedPickerProps): JSX.
@@ -362,6 +407,8 @@ export const UnifiedPicker = (props: IUnifiedPickerProps): JSX. aria-haspopup="listbox" role="combobox" className={css('ms-BasePicker-div', classNames.pickerDiv)} + onDrop={_onDropAutoFill} + onDragOver={_onDragOverAutofill} > { */ onInputChange?: (filter: string) => void; + /** + * Callback for when a key is pressed + */ + onKeyDown?: (ev: React.KeyboardEvent) => void; + /** * Drag drop events callback interface */ @@ -95,4 +100,23 @@ export interface IUnifiedPickerProps { * if this is used */ customClipboardType?: string; + + /** + * If dragDropEvents is set, this property will be ignored + * @defaultvalue true + */ + defaultDragDropEnabled?: boolean; + + /** + * If this property is not specified, defaultDragDropEnabled will be used + * @defaultvalue true + */ + autofillDragDropEnabled?: boolean; + + /** + * Function to customize drop behavior over the autofill portion + * If this is not set, but autofillDragDropEnabled is, the built + * in drop behavior will be used. + */ + onDropAutoFill?: (event?: React.DragEvent) => void; } diff --git a/packages/experiments/src/components/UnifiedPicker/__snapshots__/UnifiedPicker.test.tsx.snap b/packages/experiments/src/components/UnifiedPicker/__snapshots__/UnifiedPicker.test.tsx.snap index 346ec9f0c75e4..6be0e81932ec0 100644 --- a/packages/experiments/src/components/UnifiedPicker/__snapshots__/UnifiedPicker.test.tsx.snap +++ b/packages/experiments/src/components/UnifiedPicker/__snapshots__/UnifiedPicker.test.tsx.snap @@ -61,6 +61,8 @@ exports[`UnifiedPicker renders correctly with no items 1`] = ` display: flex; flex: 1 1 auto; } + onDragOver={[Function]} + onDrop={[Function]} role="combobox" > ( updatedItems.push(item); } } + // if the insert index is at the end, add them now + if (insertIndex === currentItems.length) { + itemsToAdd.forEach(draggedItem => { + updatedItems.push(draggedItem); + }); + } setSelectedItems(updatedItems); selection.setItems(updatedItems); }; diff --git a/packages/react-examples/src/experiments/UnifiedPeoplePicker/DoubleUnifiedPeoplePicker.Example.tsx b/packages/react-examples/src/experiments/UnifiedPeoplePicker/DoubleUnifiedPeoplePicker.Example.tsx index c634c90e76cfc..7591956aafce1 100644 --- a/packages/react-examples/src/experiments/UnifiedPeoplePicker/DoubleUnifiedPeoplePicker.Example.tsx +++ b/packages/react-examples/src/experiments/UnifiedPeoplePicker/DoubleUnifiedPeoplePicker.Example.tsx @@ -162,10 +162,19 @@ const UnifiedPeoplePickerExample = (): JSX.Element => { updatedItems.push(item); } } + if (insertIndex === currentItems.length) { + newItems.forEach(draggedItem => { + updatedItems.push(draggedItem); + }); + } setPeopleSelectedItems(updatedItems); } }; + const _itemsAreEqual = (item1?: any, item2?: any): boolean => { + return item1?.key === item2?.key; + }; + const _onItemsRemoved = (itemsToRemove: IPersonaProps[]): void => { // Updating the local copy as well at the parent level. const currentItems: IPersonaProps[] = [...peopleSelectedItems]; @@ -213,6 +222,7 @@ const UnifiedPeoplePickerExample = (): JSX.Element => { serializeItemsForDrag: _serializeItemsForDrag, deserializeItemsFromDrop: _deserializeItemsFromDrop, dropItemsAt: _dropItemsAt, + itemsAreEqual: _itemsAreEqual, } as ISelectedPeopleListProps; const inputProps = { diff --git a/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.Example.tsx b/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.Example.tsx index df27f168a9d50..e898086c4081c 100644 --- a/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.Example.tsx +++ b/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.Example.tsx @@ -219,6 +219,7 @@ export const UnifiedPeoplePickerExample = (): JSX.Element => { onInputChange={_onInputChange} // eslint-disable-next-line react/jsx-no-bind onPaste={_onPaste} + autofillDragDropEnabled={false} /> ); diff --git a/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.WithEdit.Example.tsx b/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.WithEdit.Example.tsx index d0534023fc9c5..1ce5b62defcbe 100644 --- a/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.WithEdit.Example.tsx +++ b/packages/react-examples/src/experiments/UnifiedPeoplePicker/UnifiedPeoplePicker.WithEdit.Example.tsx @@ -154,29 +154,6 @@ export const UnifiedPeoplePickerWithEditExample = (): JSX.Element => { setPeopleSelectedItems(prevPeopleSelectedItems => [...prevPeopleSelectedItems, ...newList]); }; - const _dropItemsAt = (insertIndex: number, newItems: IPersonaProps[], indicesToRemove: number[]): void => { - // Insert those items into the current list - if (insertIndex > -1) { - const currentItems: IPersonaProps[] = [...peopleSelectedItems]; - const updatedItems: IPersonaProps[] = []; - - for (let i = 0; i < currentItems.length; i++) { - const item = currentItems[i]; - // If this is the insert before index, insert the dragged items, then the current item - if (i === insertIndex) { - newItems.forEach(draggedItem => { - updatedItems.push(draggedItem); - }); - updatedItems.push(item); - } else if (!indicesToRemove.includes(i)) { - // only insert items into the new list that are not being dragged - updatedItems.push(item); - } - } - setPeopleSelectedItems(updatedItems); - } - }; - const _onItemsRemoved = (itemsToRemove: IPersonaProps[]): void => { // Updating the local copy as well at the parent level. const currentItems: IPersonaProps[] = [...peopleSelectedItems]; @@ -241,7 +218,6 @@ export const UnifiedPeoplePickerWithEditExample = (): JSX.Element => { removeButtonAriaLabel: 'Remove', onItemsRemoved: _onItemsRemoved, getItemCopyText: _getItemsCopyText, - dropItemsAt: _dropItemsAt, onRenderItem: SelectedItem, replaceItem: _replaceItem, } as ISelectedPeopleListProps; @@ -261,6 +237,7 @@ export const UnifiedPeoplePickerWithEditExample = (): JSX.Element => { onInputChange={_onInputChange} // eslint-disable-next-line react/jsx-no-bind onPaste={_onPaste} + defaultDragDropEnabled={false} /> );