@uifabric/experiments
Version:
Experimental React components for building experiences for Microsoft 365.
445 lines • 25.4 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
var tslib_1 = require("tslib");
var React = require("react");
var UnifiedPicker_styles_1 = require("./UnifiedPicker.styles");
var Utilities_1 = require("../../Utilities");
var DragDropHelper_1 = require("office-ui-fabric-react/lib-commonjs/utilities/dragdrop/DragDropHelper");
var office_ui_fabric_react_1 = require("office-ui-fabric-react");
var useFloatingSuggestionItems_1 = require("./hooks/useFloatingSuggestionItems");
var useSelectedItems_1 = require("./hooks/useSelectedItems");
var Styling_1 = require("office-ui-fabric-react/lib-commonjs/Styling");
var merge_styles_1 = require("@uifabric/merge-styles");
var Utilities_2 = require("office-ui-fabric-react/lib-commonjs/Utilities");
exports.UnifiedPicker = function (props) {
var _a, _b;
var getClassNames = Utilities_1.classNamesFunction();
var classNames = getClassNames(UnifiedPicker_styles_1.getStyles);
var dragDropEvents = props.dragDropEvents, onKeyDown = props.onKeyDown, onDropAutoFill = props.onDropAutoFill, onPaste = props.onPaste, className = props.className, focusZoneProps = props.focusZoneProps, inputProps = props.inputProps, onRenderSelectedItems = props.onRenderSelectedItems, selectedItemsListProps = props.selectedItemsListProps, onRenderFloatingSuggestions = props.onRenderFloatingSuggestions, floatingSuggestionProps = props.floatingSuggestionProps, headerComponent = props.headerComponent, onInputChange = props.onInputChange, customClipboardType = props.customClipboardType, onValidateInput = props.onValidateInput;
var _c = props.floatingSuggestionProps, pickerWidth = _c.pickerWidth, suggestions = _c.suggestions, selectedSuggestionIndex = _c.selectedSuggestionIndex, selectedFooterIndex = _c.selectedFooterIndex, selectedHeaderIndex = _c.selectedHeaderIndex, pickerSuggestionsProps = _c.pickerSuggestionsProps, isSuggestionsVisible = _c.isSuggestionsVisible, onSuggestionSelected = _c.onSuggestionSelected, onFloatingSuggestionsDismiss = _c.onFloatingSuggestionsDismiss, onRemoveSuggestion = _c.onRemoveSuggestion;
var _d = props.selectedItemsListProps, selectedItemsListOnItemsRemoved = _d.onItemsRemoved, selectedItemsListDropItemsAt = _d.dropItemsAt, selectedItemsListGetItemCopyText = _d.getItemCopyText, selectedItemsListReplaceItem = _d.replaceItem, itemsAreEqual = _d.itemsAreEqual, deserializeItemsFromDrop = _d.deserializeItemsFromDrop, serializeItemsForDrag = _d.serializeItemsForDrag, createGenericItem = _d.createGenericItem;
var _e = props.inputProps || {}, inputPropsOnClick = _e.onClick, inputPropsOnFocus = _e.onFocus;
var rootRef = React.createRef();
var input = React.useRef(null);
var _f = React.useState(new Utilities_1.Selection({ onSelectionChanged: function () { return _onSelectionChanged(); } })), selection = _f[0], setSelection = _f[1];
var _g = React.useState(selection.getSelectedIndices() || []), focusedItemIndices = _g[0], setFocusedItemIndices = _g[1];
var _h = React.useState(-1), draggedIndex = _h[0], setDraggedIndex = _h[1];
var dragDropHelper = new DragDropHelper_1.DragDropHelper({
selection: selection,
});
var _j = useFloatingSuggestionItems_1.useFloatingSuggestionItems(suggestions, (_a = pickerSuggestionsProps) === null || _a === void 0 ? void 0 : _a.footerItemsProps, (_b = pickerSuggestionsProps) === null || _b === void 0 ? void 0 : _b.headerItemsProps, selectedSuggestionIndex, selectedFooterIndex, selectedHeaderIndex, isSuggestionsVisible), focusItemIndex = _j.focusItemIndex, suggestionItems = _j.suggestionItems, footerItemIndex = _j.footerItemIndex, footerItems = _j.footerItems, headerItemIndex = _j.headerItemIndex, headerItems = _j.headerItems, isSuggestionsShown = _j.isSuggestionsShown, showPicker = _j.showPicker, selectPreviousSuggestion = _j.selectPreviousSuggestion, selectNextSuggestion = _j.selectNextSuggestion, queryString = _j.queryString, setQueryString = _j.setQueryString;
var _k = useSelectedItems_1.useSelectedItems(selection, props.selectedItemsListProps.selectedItems), selectedItems = _k.selectedItems, addItems = _k.addItems, dropItemsAt = _k.dropItemsAt, removeItems = _k.removeItems, removeItemAt = _k.removeItemAt, removeSelectedItems = _k.removeSelectedItems, unselectAll = _k.unselectAll, getSelectedItems = _k.getSelectedItems, setSelectedItems = _k.setSelectedItems;
var _onSelectionChanged = function () {
showPicker(false);
setSelection(selection);
setFocusedItemIndices(selection.getSelectedIndices());
};
var defaultDragDropEnabled = props.defaultDragDropEnabled !== undefined ? props.defaultDragDropEnabled : true;
var autofillDragDropEnabled = props.autofillDragDropEnabled !== undefined ? props.autofillDragDropEnabled : defaultDragDropEnabled;
React.useImperativeHandle(props.componentRef, function () { return ({
clearInput: function () {
if (input.current) {
input.current.clear();
}
},
focus: function () {
if (input.current) {
input.current.focus();
}
},
getSelectedItems: function () {
return getSelectedItems();
},
forceResolve: function () {
if (focusItemIndex >= 0) {
_onSuggestionSelected(undefined, suggestionItems[focusItemIndex]);
return true;
}
else {
return false;
}
},
}); });
// All of the drag drop functions are the default behavior. Users can override that by setting the dragDropEvents prop
var theme = Styling_1.getTheme();
var dragEnterClass = merge_styles_1.mergeStyles({
backgroundColor: theme.palette.neutralLight,
});
var _onDragEnter = function (item, event) {
// return string is the css classes that will be added to the entering element.
return dragEnterClass;
};
var insertIndex = -1;
var isInDropAction = false;
var _dropItemsAt = function (newItems) {
var _a;
var indicesToRemove = [];
// If we are moving items within the same picker, remove them from their old places as well
if (draggedIndex > -1) {
indicesToRemove = focusedItemIndices.includes(draggedIndex) ? tslib_1.__spreadArrays(focusedItemIndices) : [draggedIndex];
}
(_a = selectedItemsListDropItemsAt) === null || _a === void 0 ? void 0 : _a(insertIndex, newItems, indicesToRemove);
dropItemsAt(insertIndex, newItems, indicesToRemove);
unselectAll();
insertIndex = -1;
isInDropAction = false;
};
var _onDragOverAutofill = function (event) {
var _a;
if (autofillDragDropEnabled) {
(_a = event) === null || _a === void 0 ? void 0 : _a.preventDefault();
}
};
var _onDropAutoFill = function (event) {
var _a, _b;
isInDropAction = true;
(_a = event) === null || _a === void 0 ? void 0 : _a.preventDefault();
if (onDropAutoFill) {
onDropAutoFill(event);
}
else {
insertIndex = selectedItems.length;
_onDropInner((_b = event) === null || _b === void 0 ? void 0 : _b.dataTransfer);
}
};
var _canDrop = function (dropContext, dragContext) {
return defaultDragDropEnabled && !focusedItemIndices.includes(dropContext.index);
};
var _onDropList = function (item, event) {
var _a, _b, _c;
isInDropAction = true;
/* 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 (itemsAreEqual) {
insertIndex = selectedItems.findIndex(function (currentItem) { return (itemsAreEqual ? itemsAreEqual(currentItem, item) : false); });
}
else {
insertIndex = selectedItems.indexOf(item);
}
// If the drop is in the right half of the item, we want to drop at index+1
if (event && event.currentTarget) {
var targetElement = event.currentTarget;
var halfwayPoint = targetElement.offsetLeft + targetElement.offsetWidth / 2;
if (Utilities_2.getRTL()) {
if (event.pageX < halfwayPoint) {
insertIndex++;
}
}
else {
if (event.pageX > halfwayPoint) {
insertIndex++;
}
}
}
(_a = event) === null || _a === void 0 ? void 0 : _a.preventDefault();
_onDropInner(((_b = event) === null || _b === void 0 ? void 0 : _b.dataTransfer) !== null ? (_c = event) === null || _c === void 0 ? void 0 : _c.dataTransfer : undefined);
};
var _onDropInner = function (dataTransfer) {
var isDropHandled = false;
if (dataTransfer) {
var data = dataTransfer.items;
for (var i = 0; i < data.length; i++) {
if (data[i].kind === 'string' && data[i].type === customClipboardType) {
isDropHandled = true;
data[i].getAsString(function (dropText) {
if (deserializeItemsFromDrop) {
var newItems = deserializeItemsFromDrop(dropText);
_dropItemsAt(newItems);
}
});
}
}
}
if (!isDropHandled && draggedIndex > -1) {
var newItems = focusedItemIndices.includes(draggedIndex)
? getSelectedItems()
: [selectedItems[draggedIndex]];
_dropItemsAt(newItems);
}
};
var _onDragStart = function (item, itemIndex, tempSelectedItems, event) {
var _a, _b, _c;
/* eslint-disable-next-line eqeqeq */
var draggedItemIndex = itemIndex != null ? itemIndex : -1;
setDraggedIndex(draggedItemIndex);
// Set effectAllowed to be only move so that browser doesn't incorrectly copy instead
if (event && event.dataTransfer) {
event.dataTransfer.effectAllowed = 'move';
}
if (event) {
var dataList = (_b = (_a = event) === null || _a === void 0 ? void 0 : _a.dataTransfer) === null || _b === void 0 ? void 0 : _b.items;
if (serializeItemsForDrag && customClipboardType) {
var draggedItems = focusedItemIndices.includes(draggedItemIndex) ? tslib_1.__spreadArrays(getSelectedItems()) : [item];
var dragText = serializeItemsForDrag(draggedItems);
(_c = dataList) === null || _c === void 0 ? void 0 : _c.add(dragText, customClipboardType);
}
}
};
var _onDragEnd = function (item, event) {
var _a, _b, _c, _d;
// Because these calls are async, it's possible for us to get the drag end call while
// we're in the middle of a drop action, so don't run the delete code if that's the case
if (event && !isInDropAction) {
// If we have a move event, and we still have selected items (indicating that we
// haven't already moved items within the well) we should remove the item(s)
if (((_a = event.dataTransfer) === null || _a === void 0 ? void 0 : _a.dropEffect) === 'move' && focusedItemIndices.length > 0) {
var itemsToRemove = focusedItemIndices.includes(draggedIndex)
? getSelectedItems()
: [selectedItems[draggedIndex]];
_onRemoveSelectedItems(itemsToRemove);
}
// Clear any remaining drag data
var dataList = (_c = (_b = event) === null || _b === void 0 ? void 0 : _b.dataTransfer) === null || _c === void 0 ? void 0 : _c.items;
(_d = dataList) === null || _d === void 0 ? void 0 : _d.clear();
}
setDraggedIndex(-1);
isInDropAction = false;
};
var defaultDragDropEvents = {
canDrop: _canDrop,
canDrag: function () { return defaultDragDropEnabled; },
onDragEnter: _onDragEnter,
onDragLeave: function () { return undefined; },
onDrop: _onDropList,
onDragStart: _onDragStart,
onDragEnd: _onDragEnd,
};
var _onSuggestionSelected = React.useCallback(function (ev, item) {
var _a;
addItems([item.item]);
(_a = onSuggestionSelected) === null || _a === void 0 ? void 0 : _a(ev, item);
if (input.current) {
input.current.clear();
}
showPicker(false);
}, [addItems, onSuggestionSelected, showPicker]);
var _onKeyDown = React.useCallback(function (ev) {
var _a, _b, _c, _d;
// Allow the caller to handle the key down
(_a = onKeyDown) === null || _a === void 0 ? void 0 : _a(ev);
// This is a temporary work around, it has localization issues
// we plan on rewriting how this works in the future
var isDel = ev.which === Utilities_1.KeyCodes.del;
var isCut = (ev.shiftKey && isDel) || (ev.ctrlKey && ev.which === Utilities_1.KeyCodes.x);
var isBackspace = ev.which === Utilities_1.KeyCodes.backspace;
var isCopy = ev.ctrlKey && ev.which === Utilities_1.KeyCodes.c;
var needToCopy = isCut || isCopy;
var needToDelete = (isBackspace && selectedItems.length > 0) || ((isCut || isDel) && focusedItemIndices.length > 0);
// Handle copy (or cut) if focus is in the selected items list
if (needToCopy) {
if (focusedItemIndices.length > 0 && selectedItemsListGetItemCopyText) {
ev.preventDefault();
var copyItems = selection.getSelection();
var copyString = selectedItemsListGetItemCopyText(copyItems);
navigator.clipboard.writeText(copyString).then(function () {
/* clipboard successfully set */
}, function () {
/* clipboard write failed */
// Swallow the error
});
}
}
// Handle delete of items via Backspace/Del/Ctrl+X
if (needToDelete) {
if (focusedItemIndices.length === 0 &&
input &&
input.current &&
!input.current.isValueSelected &&
input.current.inputElement === document.activeElement &&
input.current.cursorLocation === 0) {
showPicker(false);
ev.preventDefault();
(_b = selectedItemsListOnItemsRemoved) === null || _b === void 0 ? void 0 : _b([selectedItems[selectedItems.length - 1]]);
removeItemAt(selectedItems.length - 1);
}
else if (focusedItemIndices.length > 0) {
showPicker(false);
ev.preventDefault();
(_c = selectedItemsListOnItemsRemoved) === null || _c === void 0 ? void 0 : _c(getSelectedItems());
removeSelectedItems();
(_d = input.current) === null || _d === void 0 ? void 0 : _d.focus();
}
}
}, [
focusedItemIndices.length,
getSelectedItems,
onKeyDown,
removeItemAt,
removeSelectedItems,
selectedItems,
selectedItemsListGetItemCopyText,
selectedItemsListOnItemsRemoved,
selection,
showPicker,
]);
var _onValidateInput = React.useCallback(function () {
if (onValidateInput && createGenericItem) {
var itemToConvert = createGenericItem(queryString, onValidateInput(queryString));
if (itemToConvert !== null && itemToConvert !== undefined) {
_onSuggestionSelected(undefined, { item: itemToConvert, isSelected: false });
}
}
}, [onValidateInput, createGenericItem, queryString, _onSuggestionSelected]);
var _onInputKeyDown = React.useCallback(function (ev) {
if (isSuggestionsShown) {
var keyCode = ev.which;
switch (keyCode) {
case Utilities_1.KeyCodes.escape:
showPicker(false);
ev.preventDefault();
ev.stopPropagation();
break;
case Utilities_1.KeyCodes.enter:
case Utilities_1.KeyCodes.tab:
if (!ev.shiftKey && !ev.ctrlKey && (focusItemIndex >= 0 || footerItemIndex >= 0 || headerItemIndex >= 0)) {
ev.preventDefault();
ev.stopPropagation();
if (focusItemIndex >= 0) {
// Get the focused element and add it to selectedItemsList
showPicker(false);
_onSuggestionSelected(ev, suggestionItems[focusItemIndex]);
}
else if (footerItemIndex >= 0) {
// execute the footer action
footerItems[footerItemIndex].onExecute();
}
else if (headerItemIndex >= 0) {
// execute the header action
headerItems[headerItemIndex].onExecute();
}
}
else {
_onValidateInput();
}
break;
case Utilities_1.KeyCodes.up:
ev.preventDefault();
ev.stopPropagation();
selectPreviousSuggestion();
break;
case Utilities_1.KeyCodes.down:
ev.preventDefault();
ev.stopPropagation();
selectNextSuggestion();
break;
}
}
}, [
_onSuggestionSelected,
focusItemIndex,
footerItemIndex,
footerItems,
headerItemIndex,
headerItems,
isSuggestionsShown,
selectNextSuggestion,
selectPreviousSuggestion,
showPicker,
suggestionItems,
_onValidateInput,
]);
React.useEffect(function () {
var _a, _b;
var inputElement = (_a = input.current) === null || _a === void 0 ? void 0 : _a.inputElement;
(_b = inputElement) === null || _b === void 0 ? void 0 : _b.addEventListener('keydown', _onInputKeyDown);
return function () {
var _a;
(_a = inputElement) === null || _a === void 0 ? void 0 : _a.removeEventListener('keydown', _onInputKeyDown);
};
});
var _onCopy = React.useCallback(function (ev) {
if (focusedItemIndices.length > 0 && selectedItemsListGetItemCopyText) {
var copyItems = selection.getSelection();
var copyString = selectedItemsListGetItemCopyText(copyItems);
ev.clipboardData.setData('text/plain', copyString);
ev.preventDefault();
}
}, [focusedItemIndices.length, selectedItemsListGetItemCopyText, selection]);
var _onInputFocus = React.useCallback(function (ev) {
var _a;
unselectAll();
(_a = inputPropsOnFocus) === null || _a === void 0 ? void 0 : _a(ev);
}, [inputPropsOnFocus, unselectAll]);
var _onInputClick = React.useCallback(function (ev) {
var _a;
unselectAll();
showPicker(true);
(_a = inputPropsOnClick) === null || _a === void 0 ? void 0 : _a(ev);
}, [inputPropsOnClick, showPicker, unselectAll]);
var _onInputChange = React.useCallback(function (value, composing, resultItemsList) {
if (!composing) {
// update query string
setQueryString(value);
// suggestions isn't showing and we haven't just cleared the input, show the picker
!isSuggestionsShown && value !== '' ? showPicker(true) : null;
if (!resultItemsList) {
resultItemsList = [];
}
if (onInputChange) {
onInputChange(value, composing, resultItemsList);
if (resultItemsList && resultItemsList.length > 0) {
addItems(resultItemsList);
showPicker(false);
// Clear the input
if (input.current) {
input.current.clear();
}
}
}
}
}, [addItems, isSuggestionsShown, onInputChange, setQueryString, showPicker]);
var _onPaste = React.useCallback(function (ev) {
if (onPaste) {
var inputText = ev.clipboardData.getData('Text');
ev.preventDefault();
// Pass current selected items
onPaste(inputText, selectedItems);
setSelectedItems(selectedItems);
selection.setItems(selectedItems);
}
}, [onPaste, selectedItems, selection, setSelectedItems]);
var _renderSelectedItemsList = function () {
return onRenderSelectedItems(tslib_1.__assign(tslib_1.__assign({}, selectedItemsListProps), { selectedItems: selectedItems, focusedItemIndices: focusedItemIndices, onItemsRemoved: _onRemoveSelectedItems, replaceItem: _replaceItem, dragDropHelper: dragDropHelper, dragDropEvents: dragDropEvents ? dragDropEvents : defaultDragDropEvents }));
};
var _onFloatingSuggestionsDismiss = React.useCallback(function (ev) {
var _a;
(_a = onFloatingSuggestionsDismiss) === null || _a === void 0 ? void 0 : _a(ev);
showPicker(false);
}, [onFloatingSuggestionsDismiss, showPicker]);
var _onFloatingSuggestionRemoved = React.useCallback(function (ev, item) {
var _a;
(_a = onRemoveSuggestion) === null || _a === void 0 ? void 0 : _a(ev, item);
// We want to keep showing the picker to show the user that the entry has been removed from the list.
showPicker(true);
}, [onRemoveSuggestion, showPicker]);
var _onRemoveSelectedItems = React.useCallback(function (itemsToRemove) {
var _a;
removeItems(itemsToRemove);
(_a = selectedItemsListOnItemsRemoved) === null || _a === void 0 ? void 0 : _a(itemsToRemove);
}, [removeItems, selectedItemsListOnItemsRemoved]);
var _replaceItem = React.useCallback(function (newItem, index) {
var _a;
var newItems = Array.isArray(newItem) ? newItem : [newItem];
dropItemsAt(index, newItems, [index]);
(_a = selectedItemsListReplaceItem) === null || _a === void 0 ? void 0 : _a(newItem, index);
}, [dropItemsAt, selectedItemsListReplaceItem]);
var _renderFloatingPicker = function () {
var _a;
return onRenderFloatingSuggestions(tslib_1.__assign(tslib_1.__assign({}, floatingSuggestionProps), { pickerWidth: pickerWidth || '300px', targetElement: (_a = input.current) === null || _a === void 0 ? void 0 : _a.inputElement, isSuggestionsVisible: isSuggestionsShown, suggestions: suggestionItems, selectedSuggestionIndex: focusItemIndex, selectedFooterIndex: footerItemIndex, selectedHeaderIndex: headerItemIndex, pickerSuggestionsProps: pickerSuggestionsProps, onFloatingSuggestionsDismiss: _onFloatingSuggestionsDismiss, onSuggestionSelected: _onSuggestionSelected, onKeyDown: _onInputKeyDown, onRemoveSuggestion: _onFloatingSuggestionRemoved }));
};
var _canAddItems = function () { return true; };
return (React.createElement("div", { ref: rootRef, className: Utilities_1.css('ms-BasePicker ms-BaseExtendedPicker', className ? className : ''), onKeyDown: _onKeyDown, onCopy: _onCopy },
React.createElement(office_ui_fabric_react_1.FocusZone, tslib_1.__assign({ direction: office_ui_fabric_react_1.FocusZoneDirection.bidirectional }, focusZoneProps),
React.createElement(office_ui_fabric_react_1.SelectionZone, { selection: selection, selectionMode: Utilities_1.SelectionMode.multiple, className: Utilities_1.css('ms-UnifiedPicker-selectionZone', classNames.selectionZone) },
React.createElement("div", { className: Utilities_1.css('ms-BasePicker-text', classNames.pickerText) },
headerComponent,
_renderSelectedItemsList(),
_canAddItems() && (React.createElement("div", { "aria-owns": isSuggestionsShown ? 'suggestion-list' : undefined, "aria-expanded": isSuggestionsShown, "aria-haspopup": "listbox", role: "combobox", className: Utilities_1.css('ms-BasePicker-div', classNames.pickerDiv), onDrop: _onDropAutoFill, onDragOver: _onDragOverAutofill },
React.createElement(office_ui_fabric_react_1.Autofill, tslib_1.__assign({}, inputProps, { className: Utilities_1.css('ms-BasePicker-input', classNames.pickerInput), ref: input, onFocus: _onInputFocus, onClick: _onInputClick, onInputValueChange: _onInputChange, "aria-autocomplete": "list", "aria-activedescendant": isSuggestionsShown && focusItemIndex >= 0
? 'FloatingSuggestionsItemId-' + focusItemIndex
: undefined, disabled: false, onPaste: _onPaste }))))))),
_renderFloatingPicker()));
};
//# sourceMappingURL=UnifiedPicker.js.map