Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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> {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This almost feels too specific, could you style a button with contextualmenuitems to meet your needs?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right. I will revisit this and try and create a better abstraction to fit my needs

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 {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why use begin and complete editing? You should be able to pass a dismiss function to contextualmenu that will handle some of this.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The contextual menu will not be open throughout the duration of this, so I don't think using this dismiss function is correct here.

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