Skip to content

Commit

Permalink
7.0: Fix UPP drop bugs, add drop to the end of the list (#15554)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
elisabethcvs committed Oct 22, 2020
1 parent 94bf37f commit 002ca5c
Show file tree
Hide file tree
Showing 12 changed files with 133 additions and 38 deletions.
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
@@ -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"
}
Original file line number Diff line number Diff line change
Expand Up @@ -134,4 +134,10 @@ export interface ISelectedItemsListProps<T> extends React.ClassAttributes<any> {
* 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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],
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ exports[`UnifiedPeoplePicker renders correctly with no items 1`] = `
display: flex;
flex: 1 1 auto;
}
onDragOver={[Function]}
onDrop={[Function]}
role="combobox"
>
<input
Expand Down Expand Up @@ -786,6 +788,8 @@ exports[`UnifiedPeoplePicker renders correctly with selected and suggested items
display: flex;
flex: 1 1 auto;
}
onDragOver={[Function]}
onDrop={[Function]}
role="combobox"
>
<input
Expand Down
73 changes: 60 additions & 13 deletions packages/experiments/src/components/UnifiedPicker/UnifiedPicker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,16 @@ export const UnifiedPicker = <T extends {}>(props: IUnifiedPickerProps<T>): 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) {
Expand Down Expand Up @@ -113,16 +123,49 @@ export const UnifiedPicker = <T extends {}>(props: IUnifiedPickerProps<T>): JSX.
insertIndex = -1;
};

const _onDragOverAutofill = (event?: React.DragEvent<HTMLDivElement>) => {
if (autofillDragDropEnabled) {
event?.preventDefault();
}
};

const _onDropAutoFill = (event?: React.DragEvent<HTMLDivElement>) => {
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;
Expand Down Expand Up @@ -176,20 +219,22 @@ export const UnifiedPicker = <T extends {}>(props: IUnifiedPickerProps<T>): 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<HTMLDivElement>) => {
if (ev.which !== KeyCodes.backspace) {
return;
const _onKeyDown = (ev: React.KeyboardEvent<HTMLDivElement>) => {
// 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 &&
Expand Down Expand Up @@ -347,7 +392,7 @@ export const UnifiedPicker = <T extends {}>(props: IUnifiedPickerProps<T>): JSX.
<div
ref={rootRef}
className={css('ms-BasePicker ms-BaseExtendedPicker', className ? className : '')}
onKeyDown={_onBackspace}
onKeyDown={_onKeyDown}
onCopy={_onCopy}
>
<FocusZone direction={FocusZoneDirection.bidirectional} {...focusZoneProps}>
Expand All @@ -362,6 +407,8 @@ export const UnifiedPicker = <T extends {}>(props: IUnifiedPickerProps<T>): JSX.
aria-haspopup="listbox"
role="combobox"
className={css('ms-BasePicker-div', classNames.pickerDiv)}
onDrop={_onDropAutoFill}
onDragOver={_onDragOverAutofill}
>
<Autofill
{...(inputProps as IInputProps)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,11 @@ export interface IUnifiedPickerProps<T> {
*/
onInputChange?: (filter: string) => void;

/**
* Callback for when a key is pressed
*/
onKeyDown?: (ev: React.KeyboardEvent<HTMLDivElement>) => void;

/**
* Drag drop events callback interface
*/
Expand All @@ -95,4 +100,23 @@ export interface IUnifiedPickerProps<T> {
* 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<HTMLDivElement>) => void;
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ exports[`UnifiedPicker renders correctly with no items 1`] = `
display: flex;
flex: 1 1 auto;
}
onDragOver={[Function]}
onDrop={[Function]}
role="combobox"
>
<input
Expand Down Expand Up @@ -191,6 +193,8 @@ exports[`UnifiedPicker renders correctly with selected and suggested items 1`] =
display: flex;
flex: 1 1 auto;
}
onDragOver={[Function]}
onDrop={[Function]}
role="combobox"
>
<input
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@ export const useSelectedItems = <T extends {}>(
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);
};
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -213,6 +222,7 @@ const UnifiedPeoplePickerExample = (): JSX.Element => {
serializeItemsForDrag: _serializeItemsForDrag,
deserializeItemsFromDrop: _deserializeItemsFromDrop,
dropItemsAt: _dropItemsAt,
itemsAreEqual: _itemsAreEqual,
} as ISelectedPeopleListProps<IPersonaProps>;

const inputProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ export const UnifiedPeoplePickerExample = (): JSX.Element => {
onInputChange={_onInputChange}
// eslint-disable-next-line react/jsx-no-bind
onPaste={_onPaste}
autofillDragDropEnabled={false}
/>
</>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down Expand Up @@ -241,7 +218,6 @@ export const UnifiedPeoplePickerWithEditExample = (): JSX.Element => {
removeButtonAriaLabel: 'Remove',
onItemsRemoved: _onItemsRemoved,
getItemCopyText: _getItemsCopyText,
dropItemsAt: _dropItemsAt,
onRenderItem: SelectedItem,
replaceItem: _replaceItem,
} as ISelectedPeopleListProps<IPersonaProps>;
Expand All @@ -261,6 +237,7 @@ export const UnifiedPeoplePickerWithEditExample = (): JSX.Element => {
onInputChange={_onInputChange}
// eslint-disable-next-line react/jsx-no-bind
onPaste={_onPaste}
defaultDragDropEnabled={false}
/>
</>
);
Expand Down

0 comments on commit 002ca5c

Please sign in to comment.