Skip to content

Commit

Permalink
BaseExtendedPicker: Create contextmenu for renderedItem, fix auto foc…
Browse files Browse the repository at this point in the history
…us (#3954)

* add some fixes

* create contextual menu item wrapper - convert results to promise in example

* add editing item margin, and fix on suggestionSelected focus

* add change file

* fix tslint issues
  • Loading branch information
amyngu authored and dzearing committed Mar 2, 2018
1 parent 9459022 commit 1340339
Show file tree
Hide file tree
Showing 10 changed files with 148 additions and 69 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "@uifabric/experiments",
"comment": "BaseExtendedPicker: Create component to wrap the rendered item, so users get contextual menu if certain props are present, get rid of loading state, fix autofocus on input after suggestion selection",
"type": "minor"
}
],
"packageName": "@uifabric/experiments",
"email": "amyngu@microsoft.com"
}
Original file line number Diff line number Diff line change
Expand Up @@ -248,10 +248,11 @@ export class BaseExtendedPicker<T, P extends IBaseExtendedPickerProps<T>> extend
this.input.clear();

this.floatingPicker.hidePicker();
this.focus();
}

@autobind
protected _onSelectedItemsChanged(): void {
this.input.focus();
this.focus();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -164,23 +164,23 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
}

@autobind
private _onFilterChanged(filterText: string, currentPersonas: IPersonaProps[], limitResults?: number): IPersonaProps[] {
private _onFilterChanged(filterText: string, currentPersonas: IPersonaProps[], limitResults?: number): Promise<IPersonaProps[]> | null {
if (filterText) {
let filteredPersonas: IPersonaProps[] = this._filterPersonasByText(filterText);

filteredPersonas = this._removeDuplicates(filteredPersonas, currentPersonas);
filteredPersonas = limitResults ? filteredPersonas.splice(0, limitResults) : filteredPersonas;
return filteredPersonas;
return this._convertResultsToPromise(filteredPersonas);
} else {
return [];
return this._convertResultsToPromise([]);
}
}

@autobind
private _returnMostRecentlyUsed(currentPersonas: IPersonaProps[]): IPersonaProps[] | Promise<IPersonaProps[]> {
let { mostRecentlyUsed } = this.state;
mostRecentlyUsed = this._removeDuplicates(mostRecentlyUsed, this._picker.items);
return mostRecentlyUsed;
return this._convertResultsToPromise(mostRecentlyUsed);
}

private _onCopyItems(items: IExtendedPersonaProps[]): string {
Expand Down Expand Up @@ -225,6 +225,11 @@ export class ExtendedPeoplePickerTypesExample extends BaseComponent<{}, IPeopleP
return persona.primaryText as string;
}

private _convertResultsToPromise(results: IPersonaProps[]): Promise<IPersonaProps[]> {
// tslint:disable-next-line:no-any
return new Promise<IPersonaProps[]>((resolve: any, reject: any) => setTimeout(() => resolve(results), 150));
}

@autobind
private _validateInput(input: string): boolean {
if (input.indexOf('@') !== -1) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
} else {
this.updateValue(queryString);
}

}
}

Expand All @@ -103,10 +102,13 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
suggestionsVisible: true,
});

if (this.state.queryString === '') {
this.updateSuggestionWithZeroState();
} else {
this.updateValue(this.state.queryString);
if (this.suggestionStore.suggestions.length === 0
|| this.props.inputElement && this.props.inputElement.textContent !== this.state.queryString) {
if (this.state.queryString === '') {
this.updateSuggestionWithZeroState();
} else {
this.updateValue(this.state.queryString);
}
}
}

Expand Down Expand Up @@ -255,18 +257,9 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
this.suggestionStore.updateSuggestions(suggestionsArray, 0);
}
} else if (suggestionsPromiseLike && suggestionsPromiseLike.then) {
if (!this.loadingTimer) {
this.loadingTimer = this._async.setTimeout(
() =>
this.setState({
suggestionsLoading: true
}),
500
);
}

// Clear suggestions
this.suggestionStore.updateSuggestions([]);
this.setState({
suggestionsLoading: true
});

if (updatedValue !== undefined) {
this.setState({
Expand All @@ -289,6 +282,7 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
} else {
this.suggestionStore.updateSuggestions(newSuggestions);
this.setState({
suggestionsVisible: newSuggestions.length > 0,
suggestionsLoading: false
});
}
Expand Down Expand Up @@ -483,9 +477,11 @@ export class BaseFloatingPicker<T, P extends IBaseFloatingPickerProps<T>> extend
}

private _onResolveSuggestions(updatedValue: string): void {
let suggestions: T[] | PromiseLike<T[]> = this.props.onResolveSuggestions(updatedValue, this.props.selectedItems);
let suggestions: T[] | PromiseLike<T[]> | null = this.props.onResolveSuggestions(updatedValue, this.props.selectedItems);

this.updateSuggestionsList(suggestions, updatedValue);
if (suggestions !== null) {
this.updateSuggestionsList(suggestions, updatedValue);
}
}

@autobind
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export interface IBaseFloatingPickerProps<T> extends React.Props<any> {
* Returns the already selected items so the resolver can filter them out.
* If used in conjunction with resolveDelay this will ony kick off after the delay throttle.
*/
onResolveSuggestions: (filter: string, selectedItems?: T[]) => T[] | PromiseLike<T[]>;
onResolveSuggestions: (filter: string, selectedItems?: T[]) => T[] | PromiseLike<T[]> | null;

/**
* A callback for when the input has been changed
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
.editingInput {
border: 0px;
outline: none;
width: 100%;

&::-ms-clear {
display: none;
}
}

.editingContainer {
margin: 4px;
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
/* tslint:disable */
import * as React from 'react';
/* tslint:enable */
import { BaseComponent, KeyCodes, autobind, getId, getNativeProps, inputProperties } from '../../../../Utilities';
import { BaseComponent, KeyCodes, autobind, getId, getNativeProps, inputProperties, css } from '../../../../Utilities';
import { FloatingPeoplePicker, IBaseFloatingPickerProps } from '../../../../FloatingPicker';
import { ISelectedPeopleItemProps } from '../SelectedPeopleList';
import { IExtendedPersonaProps } from '../SelectedPeopleList';
Expand Down Expand Up @@ -49,7 +49,7 @@ export class EditingItem extends BaseComponent<IEditingSelectedPeopleItemProps,
const itemId = getId();
const nativeProps = getNativeProps(this.props, inputProperties);
return (
<div aria-labelledby={ 'editingItemPersona-' + itemId } className={ 'ms-EditingItem' }>
<div aria-labelledby={ 'editingItemPersona-' + itemId } className={ css('ms-EditingItem', styles.editingContainer) }>
<input
{ ...nativeProps}
ref={ this._resolveInputRef }
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,10 @@
/* tslint:disable */
import * as React from 'react';
/* tslint:enable */
import { BaseComponent, autobind, css, getId } from '../../../../Utilities';
import { BaseComponent, css, getId } from '../../../../Utilities';
import { Persona, PersonaSize } from 'office-ui-fabric-react/lib/Persona';
import { ISelectedPeopleItemProps } from '../SelectedPeopleList';
import { IconButton } from 'office-ui-fabric-react/lib/Button';
import { ContextualMenu, DirectionalHint } from 'office-ui-fabric-react/lib/ContextualMenu';
import * as stylesImport from './ExtendedSelectedItem.scss';
// tslint:disable-next-line:no-any
const styles: any = stylesImport;
Expand Down Expand Up @@ -46,7 +45,6 @@ export class ExtendedSelectedItem extends BaseComponent<ISelectedPeopleItemProps
data-selection-index={ index }
role={ 'listitem' }
aria-labelledby={ 'selectedItemPersona-' + itemId }
onContextMenu={ this._onClick }
>
<div hidden={ !item.canExpand || onExpandItem === undefined }>
<IconButton
Expand Down Expand Up @@ -75,16 +73,6 @@ export class ExtendedSelectedItem extends BaseComponent<ISelectedPeopleItemProps
ariaLabel={ removeButtonAriaLabel }
/>
</div >
{ this.state.contextualMenuVisible ? (
<ContextualMenu
items={ this.props.menuItems }
shouldFocusOnMount={ true }
target={ this.persona }
onDismiss={ this._onCloseContextualMenu }
directionalHint={ DirectionalHint.bottomAutoEdge }
/>)
: null
}
</div >);
}

Expand All @@ -97,15 +85,4 @@ export class ExtendedSelectedItem extends BaseComponent<ISelectedPeopleItemProps
}
};
}

@autobind
private _onClick(ev: React.MouseEvent<HTMLElement>): void {
ev.preventDefault();
this.setState({ contextualMenuVisible: true });
}

@autobind
private _onCloseContextualMenu(ev: Event): void {
this.setState({ contextualMenuVisible: false });
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* tslint:disable */
import * as React from 'react';
/* tslint:enable */
import { BaseComponent, autobind } from '../../../../Utilities';
import { IExtendedPersonaProps } from '../SelectedPeopleList';
import { ContextualMenu, DirectionalHint } from 'office-ui-fabric-react/lib/ContextualMenu';
import { IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu';
import { IBaseProps } from 'office-ui-fabric-react/lib/Utilities';

export interface IPeoplePickerItemState {
contextualMenuVisible: boolean;
}

export interface ISelectedItemWithContextMenuProps extends IBaseProps {
renderedItem: JSX.Element;
beginEditing?: (item: IExtendedPersonaProps) => void;
menuItems: IContextualMenuItem[];
item: IExtendedPersonaProps;
}

export class SelectedItemWithContextMenu extends BaseComponent<ISelectedItemWithContextMenuProps, IPeoplePickerItemState> {
protected itemElement: HTMLElement;

constructor(props: ISelectedItemWithContextMenuProps) {
super(props);
this.state = { contextualMenuVisible: false };
}

public render(): JSX.Element {
return (
<div
ref={ this._resolveRef('itemElement') }
onContextMenu={ this._onClick }
>
{ this.props.renderedItem }
{ this.state.contextualMenuVisible ? (
<ContextualMenu
items={ this.props.menuItems }
shouldFocusOnMount={ true }
target={ this.itemElement }
onDismiss={ this._onCloseContextualMenu }
directionalHint={ DirectionalHint.bottomLeftEdge }
/>)
: null
}
</div >);
}

@autobind
private _onClick(ev: React.MouseEvent<HTMLElement>): void {
ev.preventDefault();
if (this.props.beginEditing && !this.props.item.isValid) {
this.props.beginEditing(this.props.item);
} else {
this.setState({ contextualMenuVisible: true });
}
}

@autobind
private _onCloseContextualMenu(ev: Event): void {
this.setState({ contextualMenuVisible: false });
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { BaseSelectedItemsList } from '../BaseSelectedItemsList';
import { IBaseSelectedItemsListProps, ISelectedItemProps } from '../BaseSelectedItemsList.types';
import { IPersonaProps } from 'office-ui-fabric-react/lib/Persona';
import { ExtendedSelectedItem } from './Items/ExtendedSelectedItem';
import { SelectedItemWithContextMenu } from './Items/SelectedItemWithContextMenu';
import { autobind, IRenderFunction } from '../../../Utilities';
import { IContextualMenuItem } from 'office-ui-fabric-react/lib/ContextualMenu';
import { IBaseFloatingPickerProps } from '../../../FloatingPicker';
Expand All @@ -20,7 +21,6 @@ export interface IExtendedPersonaProps extends IPersonaProps {

export interface ISelectedPeopleItemProps extends ISelectedItemProps<IExtendedPersonaProps> {
onExpandItem?: () => void;
menuItems: IContextualMenuItem[];
renderPersonaCoin?: IRenderFunction<IPersonaProps>;
renderPrimaryText?: IRenderFunction<IPersonaProps>;
}
Expand Down Expand Up @@ -93,10 +93,28 @@ export class SelectedPeopleList extends BasePeopleSelectedItemsList {
);
} else {
let onRenderItem = this.props.onRenderItem as (props: ISelectedPeopleItemProps) => JSX.Element;
return onRenderItem({ ...props });
let renderedItem = onRenderItem(props);
return (
props.menuItems.length > 0 ?
(
<SelectedItemWithContextMenu
renderedItem={ renderedItem }
beginEditing={ this._beginEditing }
menuItems={ this._createMenuItems(props.item) }
item={ props.item }
/>
)
: renderedItem
);
}
}

@autobind
private _beginEditing(item: IExtendedPersonaProps): void {
item.isEditing = true;
this.forceUpdate();
}

@autobind
// tslint:disable-next-line:no-any
private _completeEditing(oldItem: any, newItem: any): void {
Expand All @@ -111,35 +129,38 @@ export class SelectedPeopleList extends BasePeopleSelectedItemsList {
if (this.props.editMenuItemText && this.props.getEditingItemText) {
menuItems.push({
key: 'Edit',
name: this.props.editMenuItemText ? this.props.editMenuItemText : 'Edit',
name: this.props.editMenuItemText,
onClick: (ev: React.MouseEvent<HTMLElement>, menuItem: IContextualMenuItem) => {
(menuItem.data as IExtendedPersonaProps).isEditing = true;
this.forceUpdate();
this._beginEditing(menuItem.data);
},
data: item,
});
}

menuItems.push(
{
key: 'Remove',
name: this.props.removeMenuItemText ? this.props.removeMenuItemText : 'Remove',
onClick: (ev: React.MouseEvent<HTMLElement>, menuItem: IContextualMenuItem) => {
this.removeItem(menuItem.data as ISelectedItemProps<IExtendedPersonaProps>);
},
data: item,
},
{
if (this.props.removeMenuItemText) {
menuItems.push(
{
key: 'Remove',
name: this.props.removeMenuItemText,
onClick: (ev: React.MouseEvent<HTMLElement>, menuItem: IContextualMenuItem) => {
this.removeItem(menuItem.data as ISelectedItemProps<IExtendedPersonaProps>);
},
data: item,
});
}

if (this.props.copyMenuItemText) {
menuItems.push({
key: 'Copy',
name: this.props.copyMenuItemText ? this.props.copyMenuItemText : 'Copy',
name: this.props.copyMenuItemText,
onClick: (ev: React.MouseEvent<HTMLElement>, menuItem: IContextualMenuItem) => {
if (this.props.onCopyItems) {
(this.copyItems as (items: IExtendedPersonaProps[]) => void)([menuItem.data] as IExtendedPersonaProps[]);
}
},
data: item,
},
);
});
}

return menuItems;
}
Expand Down

0 comments on commit 1340339

Please sign in to comment.