UNPKG

@microsoft/mgt

Version:
546 lines • 20.6 kB
/** * ------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. * See License in the project root for license information. * ------------------------------------------------------------------------------------------- */ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) { var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; return c > 3 && r && Object.defineProperty(target, key, r), r; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; import { customElement, html, property } from 'lit-element'; import { repeat } from 'lit-html/directives/repeat'; import { Providers } from '../../Providers'; import { ProviderState } from '../../providers/IProvider'; import '../../styles/fabric-icon-font'; import { debounce } from '../../utils/Utils'; import '../mgt-person/mgt-person'; import { MgtTemplatedComponent } from '../templatedComponent'; import { styles } from './mgt-people-picker-css'; /** * Web component used to search for people from the Microsoft Graph * * @export * @class MgtPicker * @extends {MgtTemplatedComponent} */ let MgtPeoplePicker = class MgtPeoplePicker extends MgtTemplatedComponent { constructor() { super(); /** * containing object of MgtPersonDetails. * @type {MgtPersonDetails} */ this.people = null; /** * determining how many people to show in list. * @type {number} */ this.showMax = 6; /** * array of user picked people. * @type {Array<any>} */ this.selectedPeople = []; // User input in search this._userInput = ''; // if search is still loading don't load "people not found" state this.showLoading = false; this.isLoading = false; // tracking of user arrow key input for selection this.arrowSelectionCount = 0; } /** * Array of styles to apply to the element. The styles should be defined * user the `css` tag function. */ static get styles() { return styles; } /** * Synchronizes property values when attributes change. * * @param {*} name * @param {*} oldValue * @param {*} newValue * @memberof MgtPersonCard */ attributeChangedCallback(att, oldval, newval) { super.attributeChangedCallback(att, oldval, newval); if (att === 'group-id' && oldval !== newval) { this.findGroup(); } } /** * Invoked when the element is first updated. Implement to perform one time work on the element after update. * Checks if group-id is present * @memberof MgtPeoplePicker */ firstUpdated() { if (this.groupId) { Providers.onProviderUpdated(() => this.findGroup()); this.findGroup(); } } /** * Invoked on each update to perform rendering tasks. This method must return a lit-html TemplateResult. * Setting properties inside this method will not trigger the element to update. * @returns * @memberof MgtPeoplePicker */ render() { return (this.renderTemplate('default', { people: this.people }) || html ` <div class="people-picker" @blur=${this.lostFocus}> <div class="people-picker-input" @click=${this.gainedFocus}> ${this.renderSelectedPeople()} </div> <div class="people-list-separator"></div> ${this.renderPeopleList()} </div> `); } /** * Focuses the input element when focus is called * * @param {FocusOptions} [options] * @memberof MgtPeoplePicker */ focus(options) { const peopleInput = this.renderRoot.querySelector('.people-selected-input'); if (peopleInput) { peopleInput.focus(options); } } /** * Queries the microsoft graph for a user based on the user id and adds them to the selectedPeople array * * @param {[string]} an array of user ids to add to selectedPeople * @returns {Promise<void>} * @memberof MgtPeoplePicker */ selectUsersById(userIds) { return __awaiter(this, void 0, void 0, function* () { const provider = Providers.globalProvider; const client = Providers.globalProvider.graph; if (provider && provider.state === ProviderState.SignedIn) { // tslint:disable-next-line: forin for (const id in userIds) { try { const personDetails = yield client.getUser(userIds[id]); this.addPerson(personDetails); // tslint:disable-next-line: no-empty } catch (e) { } } } }); } /** * Adds debounce method for set delay on user input */ onUserKeyUp(event) { if (event.keyCode === 40 || event.keyCode === 38) { // keyCodes capture: down arrow (40) and up arrow (38) return; } const input = event.target; if (event.code === 'Escape') { input.value = ''; this._userInput = ''; this.people = []; return; } if (event.code === 'Backspace' && this._userInput.length === 0 && this.selectedPeople.length > 0) { input.value = ''; this._userInput = ''; // remove last person in selected list this.selectedPeople = this.selectedPeople.splice(0, this.selectedPeople.length - 1); // fire selected people changed event this.fireCustomEvent('selectionChanged', this.selectedPeople); return; } this.handleUserSearch(input); } /** * Async query to Graph for members of group if determined by developer. * set's `this.groupPeople` to those members. */ findGroup() { return __awaiter(this, void 0, void 0, function* () { const provider = Providers.globalProvider; if (provider && provider.state === ProviderState.SignedIn) { const client = Providers.globalProvider.graph; this.groupPeople = yield client.getPeopleFromGroup(this.groupId); } }); } /** * Tracks event on user input in search * @param input - input text */ handleUserSearch(input) { if (!this.debouncedSearch) { this.debouncedSearch = debounce(() => { if (this._userInput !== input.value) { this._userInput = input.value; this.loadPersonSearch(this._userInput); this.arrowSelectionCount = 0; } }, 200); } this.debouncedSearch(); } /** * Tracks event on user search (keydown) * @param event - event tracked on user input (keydown) */ onUserKeyDown(event) { if (event.keyCode === 40 || event.keyCode === 38) { // keyCodes capture: down arrow (40) and up arrow (38) this.handleArrowSelection(event); if (this._userInput.length > 0) { event.preventDefault(); } } if (event.code === 'Tab' || event.code === 'Enter') { if (this.people.length) { event.preventDefault(); } this.addPerson(this.people[this.arrowSelectionCount]); event.target.value = ''; } } /** * Tracks user key selection for arrow key selection of people * @param event - tracks user key selection */ handleArrowSelection(event) { if (this.people.length) { // update arrow count if (event.keyCode === 38) { // up arrow if (this.arrowSelectionCount > 0) { this.arrowSelectionCount--; } else { this.arrowSelectionCount = 0; } } if (event.keyCode === 40) { // down arrow if (this.arrowSelectionCount + 1 !== this.people.length && this.arrowSelectionCount + 1 < this.showMax) { this.arrowSelectionCount++; } else { this.arrowSelectionCount = 0; } } const peopleList = this.renderRoot.querySelector('.people-list'); // reset background color // tslint:disable-next-line: prefer-for-of for (let i = 0; i < peopleList.children.length; i++) { peopleList.children[i].setAttribute('class', 'list-person people-person-list'); } // set selected background peopleList.children[this.arrowSelectionCount].setAttribute('class', 'list-person people-person-list-fill'); } } /** * Tracks when user selects person from picker * @param person - contains details pertaining to selected user * @param event - tracks user event */ addPerson(person) { if (person) { this._userInput = ''; const duplicatePeople = this.selectedPeople.filter(p => { return p.id === person.id; }); if (duplicatePeople.length === 0) { this.selectedPeople.push(person); this.fireCustomEvent('selectionChanged', this.selectedPeople); this.people = []; } } this.gainedFocus(); } /** * Async method which query's the Graph with user input * @param name - user input or name of person searched */ loadPersonSearch(name) { return __awaiter(this, void 0, void 0, function* () { if (name.length) { name = name.toLowerCase(); const provider = Providers.globalProvider; let people; if (provider && provider.state === ProviderState.SignedIn) { this.isLoading = true; setTimeout(() => { this.showLoading = this.isLoading; }, 400); const client = Providers.globalProvider.graph; // filtering groups if (this.groupId) { people = this.groupPeople; } else { people = yield client.findPerson(name); } if (people) { people = people.filter(person => { return person.displayName.toLowerCase().indexOf(name) !== -1; }); } this.people = this.filterPeople(people); this.isLoading = false; this.showLoading = false; } } else { this.people = []; } }); } /** * Filters people searched from already selected people * @param people - array of people returned from query to Graph */ filterPeople(people) { // check if people need to be updated // ensuring people list is displayed // find ids from selected people if (people) { const idFilter = this.selectedPeople.map(el => { return el.id; }); // filter id's const filtered = people.filter(person => { return idFilter.indexOf(person.id) === -1; }); return filtered; } } /** * Removes person from selected people * @param person - person and details pertaining to user selected */ removePerson(person) { const filteredPersonArr = this.selectedPeople.filter(p => { return p.id !== person.id; }); this.selectedPeople = filteredPersonArr; this.fireCustomEvent('selectionChanged', this.selectedPeople); } renderErrorMessage() { return html ` <div class="message-parent"> <div label="search-error-text" aria-label="We didn't find any matches." class="search-error-text"> We didn't find any matches. </div> </div> `; } renderLoadingMessage() { return html ` <div class="message-parent"> <div label="search-error-text" aria-label="loading" class="loading-text"> ...... </div> </div> `; } renderSelectedPeople() { let peopleList; let inputClass = 'input-search-start'; if (this.selectedPeople.length > 0) { inputClass = 'input-search'; peopleList = html ` ${this.selectedPeople.slice(0, this.selectedPeople.length).map(person => html ` <div class="people-person"> ${this.renderTemplate('selected-person', { person }, `selected-${person.id}`) || this.renderSelectedPerson(person)} <div class="CloseIcon" @click="${() => this.removePerson(person)}">\uE711</div> </div> `)} `; } else { peopleList = null; } // tslint:disable return html ` <div class="people-selected-list"> ${peopleList} <div class="${inputClass}"> <input id="people-picker-input" class="people-selected-input" type="text" placeholder="Start typing a name" label="people-picker-input" aria-label="people-picker-input" role="input" .value="${this._userInput}" @keydown="${this.onUserKeyDown}" @keyup="${this.onUserKeyUp}" /> </div> </div> `; } // tslint:enable gainedFocus() { const peopleList = this.renderRoot.querySelector('.people-list'); const peopleInput = this.renderRoot.querySelector('.people-selected-input'); peopleInput.focus(); peopleInput.select(); if (peopleList) { // Mouse is focused on input peopleList.setAttribute('style', 'display:block'); } } lostFocus() { const peopleList = this.renderRoot.querySelector('.people-list'); if (peopleList) { peopleList.setAttribute('style', 'display:none'); } } renderHighlightText(person) { const peoples = person; const highlightLocation = peoples.displayName.toLowerCase().indexOf(this._userInput.toLowerCase()); if (highlightLocation !== -1) { // no location if (highlightLocation === 0) { // highlight is at the beginning of sentence peoples.first = ''; peoples.highlight = peoples.displayName.slice(0, this._userInput.length); peoples.last = peoples.displayName.slice(this._userInput.length, peoples.displayName.length); } else if (highlightLocation === peoples.displayName.length) { // highlight is at end of the sentence peoples.first = peoples.displayName.slice(0, highlightLocation); peoples.highlight = peoples.displayName.slice(highlightLocation, peoples.displayName.length); peoples.last = ''; } else { // highlight is in middle of sentence peoples.first = peoples.displayName.slice(0, highlightLocation); peoples.highlight = peoples.displayName.slice(highlightLocation, highlightLocation + this._userInput.length); peoples.last = peoples.displayName.slice(highlightLocation + this._userInput.length, peoples.displayName.length); } } return html ` <div> <span class="people-person-text">${peoples.first}</span ><span class="people-person-text highlight-search-text">${peoples.highlight}</span ><span class="people-person-text">${peoples.last}</span> </div> `; } renderPeopleList() { let content; if (this.showLoading) { content = this.renderTemplate('loading', null, 'loading') || this.renderLoadingMessage(); } else if (this.people) { const people = this.people.slice(0, this.showMax); if (!this.isLoading && people.length === 0 && this._userInput.length > 0) { content = this.renderTemplate('error', null, 'error') || this.renderErrorMessage(); } else { if (people[0]) { people[0].isSelected = 'fill'; } content = this.renderPeople(people); } } return html ` <div class="people-list"> ${content} </div> `; } renderPeople(people) { return html ` ${repeat(people, person => person.id, person => html ` <li class="list-person ${person.isSelected === 'fill' ? 'people-person-list-fill' : 'people-person-list'}" @click="${() => this.addPerson(person)}" > ${this.renderTemplate('person', { person }, person.id) || this.renderPerson(person)} </li> `)} `; } renderPerson(person) { return html ` <mgt-person .personDetails=${person} .personImage=${'@'}></mgt-person> <div class="people-person-text-area" id="${person.displayName}"> ${this.renderHighlightText(person)} <span class="people-person-job-title">${person.jobTitle}</span> </div> `; } renderSelectedPerson(person) { return html ` <mgt-person class="selected-person" .personDetails=${person} .personImage=${'@'} show-name person-card="click" ></mgt-person> `; } }; __decorate([ property({ attribute: 'people', type: Object }) ], MgtPeoplePicker.prototype, "people", void 0); __decorate([ property({ attribute: 'show-max', type: Number }) ], MgtPeoplePicker.prototype, "showMax", void 0); __decorate([ property({ attribute: 'group-id', type: String }) ], MgtPeoplePicker.prototype, "groupId", void 0); __decorate([ property({ attribute: 'selected-people', type: Array }) ], MgtPeoplePicker.prototype, "selectedPeople", void 0); __decorate([ property() ], MgtPeoplePicker.prototype, "_userInput", void 0); __decorate([ property() ], MgtPeoplePicker.prototype, "showLoading", void 0); __decorate([ property() ], MgtPeoplePicker.prototype, "isLoading", void 0); MgtPeoplePicker = __decorate([ customElement('mgt-people-picker') ], MgtPeoplePicker); export { MgtPeoplePicker }; //# sourceMappingURL=mgt-people-picker.js.map