From 6d1860675531510654bb49b3939bf746a22fb47a Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Fri, 2 Jun 2017 08:25:36 -0700 Subject: [PATCH 01/31] Add ComboBox functionality --- .../src/components/App/AppState.tsx | 803 ++++++------ .../Components/ComboBoxComponentPage.tsx | 33 + .../office-ui-fabric-react/src/ComboBox.ts | 1 + .../src/components/ComboBox/ComboBox.Props.ts | 56 + .../src/components/ComboBox/ComboBox.scss | 266 ++++ .../src/components/ComboBox/ComboBox.test.tsx | 225 ++++ .../src/components/ComboBox/ComboBox.tsx | 1122 +++++++++++++++++ .../src/components/ComboBox/ComboBoxPage.tsx | 59 + .../examples/ComboBox.Basic.Example.scss | 5 + .../examples/ComboBox.Basic.Example.tsx | 195 +++ .../src/components/ComboBox/index.ts | 2 + .../pickers/AutoFill/BaseAutoFill.Props.ts | 5 + .../pickers/AutoFill/BaseAutoFill.tsx | 18 +- .../DynamicAutoFill/DynamicAutoFill.Props.ts | 13 + .../DynamicAutoFill/DynamicAutoFill.test.tsx | 133 ++ .../DynamicAutoFill/DynamicAutoFill.tsx | 110 ++ .../src/components/pickers/index.ts | 2 + .../src/demo/AppDefinition.tsx | 6 + packages/office-ui-fabric-react/src/index.ts | 1 + 19 files changed, 2648 insertions(+), 407 deletions(-) create mode 100644 apps/fabric-website/src/pages/Components/ComboBoxComponentPage.tsx create mode 100644 packages/office-ui-fabric-react/src/ComboBox.ts create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/index.ts create mode 100644 packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts create mode 100644 packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx create mode 100644 packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx diff --git a/apps/fabric-website/src/components/App/AppState.tsx b/apps/fabric-website/src/components/App/AppState.tsx index f74ad8aa2111e..d96b7835c5a3b 100644 --- a/apps/fabric-website/src/components/App/AppState.tsx +++ b/apps/fabric-website/src/components/App/AppState.tsx @@ -1,398 +1,405 @@ -import * as React from 'react'; - -// Props -import { INavPage } from '../Nav/Nav.Props'; -import { ComponentPage } from '../ComponentPage/ComponentPage'; -import { PageHeader } from '../PageHeader/PageHeader'; -import { Spinner } from 'office-ui-fabric-react/lib/Spinner'; - -export interface IAppState { - appTitle: string; - pages: INavPage[]; -} - -function LoadingComponent(props: any): JSX.Element { - return ( - - - - ); -} - -export const AppState: IAppState = { - appTitle: 'Office UI Fabric', - pages: [ - { - title: 'Fabric', - url: '#/', - className: 'fabricPage', - component: require('../../pages/HomePage/HomePage').HomePage, - isHomePage: true - }, - { - title: 'Get started', - url: '#/get-started', - className: 'getStartedPage', - component: require('../../pages/GetStarted/GetStartedPage').GetStartedPage, - }, - { - title: 'Styles', - url: '#/styles', - className: 'stylesPage', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Overviews/StylesPage').StylesPage)), - pages: [ - { - title: 'Animations', - url: '#/styles/animations', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/AnimationsPage/AnimationsPage').AnimationsPage)) - }, - { - title: 'Brand icons', - url: '#/styles/brand-icons', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/BrandIconsPage/BrandIconsPage').BrandIconsPage)) - }, - { - title: 'Colors', - url: '#/styles/colors', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/ColorsPage/ColorsPage').ColorsPage)) - }, - { - title: 'Icons', - url: '#/styles/icons', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/IconsPage/IconsPage').IconsPage)) - }, - { - title: 'Layout', - url: '#/styles/layout', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/LayoutPage/LayoutPage').LayoutPage)) - }, - { - title: 'Localization', - url: '#/styles/localization', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/LocalizationPage/LocalizationPage').LocalizationPage)) - }, - { - title: 'Typography', - url: '#/styles/typography', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/TypographyPage/TypographyPage').TypographyPage)) - }, - { - title: 'Utilities', - url: '#/styles/utilities', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/UtilitiesPage/UtilitiesPage').UtilitiesPage)) - } - - ] - }, - { - title: 'Components', - url: '#/components', - className: 'componentsPage', - component: require('../../pages/Overviews/ComponentsPage').ComponentsPage, - pages: [ - { - title: 'Breadcrumb', - url: '#/components/breadcrumb', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/BreadcrumbComponentPage').BreadcrumbComponentPage)) - }, - { - title: 'Button', - url: '#/components/button', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ButtonComponentPage').ButtonComponentPage)), - - }, - { - title: 'Callout', - url: '#/components/callout', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/CalloutComponentPage').CalloutComponentPage)) - - }, - { - title: 'Checkbox', - url: '#/components/checkbox', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/CheckboxComponentPage').CheckboxComponentPage)) - }, - { - title: 'ChoiceGroup', - url: '#/components/choicegroup', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ChoiceGroupComponentPage').ChoiceGroupComponentPage)) - - }, - { - title: 'ColorPicker', - url: '#/components/colorpicker', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ColorPickerComponentPage').ColorPickerComponentPage)) - }, - { - title: 'CommandBar', - url: '#/components/commandbar', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/CommandBarComponentPage').CommandBarComponentPage)) - }, - { - title: 'ContextualMenu', - url: '#/components/contextualmenu', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ContextualMenuComponentPage').ContextualMenuComponentPage)) - - }, - { - title: 'DatePicker', - url: '#/components/datepicker', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DatePickerComponentPage').DatePickerComponentPage)) - }, - { - title: 'DetailsList', - url: '#/components/detailslist', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DetailsListComponentPage').DetailsListComponentPage)) - }, - { - title: 'Dialog', - url: '#/components/dialog', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DialogComponentPage').DialogComponentPage)) - }, - { - title: 'DocumentCard', - url: '#/components/documentcard', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DocumentCardComponentPage').DocumentCardComponentPage)) - }, - { - title: 'Dropdown', - url: '#/components/dropdown', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DropdownComponentPage').DropdownComponentPage)) - - }, - { - title: 'Facepile', - url: '#/components/facepile', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/FacepileComponentPage').FacepileComponentPage)) - - }, - { - title: 'GroupedList', - url: '#/components/groupedlist', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/GroupedListComponentPage').GroupedListComponentPage)) - - }, - { - title: 'Image', - url: '#/components/image', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ImageComponentPage').ImageComponentPage)) - }, - { - title: 'Label', - url: '#/components/label', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/LabelComponentPage').LabelComponentPage)) - }, - { - title: 'Layer', - url: '#/components/layer', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/LayerComponentPage').LayerComponentPage)) - - }, - { - title: 'Link', - url: '#/components/link', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/LinkComponentPage').LinkComponentPage)) - }, - { - title: 'List', - url: '#/components/list', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ListComponentPage').ListComponentPage)) - }, - { - title: 'MessageBar', - url: '#/components/messagebar', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/MessageBarComponentPage').MessageBarComponentPage)) - }, - { - title: 'Modal', - url: '#/components/modal', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ModalComponentPage').ModalComponentPage)) - }, - { - title: 'Nav', - url: '#/components/nav', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/NavComponentPage').NavComponentPage)) - }, - { - title: 'Overlay', - url: '#/components/overlay', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/OverlayComponentPage').OverlayComponentPage)) - }, - { - title: 'Panel', - url: '#/components/panel', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PanelComponentPage').PanelComponentPage)) - }, - { - title: 'Persona', - url: '#/components/persona', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PersonaComponentPage').PersonaComponentPage)) - }, - { - title: 'Pickers', - url: '#/components/pickers', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PickersComponentPage').PickersComponentPage)) - }, - { - title: 'PeoplePicker', - url: '#/components/peoplepicker', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PeoplePickerComponentPage').PeoplePickerComponentPage)) - }, - { - title: 'Pivot', - url: '#/components/pivot', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PivotComponentPage').PivotComponentPage)) - }, - { - title: 'ProgressIndicator', - url: '#/components/progressindicator', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ProgressIndicatorComponentPage').ProgressIndicatorComponentPage)) - }, - { - title: 'Rating', - url: '#/components/rating', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/RatingComponentPage').RatingComponentPage)) - }, - { - title: 'SearchBox', - url: '#/components/searchbox', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SearchBoxComponentPage').SearchBoxComponentPage)) - }, - { - title: 'Slider', - url: '#/components/slider', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SliderComponentPage').SliderComponentPage)) - }, - { - title: 'Spinner', - url: '#/components/spinner', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SpinnerComponentPage').SpinnerComponentPage)) - }, - { - title: 'TextField', - url: '#/components/textfield', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/TextFieldComponentPage').TextFieldComponentPage)) - }, - { - title: 'Toggle', - url: '#/components/toggle', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ToggleComponentPage').ToggleComponentPage)) - }, - { - title: 'Utilities', - url: '#/components/utilities', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ComponentUtilitiesPage').ComponentUtilitiesPage)), - pages: [ - { - title: 'FocusTrapZone', - url: '#/components/focustrapzone', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/FocusTrapZoneUtilityPage').FocusTrapZoneUtilityPage)) - }, - { - title: 'FocusZone', - url: '#/components/focuszone', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/FocusZoneUtilityPage').FocusZoneUtilityPage)) - }, - { - title: 'MarqueeSelection', - url: '#/components/marqueeselection', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/MarqueeSelectionUtilityPage').MarqueeSelectionUtilityPage)) - }, - { - title: 'Selection', - url: '#/components/selection', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SelectionUtilityPage').SelectionUtilityPage)) - }, - { - title: 'Themes', - url: '#/components/themes', - component: () => , - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ThemesUtilityPage').ThemesUtilityPage)) - } - ] - } - ] - }, - { - title: 'Resources', - url: '#/resources', - className: 'resourcesPage', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/ResourcesPage/ResourcesPage').ResourcesPage)) - }, - { - title: 'Blog', - url: '#/blog', - className: 'blogPage', - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/BlogPage/BlogPage').BlogPage)) - }, - { - title: 'Blog Post', - url: '#/blog/blog-post', - className: 'blogPostPage', - isHiddenFromMainNav: true, - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/BlogPage/BlogPost').BlogPost)) - }, - { - title: 'Fabric JS', - url: '#/fabric-js', - className: 'fabricJsPage', - isHiddenFromMainNav: true, - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Interstitials/FabricJSPage').FabricJSPage)) - }, - { - title: 'Angular JS', - url: '#/angular-js', - className: 'angularJsPage', - isHiddenFromMainNav: true, - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Interstitials/AngularJSPage').AngularJSPage)) - }, - { - title: 'Fabric iOS', - url: '#/fabric-ios', - className: 'fabricIosPage', - isHiddenFromMainNav: true, - getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Interstitials/FabricIOSPage').FabricIOSPage)) - } - ] -}; +import * as React from 'react'; + +// Props +import { INavPage } from '../Nav/Nav.Props'; +import { ComponentPage } from '../ComponentPage/ComponentPage'; +import { PageHeader } from '../PageHeader/PageHeader'; +import { Spinner } from 'office-ui-fabric-react/lib/Spinner'; + +export interface IAppState { + appTitle: string; + pages: INavPage[]; +} + +function LoadingComponent(props: any): JSX.Element { + return ( + + + + ); +} + +export const AppState: IAppState = { + appTitle: 'Office UI Fabric', + pages: [ + { + title: 'Fabric', + url: '#/', + className: 'fabricPage', + component: require('../../pages/HomePage/HomePage').HomePage, + isHomePage: true + }, + { + title: 'Get started', + url: '#/get-started', + className: 'getStartedPage', + component: require('../../pages/GetStarted/GetStartedPage').GetStartedPage, + }, + { + title: 'Styles', + url: '#/styles', + className: 'stylesPage', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Overviews/StylesPage').StylesPage)), + pages: [ + { + title: 'Animations', + url: '#/styles/animations', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/AnimationsPage/AnimationsPage').AnimationsPage)) + }, + { + title: 'Brand icons', + url: '#/styles/brand-icons', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/BrandIconsPage/BrandIconsPage').BrandIconsPage)) + }, + { + title: 'Colors', + url: '#/styles/colors', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/ColorsPage/ColorsPage').ColorsPage)) + }, + { + title: 'Icons', + url: '#/styles/icons', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/IconsPage/IconsPage').IconsPage)) + }, + { + title: 'Layout', + url: '#/styles/layout', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/LayoutPage/LayoutPage').LayoutPage)) + }, + { + title: 'Localization', + url: '#/styles/localization', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/LocalizationPage/LocalizationPage').LocalizationPage)) + }, + { + title: 'Typography', + url: '#/styles/typography', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/TypographyPage/TypographyPage').TypographyPage)) + }, + { + title: 'Utilities', + url: '#/styles/utilities', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Styles/UtilitiesPage/UtilitiesPage').UtilitiesPage)) + } + + ] + }, + { + title: 'Components', + url: '#/components', + className: 'componentsPage', + component: require('../../pages/Overviews/ComponentsPage').ComponentsPage, + pages: [ + { + title: 'Breadcrumb', + url: '#/components/breadcrumb', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/BreadcrumbComponentPage').BreadcrumbComponentPage)) + }, + { + title: 'Button', + url: '#/components/button', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ButtonComponentPage').ButtonComponentPage)), + + }, + { + title: 'Callout', + url: '#/components/callout', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/CalloutComponentPage').CalloutComponentPage)) + + }, + { + title: 'Checkbox', + url: '#/components/checkbox', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/CheckboxComponentPage').CheckboxComponentPage)) + }, + { + title: 'ChoiceGroup', + url: '#/components/choicegroup', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ChoiceGroupComponentPage').ChoiceGroupComponentPage)) + + }, + { + title: 'ColorPicker', + url: '#/components/colorpicker', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ColorPickerComponentPage').ColorPickerComponentPage)) + }, + { + title: 'ComboBox', + url: '#/components/ComboBox', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ComboBoxComponentPage').ComboBoxComponentPage)) + + }, + { + title: 'CommandBar', + url: '#/components/commandbar', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/CommandBarComponentPage').CommandBarComponentPage)) + }, + { + title: 'ContextualMenu', + url: '#/components/contextualmenu', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ContextualMenuComponentPage').ContextualMenuComponentPage)) + + }, + { + title: 'DatePicker', + url: '#/components/datepicker', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DatePickerComponentPage').DatePickerComponentPage)) + }, + { + title: 'DetailsList', + url: '#/components/detailslist', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DetailsListComponentPage').DetailsListComponentPage)) + }, + { + title: 'Dialog', + url: '#/components/dialog', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DialogComponentPage').DialogComponentPage)) + }, + { + title: 'DocumentCard', + url: '#/components/documentcard', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DocumentCardComponentPage').DocumentCardComponentPage)) + }, + { + title: 'Dropdown', + url: '#/components/dropdown', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/DropdownComponentPage').DropdownComponentPage)) + + }, + { + title: 'Facepile', + url: '#/components/facepile', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/FacepileComponentPage').FacepileComponentPage)) + + }, + { + title: 'GroupedList', + url: '#/components/groupedlist', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/GroupedListComponentPage').GroupedListComponentPage)) + + }, + { + title: 'Image', + url: '#/components/image', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ImageComponentPage').ImageComponentPage)) + }, + { + title: 'Label', + url: '#/components/label', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/LabelComponentPage').LabelComponentPage)) + }, + { + title: 'Layer', + url: '#/components/layer', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/LayerComponentPage').LayerComponentPage)) + + }, + { + title: 'Link', + url: '#/components/link', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/LinkComponentPage').LinkComponentPage)) + }, + { + title: 'List', + url: '#/components/list', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ListComponentPage').ListComponentPage)) + }, + { + title: 'MessageBar', + url: '#/components/messagebar', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/MessageBarComponentPage').MessageBarComponentPage)) + }, + { + title: 'Modal', + url: '#/components/modal', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ModalComponentPage').ModalComponentPage)) + }, + { + title: 'Nav', + url: '#/components/nav', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/NavComponentPage').NavComponentPage)) + }, + { + title: 'Overlay', + url: '#/components/overlay', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/OverlayComponentPage').OverlayComponentPage)) + }, + { + title: 'Panel', + url: '#/components/panel', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PanelComponentPage').PanelComponentPage)) + }, + { + title: 'Persona', + url: '#/components/persona', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PersonaComponentPage').PersonaComponentPage)) + }, + { + title: 'Pickers', + url: '#/components/pickers', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PickersComponentPage').PickersComponentPage)) + }, + { + title: 'PeoplePicker', + url: '#/components/peoplepicker', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PeoplePickerComponentPage').PeoplePickerComponentPage)) + }, + { + title: 'Pivot', + url: '#/components/pivot', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/PivotComponentPage').PivotComponentPage)) + }, + { + title: 'ProgressIndicator', + url: '#/components/progressindicator', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ProgressIndicatorComponentPage').ProgressIndicatorComponentPage)) + }, + { + title: 'Rating', + url: '#/components/rating', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/RatingComponentPage').RatingComponentPage)) + }, + { + title: 'SearchBox', + url: '#/components/searchbox', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SearchBoxComponentPage').SearchBoxComponentPage)) + }, + { + title: 'Slider', + url: '#/components/slider', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SliderComponentPage').SliderComponentPage)) + }, + { + title: 'Spinner', + url: '#/components/spinner', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SpinnerComponentPage').SpinnerComponentPage)) + }, + { + title: 'TextField', + url: '#/components/textfield', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/TextFieldComponentPage').TextFieldComponentPage)) + }, + { + title: 'Toggle', + url: '#/components/toggle', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ToggleComponentPage').ToggleComponentPage)) + }, + { + title: 'Utilities', + url: '#/components/utilities', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ComponentUtilitiesPage').ComponentUtilitiesPage)), + pages: [ + { + title: 'FocusTrapZone', + url: '#/components/focustrapzone', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/FocusTrapZoneUtilityPage').FocusTrapZoneUtilityPage)) + }, + { + title: 'FocusZone', + url: '#/components/focuszone', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/FocusZoneUtilityPage').FocusZoneUtilityPage)) + }, + { + title: 'MarqueeSelection', + url: '#/components/marqueeselection', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/MarqueeSelectionUtilityPage').MarqueeSelectionUtilityPage)) + }, + { + title: 'Selection', + url: '#/components/selection', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/SelectionUtilityPage').SelectionUtilityPage)) + }, + { + title: 'Themes', + url: '#/components/themes', + component: () => , + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Components/ThemesUtilityPage').ThemesUtilityPage)) + } + ] + } + ] + }, + { + title: 'Resources', + url: '#/resources', + className: 'resourcesPage', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/ResourcesPage/ResourcesPage').ResourcesPage)) + }, + { + title: 'Blog', + url: '#/blog', + className: 'blogPage', + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/BlogPage/BlogPage').BlogPage)) + }, + { + title: 'Blog Post', + url: '#/blog/blog-post', + className: 'blogPostPage', + isHiddenFromMainNav: true, + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/BlogPage/BlogPost').BlogPost)) + }, + { + title: 'Fabric JS', + url: '#/fabric-js', + className: 'fabricJsPage', + isHiddenFromMainNav: true, + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Interstitials/FabricJSPage').FabricJSPage)) + }, + { + title: 'Angular JS', + url: '#/angular-js', + className: 'angularJsPage', + isHiddenFromMainNav: true, + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Interstitials/AngularJSPage').AngularJSPage)) + }, + { + title: 'Fabric iOS', + url: '#/fabric-ios', + className: 'fabricIosPage', + isHiddenFromMainNav: true, + getComponent: cb => require.ensure([], (require) => cb(require('../../pages/Interstitials/FabricIOSPage').FabricIOSPage)) + } + ] +}; diff --git a/apps/fabric-website/src/pages/Components/ComboBoxComponentPage.tsx b/apps/fabric-website/src/pages/Components/ComboBoxComponentPage.tsx new file mode 100644 index 0000000000000..dd4467690c113 --- /dev/null +++ b/apps/fabric-website/src/pages/Components/ComboBoxComponentPage.tsx @@ -0,0 +1,33 @@ +import * as React from 'react'; +import { ComboBoxPage } from 'office-ui-fabric-react/lib/components/ComboBox/ComboBoxPage'; +import { PageHeader } from '../../components/PageHeader/PageHeader'; +import { ComponentPage } from '../../components/ComponentPage/ComponentPage'; + +export class ComboBoxComponentPage extends React.Component { + public render() { + return ( +
+ + + + +
+ ); + } +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/ComboBox.ts b/packages/office-ui-fabric-react/src/ComboBox.ts new file mode 100644 index 0000000000000..c9b81e057c5f2 --- /dev/null +++ b/packages/office-ui-fabric-react/src/ComboBox.ts @@ -0,0 +1 @@ +export * from './components/ComboBox/index'; diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts new file mode 100644 index 0000000000000..31f7695fd69e1 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -0,0 +1,56 @@ +import { IDropdownOption, IDropdownProps } from '../../Dropdown'; +import { IIconProps } from '../../Icon'; + +export interface IComboBox { + +} + +export interface IComboBoxProps extends IDropdownProps { + + /** + * Collection of options for this ComboBox + */ + options?: IComboBoxOption[]; + + /** + * Callback issues when either: + * 1) the selected option changes + * 2) a manually edited value is submitted. In this case there may not be a matched option if allowFreeform is also true + * (and hence only value would be true, the other parameter would be null in this case) + */ + onChanged?: (option?: IComboBoxOption, index?: number, value?: string) => void; + + /** + * Callback issued when the options should be resolved, if they have been updated or + * if they need to be passed in the first time + */ + onResolveOptions?: () => IComboBoxOption[] | PromiseLike; + + /** + * Whether the ComboBox is free form, meaning that the user input is not bound to provided items. Defaults to false. + */ + allowFreeform?: boolean; + + /** + * Whether the ComboBox auto completes. As the user is inputing text, it will be suggested potential matches from the list of items. If + * the combo box is expanded, this will also scroll to the suggested item, and give it a selected style. Defaults to false. + */ + autoComplete?: boolean; + + /** + * Value to show in the input, does not have to map to a combobox option + */ + value?: string; + + /** + * The IconProps to use for the button aspect of the combobox + */ + buttonIconProps?: IIconProps; +} + +export interface IComboBoxOption extends IDropdownOption { + /** + * Font-family associated with this option. + */ + fontFamily?: string; +} diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss new file mode 100644 index 0000000000000..66336cb1cec35 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss @@ -0,0 +1,266 @@ +@import '../../common/common'; + +// Copyright (c) Microsoft. All rights reserved. Licensed under the MIT license. See LICENSE in the project root for license information. + +// +// Office UI Fabric +// -------------------------------------------------- +// ComboBox styles + +$ComboBox-selectedItem-bg: $ms-color-neutralQuaternaryAlt; +$ComboBox-selectedItem-hover-bg: $ms-color-neutralLighter; +$ComboBox-height: 32px; +$ComboBox-caretDown-width: 32px; +$ComboBox-item-height: 36px; + +// Mixin for high contrast mode link states +@mixin highContrastListItemState { + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + border-color: $ms-color-contrastBlackSelected; + color: $ms-color-black; + + &:focus { + border-color: $ms-color-black; + } + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + border-color: $ms-color-contrastWhiteSelected; + color: $ms-color-white; + } + + @include highContrastAdjust(); +} + +.root { + @include ms-u-normalize; + @include ms-font-m; + color: $ms-color-neutralPrimary; + + margin-bottom: 10px; + position: relative; + outline: 0; + user-select: none; + background: $ms-color-white; + border: 1px solid $ms-color-neutralTertiaryAlt; + cursor: text ; + display: block; + height: $ComboBox-height; + line-height: $ComboBox-height - 2px; // height minus the border + @include padding(0, $ComboBox-caretDown-width, 0, 0); + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + + + + &:hover, + &.focused { + &.wrapper + { + border-color: $ms-color-themePrimary; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteSelected; + } + } + + &.readOnly { + .input, + .caretDown { + background-color: $ms-color-neutralLighter; + } + } + } + + &.wrapperForError { + border-color: $ms-color-error; + } + + :global(.ms-Label) { + display: inline-block; + margin-bottom: 8px; + } +} + +//== State: A disabled ComboBox +.root.rootIsDisabled { + &.wrapper, + .input, + .caretDown { + background-color: $ms-color-neutralLighter; + border-color: $ms-color-neutralLighter; + color: $ms-color-neutralTertiary; + cursor: default; + + @media screen and (-ms-high-contrast: active) { + border-color: $ms-color-contrastBlackDisabled; + color: $ms-color-contrastBlackDisabled; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border-color: $ms-color-contrastWhiteDisabled; + color: $ms-color-contrastWhiteDisabled; + } + } +} + +.input { + box-sizing: border-box; + width: 100%; + height: 100%; + border: none; + outline: none; + font: inherit; + text-overflow: ellipsis; + padding-left: 12px; + + &:hover { + background-color: $ms-color-neutralLight; + color: $ms-color-neutralPrimary; + } + + &::selection { + background-color: $ms-color-neutralSecondaryAlt; + color: $ms-color-white; + } +} + +.caretDown { + color: $ms-color-neutralDark; + font-size: $ms-icon-size-s; + position: absolute; + height: $ComboBox-height; + line-height: $ComboBox-height - 2px; // height minus the border + width: $ComboBox-caretDown-width; + text-align: center; + cursor: default; + + &:hover{ + background-color: $ms-color-neutralQuaternaryAlt; + } + + &:active{ + background-color: $ms-color-neutralTertiaryAlt; + } +} + +.callout { + box-shadow: 0 0px 5px 0px rgba(0, 0, 0, 0.4); + border: 1px solid $ms-color-neutralLight; + max-height: 576px; +} + +.errorMessage{ + color: $ms-color-error; + &::before { + content: '* '; + } +} + +.items { + display: block; +} + +// Container for the ComboBox items, displayed as a panel on small screens. +.item { + background: transparent; + box-sizing: border-box; + cursor: pointer; + display: block; + width: 100%; + height: auto; + min-height: $ComboBox-item-height; + line-height: 20px; + padding: 5px 16px; + position: relative; + border: 1px solid transparent; + word-wrap: break-word; + overflow-wrap: break-word; + text-align: left; + + @media screen and (-ms-high-contrast: active) { + border-color: $ms-color-black; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border-color: $ms-color-white; + } + + &:hover { + background-color: $ComboBox-selectedItem-hover-bg; + color: $ms-color-black; + + @include highContrastListItemState; + } + + @include focus-border(); + + &:focus { + background-color: $ms-color-neutralLighter; + } + + &:active { + background-color: $ComboBox-selectedItem-hover-bg; + color: $ms-color-black; + } + + &.itemIsDisabled { + background: $ms-color-white; + color: $ms-color-neutralTertiary; + cursor: default; + } + :global(.ms-Button-flexContainer) { + justify-content: flex-start; + } +} + +// A selected ComboBox item +.item.itemIsSelected { + background-color: $ComboBox-selectedItem-bg; + color: $ms-color-black; + + &:hover { + background-color: $ComboBox-selectedItem-bg; + } + + @include focus-border(); + + @include highContrastListItemState; +} + +.header { + @include ms-font-m; + font-weight: $ms-font-weight-semibold; + color: $ms-color-themePrimary; + background: none; + border: none; + height: $ComboBox-item-height; + line-height: $ComboBox-item-height; + cursor: default; + padding: 0px 16px; + user-select: none; + @include text-align(left); +} + +.divider { + height: 1px; + background-color: $dividerColor; +} + +.optionText { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: 0px; + max-width: 100%; + word-wrap: break-word; + overflow-wrap: break-word; + margin: 1px; +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx new file mode 100644 index 0000000000000..d430e420f28e1 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -0,0 +1,225 @@ +/* tslint:disable:no-unused-variable */ +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +/* tslint:enable:no-unused-variable */ +import * as ReactTestUtils from 'react-addons-test-utils'; +import { mount, shallow } from 'enzyme'; + +let { expect } = chai; + +import { ComboBox } from './ComboBox'; +import { IComboBoxOption } from './ComboBox.Props'; + +const DEFAULT_OPTIONS: IComboBoxOption[] = [ + { key: '1', text: '1' }, + { key: '2', text: '2' }, + { key: '3', text: '3' } +]; + +const DEFAULT_OPTIONS2: IComboBoxOption[] = [ + { key: '1', text: 'One' }, + { key: '2', text: 'Foo' }, + { key: '3', text: 'Bar' } +]; + +describe('ComboBox', () => { + + it('Can flip between enabled and disabled.', () => { + let wrapper = shallow( + ); + let comboBoxRoot = wrapper.find('.ms-ComboBox'); + + expect(comboBoxRoot.find('.is-disabled').length).equals(0, `shouldn't be disabled`); + expect(comboBoxRoot.find('[data-is-interactable=true]').length).equals(1, 'data-is-focusable="true"'); + + wrapper = shallow( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + + expect(comboBoxRoot.find('.is-disabled').length).equals(1, `should be disabled`); + expect(comboBoxRoot.find('[data-is-interactable=false]').length).equals(1, 'data-is-focusable="false"'); + }); + + it('Renders no selected item in default case', () => { + + let wrapper = mount( + ); + let comboBoxRoot = wrapper.find('.ms-ComboBox'); + let inputElement = comboBoxRoot.find('[role="combobox"]'); + + expect(inputElement.text()).equals(''); + }); + + it('Renders a selected item in uncontrolled case', () => { + let wrapper = mount( + ); + let comboBoxRoot = wrapper.find('.ms-ComboBox'); + let inputElement = comboBoxRoot.find('input'); + + expect(inputElement.props().value).equals('1'); + }); + + it('Renders a selected item in controlled case', () => { + let wrapper = mount( + ); + let comboBoxRoot = wrapper.find('.ms-ComboBox'); + let inputElement = comboBoxRoot.find('input'); + + expect(inputElement.props().value).equals('1'); + }); + + it('Renders a default value with options', () => { + let wrapper = mount( + ); + let comboBoxRoot = wrapper.find('.ms-ComboBox'); + let inputElement = comboBoxRoot.find('input'); + + expect(inputElement.props().value).equals('1'); + }); + + it('Renders a default value with no options', () => { + let wrapper = mount( + ); + let comboBoxRoot = wrapper.find('.ms-ComboBox'); + let inputElement = comboBoxRoot.find('input'); + + expect(inputElement.props().value).equals('1'); + }); + + it('Can change items in uncontrolled case', () => { + let comboBoxRoot; + + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + let buttonElement = comboBoxRoot.find('button'); + buttonElement.simulate('click'); + let secondItemElement = wrapper.getDOMNode().ownerDocument.querySelector('.ms-ComboBox-item[data-index="1"]'); + ReactTestUtils.Simulate.click(secondItemElement); + } + finally { + let inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('2'); + } + }); + +// Text cases for AutoComplete: True, AllowFreeform: True + it('Can insert text in uncontrolled case with autoComplete and allowFreeform on', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: {value: 'f'} }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('Foo'); + } + }); + + it('Can insert text in uncontrolled case with autoComplete on and allowFreeform off', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: {value: 'f'} }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('Foo'); + } + }); + + it('Can insert text in uncontrolled case with autoComplete off and allowFreeform on', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: {value: 'f'} }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('f'); + } + }); + + it('Can insert text in uncontrolled case with autoComplete and allowFreeform off', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: {value: 'f'} }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('One'); + } + }); + +}); \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx new file mode 100644 index 0000000000000..60bf14bcfc810 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -0,0 +1,1122 @@ +import * as React from 'react'; +import { IComboBoxProps, IComboBoxOption } from './ComboBox.Props'; +import { DropdownMenuItemType } from '../../Dropdown'; +import { DirectionalHint } from '../../common/DirectionalHint'; +import { Callout } from '../../Callout'; +import { Label } from '../../Label'; +import { + CommandButton, + IconButton +} from '../../Button'; +import { DynamicAutoFill } from '../pickers/AutoFill/DynamicAutoFill/DynamicAutoFill'; +import { BaseAutoFill } from '../pickers/AutoFill/BaseAutoFill'; +import { + BaseComponent, + KeyCodes, + autobind, + css, + findIndex, + getId +} from '../../Utilities'; +import * as stylesImport from './ComboBox.scss'; +const styles: any = stylesImport; + +export interface IComboBoxState { + + // The open state + isOpen?: boolean; + + // The currently selected index (-1 if no index is selected) + selectedIndex?: number; + + // The focused state of the comboBox + focused?: boolean; + + // This value is used for the autocomplete hint value + suggestedDisplayValue?: string; + + // The options currently available for the callout + currentOptions?: IComboBoxOption[]; + + // when taking input, this will store the index the + // that the options input matches (-1 if no input or match) + currentPendingValueValidIndex?: number; + + // when taking input, this will store + // the actual text that is being entered + currentPendingValue: string; +} + +enum SearchDirection { + backward = -1, + none = 0, + forward = 1 + } + +export class ComboBox extends BaseComponent { + + public static defaultProps = { + options: [], + allowFreeform: false, + autoComplete: true, + buttonIconProps: { iconName: 'ChevronDownSmall' } + }; + + public refs: { + [key: string]: React.ReactInstance, + root: HTMLElement + }; + + // The input aspect of the comboBox + private _comboBox: DynamicAutoFill; + + // The wrapping div of the input and button + private _comboBoxWrapper: HTMLDivElement; + + // The callout element + private _comboBoxMenu: HTMLElement; + + // The menu item element that is currently selected + private _selectedElement: HTMLElement; + + // The base id for the comboBox + private _id: string; + + // This is used to clear any pending autocomplete + // text (used when autocomplete is true and allowFreeform is false) + private readonly _readOnlyPendingAutoCompleteTimeout: number = 1000 /* ms */; + + // After a character is inserted when autocomplete is true and + // allowFreeform is false, remember the task that will clear + // the pending string of characters + private _lastReadOnlyAutoCompleteChangeTimeoutId: number; + + // Promise used when resolving the comboBox options + private currentPromise: PromiseLike; + + constructor(props?: IComboBoxProps) { + super(props); + + this._warnMutuallyExclusive({ + 'defaultSelectedKey': 'selectedKey' + }); + + this._id = props.id || getId('ComboBox'); + + let selectedKey = props.defaultSelectedKey !== undefined ? props.defaultSelectedKey : props.selectedKey; + this._lastReadOnlyAutoCompleteChangeTimeoutId = -1; + + let index: number = this._getSelectedIndex(props.options, selectedKey); + + this.state = { + isOpen: false, + selectedIndex: index, + focused: false, + suggestedDisplayValue: '', + currentOptions: this.props.options, + currentPendingValueValidIndex: -1, + currentPendingValue: '' + }; + } + + public componentDidMount() { + // hook up resolving the options if needed on focus + this._events.on(this._comboBoxWrapper, 'focus', this._onResolveOptions, true); + } + + public componentWillReceiveProps(newProps: IComboBoxProps) { + // In controlled component usage where selectedKey is provided, update the selectedIndex + // and currentOptions state if the key or options change + if (newProps.selectedKey !== undefined && + (newProps.selectedKey !== this.props.selectedKey || newProps.options !== this.props.options)) { + let index: number = this._getSelectedIndex(newProps.options, newProps.selectedKey); + this.setState({ + selectedIndex: index, + currentOptions: newProps.options + }); + } + } + + public componentDidUpdate(prevProps: IComboBoxProps, prevState: IComboBoxState) { + let { + allowFreeform + } = this.props; + let { + isOpen, + focused, + selectedIndex + } = this.state; + + // If we are open, make sure the currently + // selected option is scrolled into view + if (isOpen) { + this._scrollIntoView(); + } + + // If we are open or we are focused but are not the activeElement, + // set focus on the input + if (isOpen || (focused && document.activeElement !== this._comboBox.inputElement)) { + this.focus(); + } + + // If we just opened/closed the menu OR + // updated the selectedIndex with the menu closed OR + // we are focused and are not allowing freeform + // we need to fix up focus and set selection + if (prevState.isOpen !== isOpen || + (!isOpen && prevState.selectedIndex !== selectedIndex) || + (!allowFreeform && focused)) { + this._select(); + } + } + + public componentWillUnmount() { + // remove the eventHanlder that was added in componentDidMount + this._events.off(this._comboBoxWrapper); + } + + // Primary Render + public render() { + let id = this._id; + let { + className, + label, + options, + disabled, + ariaLabel, + required, + errorMessage, + onRenderContainer = this._onRenderContainer, + allowFreeform, + autoComplete, + buttonIconProps + } = this.props; + let { isOpen, selectedIndex, focused, suggestedDisplayValue, currentOptions } = this.state; + let selectedOption = currentOptions[selectedIndex]; + + return ( +
+ { label && ( + + ) } +
0 ? styles.wrapperForError : null), + styles.root, className, { + 'is-open': isOpen, + ['is-disabled ' + styles.rootIsDisabled]: disabled, + 'is-required ': required, + [styles.focused]: focused, + [styles.readOnly]: !allowFreeform + } + ) + } > + = 0 ? (id + '-list' + selectedIndex) : null) } + aria-disabled={ disabled } + aria-owns={ (id + '-list') } + defaultVisibleValue={ this._getVisibleValue() } + suggestedDisplayValue={ suggestedDisplayValue } /> +
+ + { isOpen && ( + onRenderContainer(this.props, this._onRenderContainer) + ) } + { + errorMessage && +
+ { errorMessage } +
+ } +
+ ); + } + + /** + * Set focus on the input + */ + @autobind + public focus() { + if (this._comboBox) { + this._comboBox.focus(); + } + } + + /** + * Get the correct value to pass to the input + * to show to the user based off of the current props and state + * @returns {string} the value to pass to the input + */ + @autobind + private _getVisibleValue(): string { + let { + value, + allowFreeform, + autoComplete + } = this.props; + let { + selectedIndex, + currentPendingValueValidIndex, + currentOptions, + currentPendingValue, + suggestedDisplayValue + } = this.state; + + // If the user passed is a value prop, use that + if (value) { + return value; + } + + let index = selectedIndex; + + if (allowFreeform) { + + // If we are allowing freeform and autocomplete is also true + // and we've got a pending value that matches an option, remember + // the matched option's index + if (autoComplete && this._indexWithinBounds(currentOptions, currentPendingValueValidIndex)) { + index = currentPendingValueValidIndex; + } + + // Since we are allowing freeform, if there is currently a nonempty pending value, use that + // otherwise use the index determined above (falling back to '' if we did not get a valid index) + return currentPendingValue !== '' ? currentPendingValue : + (this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : ''); + } else { + + // If we are not allowing freeform and have a + // valid index that matches the pending value, + // we know we will need some version of the pending value + if (this._indexWithinBounds(currentOptions, currentPendingValueValidIndex)) { + + // If autoComplete is on, return the + // raw pending value, otherwise remember + // the matched option's index + if (autoComplete) { + return currentPendingValue; + } + + index = currentPendingValueValidIndex; + } + + // If we have a valid index then return the text value of that option, + // otherwise return the suggestedDisplayValue + return this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : suggestedDisplayValue; + } + } + + /** + * Is the index within the bounds of the array? + * @param options - options to check if the index is valid for + * @param index - the index to check + * @returns {boolean} - true if the index is valid for the given options, false otherwise + */ + @autobind + private _indexWithinBounds(options: IComboBoxOption[], index: number): boolean { + return index >= 0 && index < options.length; + } + + /** + * Handler for typing changes on the input + * @param updatedValue - the newly changed value + */ + @autobind + private _onInputChange(updatedValue: string) { + this.props.allowFreeform ? + this._processInputChangeWithFreeform(updatedValue) : + this._processInputChangeWithoutFreeform(updatedValue); + } + + /** + * Process the new input's new value when the comboBox + * allows freeform entry + * @param updatedValue - the input's newly changed value + */ + @autobind + private _processInputChangeWithFreeform(updatedValue: string) { + let { + currentOptions + } = this.state; + + // if the new value is empty, nothing needs to be done + if (updatedValue === '') { + return; + } + + // Remember the original value and then, + // make the value lowercase for comparison + let originalUpdatedValue: string = updatedValue; + updatedValue = updatedValue.toLocaleLowerCase(); + + let newSuggestedDisplayValue = ''; + let newCurrentPendingValueValidIndex = -1; + + // If autoComplete is on, attempt to find a match from the available options + if (this.props.autoComplete) { + + // If autoComplete is on, attempt to find a match where the text of an option starts with the updated value + let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== DropdownMenuItemType.Header && option.itemType !== DropdownMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase().indexOf(updatedValue) === 0); + if (items.length > 0) { + // If the user typed out the complete option text, we don't need any suggested display text anymore + newSuggestedDisplayValue = items[0].text.toLocaleLowerCase() !== updatedValue ? items[0].text : ''; + + // remember the index of the match we found + newCurrentPendingValueValidIndex = items[0].index; + } + } else { + + // If autoComplete is off, attempt to find a match only when the value is exactly equal to the text of an option + let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== DropdownMenuItemType.Header && option.itemType !== DropdownMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase() === updatedValue); + + // if we fould a match remember the index + if (items.length === 1) { + newCurrentPendingValueValidIndex = items[0].index; + } + } + + // Set the updated state + this._setPendingInfo(originalUpdatedValue, newCurrentPendingValueValidIndex, newSuggestedDisplayValue); + } + + /** + * Process the new input's new value when the comboBox + * does not allow freeform entry + * @param updatedValue - the input's newly changed value + */ + private _processInputChangeWithoutFreeform(updatedValue: string) { + let { + currentPendingValue, + currentPendingValueValidIndex, + currentOptions, + selectedIndex + } = this.state; + + if (this.props.autoComplete) { + + // If autoComplete is on while allow freeform is off, + // we will remember the keypresses and build up a string to attempt to match + // as long as characters are typed within a the timeout span of each other, + // otherwise we will clear the string and start building a new one on the next keypress. + // Also, only do this processing if we have a non-empty value + if (updatedValue !== '') { + + // If we have a pending autocomplete clearing task, + // we know that the user is typing with keypresses happening + // within the timeout of each other so remove the clearing task + // and continue building the pending value with the udpated value + if (this._lastReadOnlyAutoCompleteChangeTimeoutId > 0) { + this._async.clearTimeout(this._lastReadOnlyAutoCompleteChangeTimeoutId); + this._lastReadOnlyAutoCompleteChangeTimeoutId = -1; + updatedValue = currentPendingValue + updatedValue; + } + + let originalUpdatedValue: string = updatedValue; + updatedValue = updatedValue.toLocaleLowerCase(); + + // If autoComplete is on, attempt to find a match where the text of an option starts with the updated value + let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== DropdownMenuItemType.Header && option.itemType !== DropdownMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase().indexOf(updatedValue) === 0); + + // If we found a match, udpdate the state + if (items.length > 0) { + this._setPendingInfo(originalUpdatedValue, items[0].index, items[0].text); + + // Schedule a timeout to clear the pending value after the timeout span + this._lastReadOnlyAutoCompleteChangeTimeoutId = this._async.setTimeout(() => { this._lastReadOnlyAutoCompleteChangeTimeoutId = -1; }, this._readOnlyPendingAutoCompleteTimeout); + return; + } + } + } + + // If we get here, either autoComplete is on or we did not find a match with autoComplete on. + // Remeber we are not allowing freeform, so at this point, if we have a pending valid value index + // use that; otherwise use the selectedIndex + let index = currentPendingValueValidIndex >= 0 ? currentPendingValueValidIndex : selectedIndex; + + // Since we are not allowing freeform, we need to + // set both the pending and suggested values/index + // to allow us to select all content in the input to + // give the illusion that we are readonly (e.g. freeform off) + this._setPendingInfoFromIndex(index); + } + + /** + * Walk along the options starting at the index, stepping by the delta (positive or negative) + * looking for the next valid selectable index (e.g. skipping headings and dividers) + * @param index - the index to get the next selectable index from + * @param delta - optional delta to step by when finding the next index, defaults to 0 + * @returns {number} - the next valid selectable index. If the new index is outside of the bounds, + * it will snap to the edge of the options array. If delta == 0 and the given index is not selectable + */ + private _getNextSelectableIndex(index: number, searchDirection: SearchDirection): number { + let { currentOptions } = this.state; + + let newIndex = index + searchDirection; + + newIndex = Math.max(0, Math.min(currentOptions.length - 1, newIndex)); + + let option: IComboBoxOption = currentOptions[newIndex]; + + // attempt to skip headers and dividers + if ((option.itemType === DropdownMenuItemType.Header || + option.itemType === DropdownMenuItemType.Divider)) { + + // Should we continue looking for an index to select? + if (searchDirection !== SearchDirection.none && + ((newIndex !== 0 && searchDirection < SearchDirection.none) || + (newIndex !== currentOptions.length - 1 && searchDirection > SearchDirection.none))) { + newIndex = this._getNextSelectableIndex(newIndex, searchDirection); + } else { + // If we cannot perform a useful search just return the index we were given + return index; + } + } + + // We have the next valid selectable index, return it + return newIndex; + } + + /** + * Set the selected index. Note, this is + * the "real" selected index, not the pending selected index + * @param index - the index to set (or the index to set from if a search direction is provided) + * @param searchDirection - the direction to search along the options from the given index + */ + private _setSelectedIndex(index: number, searchDirection: SearchDirection = SearchDirection.none) { + let { onChanged } = this.props; + let { selectedIndex, currentOptions } = this.state; + + // Find the next selectable index, if searchDirection is none + // we will get our starting index back + index = this._getNextSelectableIndex(index, searchDirection); + + // Are we at a new index? If so, update the state, otherwise + // there is nothing to do + if (index !== selectedIndex) { + let option: IComboBoxOption = currentOptions[index]; + + // Set the selected option + this.setState({ + selectedIndex: index + }); + + // Did the creator give us an onChanged callback? + if (onChanged) { + onChanged(option, index); + } + + // if we have a new selected index, + // clear all of the pending info + this._clearPendingInfo(); + } + } + + /** + * Focus (and select) the content of the input + * and set the focused state + */ + @autobind + private _select() { + this._comboBox.inputElement.select(); + + if (!this.state.focused) { + this.setState({ focused: true }); + } + } + + /** + * Callback issued when the options should be resolved, if they have been updated or + * if they need to be passed in the first time. This only does work if an onResolveOptions + * callback was passed in + */ + @autobind + private _onResolveOptions() { + if (this.props.onResolveOptions) { + + // get the options + let newOptions: IComboBoxOption[] | PromiseLike = this.props.onResolveOptions(); + + // the options are either goingto be an array or a promise + let newOptionsArray: IComboBoxOption[] = newOptions as IComboBoxOption[]; + let newOptionsPromiseLike: PromiseLike = newOptions as PromiseLike; + + // Check to see if the returned value is an array, if it is update the state + // If the returned value is not an array then check to see if it's a promise or PromiseLike. If it is then resolve it asynchronously. + if (Array.isArray(newOptionsArray)) { + this.setState({ + currentOptions: newOptionsArray + }); + } else if (newOptionsPromiseLike && newOptionsPromiseLike.then) { + + // Ensure that the promise will only use the callback if it was the most recent one + // and update the state when the promise returns + let promise: PromiseLike = this.currentPromise = newOptionsPromiseLike; + promise.then((newOptionsFromPromise: IComboBoxOption[]) => { + if (promise === this.currentPromise) { + this.setState({ + currentOptions: newOptionsFromPromise + }); + } + }); + } + } + } + + /** + * OnBlur handler. Set the focused state to false + * and submit any pending value + */ + @autobind + private _onBlur() { + if (this.state.focused) { + this.setState({ focused: false }); + this._submitPendingValue(); + } + } + + /** + * Submit a pending value if there is one + */ + @autobind + private _submitPendingValue() { + let { + onChanged, + allowFreeform + } = this.props; + let { + currentPendingValue, + currentPendingValueValidIndex, + currentOptions + } = this.state; + + // If we allow freeform and we have a pending value, we + // need to handle that + if (allowFreeform && currentPendingValue !== '') { + + // Check to see if the user typed an exact match + if (currentPendingValueValidIndex >= 0) { + let pendingOptionText: string = currentOptions[currentPendingValueValidIndex].text.toLocaleLowerCase(); + + // By exact match, that means: our pending value is the same as the the pending option text OR + // the peding option starts with the pending value and we have an "autoComplete" selection + // where the total lenght is equal to pending option length; update the state + if (currentPendingValue.toLocaleLowerCase() === pendingOptionText || + (pendingOptionText.indexOf(currentPendingValue.toLocaleLowerCase()) === 0 && + this._comboBox.isValueSelected && + currentPendingValue.length + (this._comboBox.selectionEnd - this._comboBox.selectionStart) === pendingOptionText.length)) { + this._setSelectedIndex(currentPendingValueValidIndex); + this._clearPendingInfo(); + return; + } + } + + // If we did not find an exact match + // update the state with the pending value + let newFontFamily: string = ''; + if (currentPendingValue.indexOf(' ') > -1) { + newFontFamily = '"' + currentPendingValue + '"'; + } else { + newFontFamily = currentPendingValue; + } + + // add a default fallback font + newFontFamily += ',"Segoe UI",Tahoma,Sans-Serif;'; + + // Create a new option + let newOption: IComboBoxOption = { key: currentPendingValue, text: currentPendingValue, fontFamily: newFontFamily }; + let newOptions: IComboBoxOption[] = [...currentOptions, newOption]; + let newSelectedIndex: number = this._getSelectedIndex(newOptions, currentPendingValue); + + this.setState({ + currentOptions: newOptions, + selectedIndex: newSelectedIndex + }); + + if (onChanged) { + onChanged(null, null, currentPendingValue); + } + } else if (currentPendingValueValidIndex >= 0) { + // Since we are not allowing freeform, we must have a matching + // to be able to update state + this._setSelectedIndex(currentPendingValueValidIndex); + } + + // Finally, clear the pending info + this._clearPendingInfo(); + } + + // Render Callout container and pass in list + @autobind + private _onRenderContainer(props: IComboBoxProps): JSX.Element { + let { + onRenderList = this._onRenderList, + calloutProps + } = this.props; + + return ( + +
+ { onRenderList(props, this._onRenderList) } +
+
+ ); + } + + // Render List of items + @autobind + private _onRenderList(props: IComboBoxProps): JSX.Element { + let { + onRenderItem = this._onRenderItem + } = this.props; + + let id = this._id; + let { selectedIndex } = this.state; + + return ( +
+ { this.state.currentOptions.map((item, index) => onRenderItem({ ...item, index }, this._onRenderItem)) } +
+ ); + } + + // Render items + @autobind + private _onRenderItem(item: IComboBoxOption): JSX.Element { + switch (item.itemType) { + case DropdownMenuItemType.Divider: + return this._renderSeparator(item); + case DropdownMenuItemType.Header: + return this._renderHeader(item); + default: + return this._renderOption(item); + } + } + + // Render separator + private _renderSeparator(item: IComboBoxOption): JSX.Element { + let { index, key } = item; + if (index > 0) { + return
; + } + return null; + } + + private _renderHeader(item: IComboBoxOption): JSX.Element { + let { onRenderOption = this._onRenderOption } = this.props; + return ( +
+ { onRenderOption(item, this._onRenderOption) } +
); + } + + // Render menu item + @autobind + private _renderOption(item: IComboBoxOption): JSX.Element { + let { onRenderOption = this._onRenderOption } = this.props; + let id = this._id; + let isSelected: boolean = this._isOptionSelected(item.index); + return ( + this._onItemClick(item.index) } + role='option' + aria-selected={ isSelected ? 'true' : 'false' } + ariaLabel={ item.text } + > { + { onRenderOption(item, this._onRenderOption) } + + } + + ); + } + + /** + * Use the current valid pending index if it exists OR + * we do not have a valid index and we currently have a pending input value, + * otherwise use the selected index + * */ + @autobind + private _isOptionSelected(index: number): boolean { + let { + currentPendingValueValidIndex, + currentPendingValue, + selectedIndex + } = this.state; + return ((currentPendingValueValidIndex >= 0 || currentPendingValue !== '') ? + currentPendingValueValidIndex === index : selectedIndex === index); + } + + /** + * Scroll the selected element into view + */ + @autobind + private _scrollIntoView() { + if (this._selectedElement) { + let alignToTop = true; + if (this._comboBoxMenu.offsetParent) { + let scrollableParentRect = this._comboBoxMenu.offsetParent.getBoundingClientRect(); + let selectedElementRect = this._selectedElement.offsetParent.getBoundingClientRect(); + + if (scrollableParentRect.top + scrollableParentRect.height <= selectedElementRect.top) { + alignToTop = false; + } + } + + this._selectedElement.offsetParent.scrollIntoView(alignToTop); + } + } + + // Render content of item + @autobind + private _onRenderOption(item: IComboBoxOption): JSX.Element { + return { item.text }; + } + + /** + * Click handler for the menu items + * to select the item and also close the menu + * @param index - the index of the item that was clicked + */ + private _onItemClick(index) { + this._setSelectedIndex(index); + this.setState({ + isOpen: false + }); + } + + /** + * Handles dismissing (cancelling) the menu + */ + @autobind + private _onDismiss() { + + // reset the selected index + // to the last valud state + this._resetSelectedIndex(); + + // close the menu and focus the input + this.setState({ isOpen: false }); + this._comboBox.focus(); + } + + /** + * Get the index of the option that is marked as selected + * @param options - the comboBox options + * @param selectedKey - the known selected key to find + * @returns {number} - the index of the selected option, -1 if not found + */ + private _getSelectedIndex(options: IComboBoxOption[], selectedKey: string | number): number { + return findIndex(options, (option => (option.isSelected || option.selected || (selectedKey != null) && option.key === selectedKey))); + } + + /** + * Reset the selected index by clearing the + * input (of any pending text), clearing the pending state, + * and setting the suggested display value to the last + * selected state text + */ + @autobind + private _resetSelectedIndex() { + this._comboBox.clear(); + this._clearPendingInfo(); + this.setState({ + suggestedDisplayValue: this.state.currentOptions[this.state.selectedIndex].text + }); + } + + /** + * Clears the pending info state + */ + @autobind + private _clearPendingInfo() { + this._setPendingInfo('' /* suggestedDisplayValue */, -1 /* currentPendingValueValidIndex */, '' /* currentPendingValue */); + } + + /** + * Set the pending info + * @param currentPendingValue - new pending value to set + * @param currentPendingValueValidIndex - new pending value index to set + * @param suggestedDisplayValue - new suggest display value to set + */ + @autobind + private _setPendingInfo(currentPendingValue: string, currentPendingValueValidIndex: number, suggestedDisplayValue) { + this.setState({ + currentPendingValue: currentPendingValue, + currentPendingValueValidIndex: currentPendingValueValidIndex, + suggestedDisplayValue: suggestedDisplayValue + }); + } + + /** + * Set the pending info from the given index + * @param index - the index to set the pending info from + */ + @autobind + private _setPendingInfoFromIndex(index: number) { + let option = this.state.currentOptions[index]; + this._setPendingInfo(option.text, index, option.text); + } + + /** + * Sets either the pending info or the + * selected index depending of if the comboBox is open + * @param index - the index to search from + * @param searchDirection - the direction to search + */ + @autobind + private _setInfoForIndexAndDirection(index: number, searchDirection: SearchDirection) { + let { + isOpen, + selectedIndex + } = this.state; + + if (isOpen) { + index = this._getNextSelectableIndex(index, searchDirection); + this._setPendingInfoFromIndex(index); + } else { + this._setSelectedIndex(selectedIndex, searchDirection); + } + } + + /** + * Handle keydown on the input + * @param ev - The keyboard event that was fired + */ + @autobind + private _onInputKeyDown(ev: React.KeyboardEvent) { + let { + disabled, + allowFreeform, + autoComplete + } = this.props; + let { + isOpen, + currentPendingValueValidIndex, + selectedIndex, + currentOptions + } = this.state; + + if (disabled) { + this._handleKeyboardEventWhenDisabled(ev); + return; + } + + let index = currentPendingValueValidIndex >= 0 ? currentPendingValueValidIndex : selectedIndex; + + switch (ev.which) { + case KeyCodes.enter: + // On enter submit the pending value + this._submitPendingValue(); + + // If we are not allowing freeform + // or the comboBox is open, flip the open state + if (!allowFreeform || isOpen) { + this.setState({ + isOpen: !isOpen + }); + } + break; + + case KeyCodes.escape: + case KeyCodes.tab: + // reset the selected index + this._resetSelectedIndex(); + + // Close the menu if opened + if (isOpen) { + this.setState({ + isOpen: false + }); + } + + // Allow TAB to propigate + if (ev.which === KeyCodes.tab) { + return; + } + break; + + case KeyCodes.up: + // Go to the previous option + this._setInfoForIndexAndDirection(index, SearchDirection.backward); + break; + + case KeyCodes.down: + // Expand the comboBox on ALT + DownArrow + if (ev.altKey || ev.metaKey) { + this.setState({ isOpen: true }); + } else { + // Got to the next option + this._setInfoForIndexAndDirection(index, SearchDirection.forward); + } + break; + + case KeyCodes.home: + case KeyCodes.end: + if (allowFreeform) { + return; + } + + // Set the initial values to respond to HOME + // which goes to the first selectable option + index = -1; + let directionToSearch = SearchDirection.forward; + + // If end, update the values to respond to END + // which goes to the last selectable option + if (ev.which === KeyCodes.end) { + index = currentOptions.length; + directionToSearch = SearchDirection.backward; + } + + this._setInfoForIndexAndDirection(index, directionToSearch); + break; + + case KeyCodes.space: + // event handled in _onComboBoxKeyUp + if (!allowFreeform && !autoComplete) { + break; + } + + default: + return; + } + + ev.stopPropagation(); + ev.preventDefault(); + } + + /** + * Handle keyup on the input + * @param ev - the keyboard event that was fired + */ + @autobind + private _onInputKeyUp(ev: React.KeyboardEvent) { + let { + disabled, + allowFreeform, + autoComplete + } = this.props; + + if (disabled) { + this._handleKeyboardEventWhenDisabled(ev); + return; + } + + switch (ev.which) { + case KeyCodes.space: + // If we are not allowing freeform and are not autoComplete + // make space expand/collapse the comboBox + // and allow the event to propagate + if (!allowFreeform && !autoComplete) { + this.setState({ + isOpen: !this.state.isOpen + }); + return; + } + break; + + default: + return; + } + + ev.stopPropagation(); + ev.preventDefault(); + } + + /** + * Handle dismissing the menu and + * eating the required key event when disabled + * @param ev - the keyboard event that was fired + */ + @autobind + private _handleKeyboardEventWhenDisabled(ev: React.KeyboardEvent) { + // If we are disabled, close the menu (if needed) + // and eat all keystokes other than TAB or ESC + if (this.props.disabled) { + if (this.state.isOpen) { + this.setState({ isOpen: false }); + } + + if (ev.which !== KeyCodes.tab && + ev.which !== KeyCodes.escape) { + ev.stopPropagation(); + ev.preventDefault(); + } + } + } + + /** + * Click handler for the button of the comboBox + * and the input when not allowing freeform. This + * toggles the expand/collapse state of the comboBox (if enbled) + */ + @autobind + private _onComboBoxClick() { + let { disabled } = this.props; + let { isOpen } = this.state; + + if (!disabled) { + this.setState({ + isOpen: !isOpen + }); + } + } +} diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx new file mode 100644 index 0000000000000..5ff0a9a76aa46 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx @@ -0,0 +1,59 @@ +import * as React from 'react'; +import { + ExampleCard, + IComponentDemoPageProps, + ComponentPage, + PropertiesTableSet +} from '@uifabric/example-app-base'; +import { ComboBoxBasicExample } from './examples/ComboBox.Basic.Example'; + +const ComboBoxBasicExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx') as string; + +export class ComboBoxPage extends React.Component { + public render() { + return ( + + + + } + propertiesTables={ + ('!raw-loader!office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts') + ] } + /> + } + overview={ +
+

+ A ComboBox is a list in which the selected item is always visible, and the others are visible on demand by clicking a drop-down button. They are used to simplify the design and make a choice within the UI. When closed, only the selected item is visible. When users click the drop-down button, all the options become visible. To change the value, users open the list and click another value or use the arrow keys (up and down) to select a new value. +

+
+ } + bestPractices={ +
+ } + dos={ +
+
    +
  • Use a ComboBox when there are multiple choices that can be collapsed under one title. Or if the list of items is long or when space is constrained.
  • +
  • ComboBoxs contain shortened statements or words.
  • +
  • Use a ComboBox when the selected option is more important than the alternatives (in contrast to radio buttons where all the choices are visible putting more emphasis on the other options).
  • +
+
+ } + donts={ +
+ } + related={ + Fabric JS + } + isHeaderVisible={ this.props.isHeaderVisible }> +
+ ); + } +} diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss new file mode 100644 index 0000000000000..4e86e9a791396 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss @@ -0,0 +1,5 @@ +:global { + .ms-ComboBoxBasicExample { + max-width: 300px; + } +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx new file mode 100644 index 0000000000000..ef521d73238f9 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -0,0 +1,195 @@ +import * as React from 'react'; +import { ComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; +import './ComboBox.Basic.Example.scss'; +import { DropdownMenuItemType } from '../../Dropdown'; +import { + autobind +} from 'office-ui-fabric-react/lib/Utilities'; + +export class ComboBoxBasicExample extends React.Component { + private _testOptions = + [ { key: 'Header', text: 'Theme Fonts', itemType: DropdownMenuItemType.Header }, + { key: 'A', text: 'Arial Black', fontFamily: '"Arial Black", "Arial Black_MSFontService", sans-serif' }, + { key: 'B', text: 'Time New Roman', fontFamily: '"Times New Roman", "Times New Roman_MSFontService", serif' }, + { key: 'C', text: 'Comic Sans MS', fontFamily: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy' }, + { key: 'divider_2', text: '-', itemType: DropdownMenuItemType.Divider }, + { key: 'Header1', text: 'Other Options', itemType: DropdownMenuItemType.Header }, + { key: 'D', text: 'Option d' }, + { key: 'E', text: 'Option e' }, + { key: 'F', text: 'Option f' }, + { key: 'G', text: 'Option g' }, + { key: 'H', text: 'Option h' }, + { key: 'I', text: 'Option i' }, + { key: 'J', text: 'Option j' }, + ]; + + constructor() { + super(); + this.state = { + options: [], + selectedOptionKey: null, + value: 'Calibri' + }; + } + + public render() { + let { options, selectedOptionKey, value } = this.state; + + return ( +
+ + + + + + + + + + + + + + +
+ + ); + } + + @autobind + private _getOptions(): IComboBoxOption[] { + + if (this.state.options.length > 0) { + return this.state.options; + } + + let newOptions = + [ + { key: 'Header', text: 'Theme Fonts', itemType: DropdownMenuItemType.Header }, + { key: 'A', text: 'Arial Black', fontFamily: '"Arial Black", "Arial Black_MSFontService", sans-serif' }, + { key: 'B', text: 'Time New Roman', fontFamily: '"Times New Roman", "Times New Roman_MSFontService", serif' }, + { key: 'C', text: 'Comic Sans MS', fontFamily: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy' }, + { key: 'C1', text: 'Calibri', fontFamily: 'Calibri, Calibri_MSFontService, sans-serif' }, + { key: 'divider_2', text: '-', itemType: DropdownMenuItemType.Divider }, + { key: 'Header1', text: 'Other Options', itemType: DropdownMenuItemType.Header }, + { key: 'D', text: 'Option d' }, + { key: 'E', text: 'Option e' }, + { key: 'F', text: 'Option f' }, + { key: 'G', text: 'Option g' }, + { key: 'H', text: 'Option h' }, + { key: 'I', text: 'Option i' }, + { key: 'J', text: 'Option j' } + ]; + this.setState({ + options: newOptions, + selectedOptionKey: 'C1', + value: null + }); + + return newOptions; + } + + @autobind + private _optionsContainsKey(key: string): boolean { + return false; + } + + @autobind + private _onChanged(option: IComboBoxOption, index: number, value: string) { + if (option != null) { + this.setState({ + selectedOptionKey: option.key, + value: null + }); + } else if (index != null && index >= 0 && index < this.state.options.length) { + this.setState({ + selectedOptionKey: this.state.options[index].key, + value: null + }); + } else if (value !== null) { + if (!this._optionsContainsKey(value)) { + let newFontFamily: string = ''; + if (value.indexOf(' ') > -1) { + newFontFamily = '"' + value + '"'; + } else { + newFontFamily = value; + } + + // add a default fallback font + newFontFamily += ',"Segoe UI",Tahoma,Sans-Serif;'; + + let newOption: IComboBoxOption = { key: value, text: value, fontFamily: newFontFamily }; + + this.setState({ + options: [...this.state.options, newOption], + selectedOptionKey: newOption.key, + value: null + }); + } + } + } +} diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/index.ts b/packages/office-ui-fabric-react/src/components/ComboBox/index.ts new file mode 100644 index 0000000000000..fddc1b1ca5ba7 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/index.ts @@ -0,0 +1,2 @@ +export * from './ComboBox'; +export * from './ComboBox.Props'; diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts index 09f8613191931..b1e8fd7a696a3 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts @@ -59,4 +59,9 @@ export interface IBaseAutoFillProps extends React.HTMLProps) { + protected _onKeyDown(ev: React.KeyboardEvent) { switch (ev.which) { case KeyCodes.backspace: this._autoFillEnabled = false; @@ -147,7 +147,7 @@ export class BaseAutoFill extends BaseComponent) { + protected _onChange(ev: React.FormEvent) { let value: string = (ev.target as HTMLInputElement).value; if (value && (ev.target as HTMLInputElement).selectionStart === value.length && !this._autoFillEnabled && value.length > this._value.length) { this._autoFillEnabled = true; @@ -155,13 +155,13 @@ export class BaseAutoFill extends BaseComponent this._notifyInputChange(newValue)); } - private _doesTextStartWith(text: string, startWith: string) { + protected _doesTextStartWith(text: string, startWith: string) { if (!text || !startWith) { return false; } diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts new file mode 100644 index 0000000000000..0eca41eb7786b --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts @@ -0,0 +1,13 @@ +import { IBaseAutoFill, IBaseAutoFillProps } from '../BaseAutoFill.Props'; + +export interface IDynamicAutoFill extends IBaseAutoFill { + +} + +export interface IDynamicAutoFillProps extends IBaseAutoFillProps { + /** + * the default value to be visible + */ + defaultVisibleValue?: string; + +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx new file mode 100644 index 0000000000000..d5ede9b4c1bf0 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx @@ -0,0 +1,133 @@ +/* tslint:disable:no-unused-variable */ +import * as React from 'react'; +/* tslint:enable:no-unused-variable */ + +import * as ReactDOM from 'react-dom'; +import * as ReactTestUtils from 'react-addons-test-utils'; +import { KeyCodes } from '../../../../Utilities'; +import { DynamicAutoFill } from './DynamicAutoFill'; + +let { assert } = chai; +describe('DynamicAutoFill', () => { + let autoFill: DynamicAutoFill; + let autoFillInput: HTMLInputElement; + let baseNode = document.createElement('div'); + document.body.appendChild(baseNode); + beforeEach(() => { + + let component = ReactDOM.render( + autoFill = c } + suggestedDisplayValue='hello' />, + baseNode + ); + autoFillInput = ReactDOM.findDOMNode(component as React.ReactInstance) as HTMLInputElement; + }); + + it('correctly autofills', (done) => { + ReactDOM.render( + autoFill = c } + onInputValueChange={ + (text) => { + assert(text === 'hel', 'text was ' + text); + assert(autoFill.value === 'hel', 'autoFill value was ' + autoFill.value); + done(); + } + } + suggestedDisplayValue='hello' />, + baseNode + ); + autoFillInput.value = 'hel'; + ReactTestUtils.Simulate.change(autoFillInput); + ReactDOM.render( + autoFill = c } + onInputValueChange={ + (text) => { + assert(text === 'hel', 'text was ' + text); + assert(autoFill.value === 'hel', 'autoFill value was ' + autoFill.value); + done(); + } + } + suggestedDisplayValue='hello' />, baseNode); + assert(autoFill.inputElement.value === 'hello'); + + }); + + it('does not autofill if suggestedDisplayValue does not match input', (done) => { + autoFillInput.value = 'hep'; + ReactTestUtils.Simulate.change(autoFillInput); + ReactDOM.render( + autoFill = c } + onInputValueChange={ + (text) => { + assert(autoFill.value === 'hep', 'text was ' + autoFill.value); + assert(text === 'hep', 'text was ' + text); + assert(autoFill.inputElement.value === 'hep'); + done(); + } + } + suggestedDisplayValue='hello' />, + baseNode + ); + ReactTestUtils.Simulate.change(autoFillInput); + }); + + it('does not autofill if left or right arrow has been pressed', () => { + autoFillInput.value = 'hel'; + ReactTestUtils.Simulate.change(autoFillInput); + + ReactDOM.render( + autoFill = c } + suggestedDisplayValue='hello' />, + baseNode + ); + + ReactTestUtils.Simulate.keyDown(autoFillInput, { keyCode: KeyCodes.left, which: KeyCodes.left }); + + // Because reacttestutils doesn't allow you to enter text normally we need to reset the autofillinput value to hel. + // If we don't the change event will cause the current input value, 'hello', to be set. + autoFillInput.value = 'hel'; + + ReactTestUtils.Simulate.change(autoFillInput); + + assert(autoFill.value === 'hel', 'text was ' + autoFill.value); + assert(autoFill.inputElement.value === 'hel'); + }); + + it('will autofill if keyCode up or down is pressed', () => { + autoFillInput.value = 'hel'; + ReactTestUtils.Simulate.change(autoFillInput); + + ReactDOM.render( + autoFill = c } + suggestedDisplayValue='hello' />, + baseNode + ); + + assert(autoFill.value === 'hel', 'text was ' + autoFill.value); + assert(autoFill.inputElement.value === 'hel'); + + ReactTestUtils.Simulate.keyDown(autoFillInput, { keyCode: KeyCodes.left, which: KeyCodes.left }); + + autoFillInput.value = 'hel'; + + ReactTestUtils.Simulate.keyDown(autoFillInput, { keyCode: KeyCodes.up, which: KeyCodes.up }); + autoFillInput.value = 'hel'; + ReactTestUtils.Simulate.change(autoFillInput); + + ReactDOM.render( + autoFill = c } + suggestedDisplayValue='hello' />, + baseNode + ); + + assert(autoFill.value === 'hel'); + assert(autoFill.inputElement.value === 'hello'); + }); +}); diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx new file mode 100644 index 0000000000000..162a9944c51b5 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx @@ -0,0 +1,110 @@ +import * as React from 'react'; +import { IDynamicAutoFillProps, IDynamicAutoFill } from './DynamicAutoFill.Props'; +import { BaseAutoFill } from '../BaseAutoFill'; +import { IBaseAutoFillProps } from '../BaseAutoFill.Props'; +import { + KeyCodes, + autobind, + getNativeProps, + inputProperties +} from '../../../../Utilities'; + +export interface IDynamicAutoFillState { + displayValue?: string; +} + +const SELECTION_FORWARD = 'forward'; +const SELECTION_BACKWARD = 'backward'; + +export class DynamicAutoFill extends BaseAutoFill implements IDynamicAutoFill { + + public static defaultProps = { + enableAutoFillOnKeyPress: [KeyCodes.down, KeyCodes.up], + defaultVisibleValue: '' + }; + + constructor(props: IBaseAutoFillProps) { + super(props); + + this._value = ''; + this.state = { + displayValue: props.defaultVisibleValue + }; + } + + public componentWillReceiveProps(nextProps: IDynamicAutoFillProps) { + if (nextProps.defaultVisibleValue && nextProps.defaultVisibleValue !== '' && (nextProps.defaultVisibleValue !== nextProps.defaultVisibleValue || this._value !== nextProps.defaultVisibleValue)) { + this._value = nextProps.defaultVisibleValue; + } + if (this._autoFillEnabled && this._doesTextStartWith(nextProps.suggestedDisplayValue, this._value)) { + this.setState({ displayValue: nextProps.suggestedDisplayValue }); + } + } + + public componentDidUpdate() { + let value = this._value; + let { suggestedDisplayValue } = this.props; + let differenceIndex = 0; + if (this._autoFillEnabled && value && suggestedDisplayValue && this._doesTextStartWith(suggestedDisplayValue, value)) { + if (this.props.defaultVisibleValue === this.props.suggestedDisplayValue) { + this._inputElement.setSelectionRange(0, suggestedDisplayValue.length, SELECTION_BACKWARD); + } else { + while (differenceIndex < value.length && value[differenceIndex].toLocaleLowerCase() === suggestedDisplayValue[differenceIndex].toLocaleLowerCase()) { + differenceIndex++; + } + if (differenceIndex > 0) { + this._inputElement.setSelectionRange(differenceIndex, suggestedDisplayValue.length, SELECTION_BACKWARD); + } + } + } + } + + public render() { + let { + displayValue + } = this.state; + + const nativeProps = getNativeProps(this.props, inputProperties); + return ; + } + + @autobind + protected _onKeyDown(ev: React.KeyboardEvent) { + if (this.props.onKeyDown) { + this.props.onKeyDown(ev); + } + + switch (ev.which) { + case KeyCodes.backspace: + this._autoFillEnabled = false; + break; + case KeyCodes.left: + if (this._autoFillEnabled) { + this._autoFillEnabled = false; + } + break; + case KeyCodes.right: + if (this._autoFillEnabled) { + this._autoFillEnabled = false; + } + break; + default: + if (!this._autoFillEnabled) { + if (this.props.enableAutoFillOnKeyPress.indexOf(ev.which) !== -1) { + this._autoFillEnabled = true; + } + } + break; + } + + } +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/index.ts b/packages/office-ui-fabric-react/src/components/pickers/index.ts index 5c3371b8fde4a..25e8450044d4c 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/index.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/index.ts @@ -3,6 +3,8 @@ export * from './Suggestions/Suggestions.Props'; export * from './Suggestions/SuggestionsController'; export * from './AutoFill/BaseAutoFill'; export * from './AutoFill/BaseAutoFill.Props'; +export * from './AutoFill/DynamicAutoFill/DynamicAutoFill'; +export * from './AutoFill/DynamicAutoFill/DynamicAutoFill.Props'; export * from './BasePicker'; export * from './BasePicker.Props'; export * from './PickerItem.Props'; diff --git a/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx b/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx index fc8a48b522e64..bbc88766c8c51 100644 --- a/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx +++ b/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx @@ -52,6 +52,12 @@ export const AppDefinition: IAppDefinition = { name: 'ChoiceGroup', url: '#/examples/choicegroup' }, + { + component: require('../components/ComboBox/ComboBoxPage').ComboBoxPage, + key: 'ComboBox', + name: 'ComboBox', + url: '#/examples/ComboBox' + }, { component: require('../components/CommandBar/CommandBarPage').CommandBarPage, key: 'CommandBar', diff --git a/packages/office-ui-fabric-react/src/index.ts b/packages/office-ui-fabric-react/src/index.ts index c96aa1d436290..97eb301777c17 100644 --- a/packages/office-ui-fabric-react/src/index.ts +++ b/packages/office-ui-fabric-react/src/index.ts @@ -10,6 +10,7 @@ export * from './Callout'; export * from './Checkbox'; export * from './ChoiceGroup'; export * from './ColorPicker'; +export * from './ComboBox'; export * from './CommandBar'; export * from './ContextualMenu'; export * from './DatePicker'; From ec782248c850fce68887fcd5c46fb3be5cbf0753 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Fri, 2 Jun 2017 12:38:54 -0700 Subject: [PATCH 02/31] Make a fix for IE where non-allowFreeform is showing the keypresses... --- .../src/components/ComboBox/ComboBox.scss | 2 +- .../src/components/ComboBox/ComboBox.tsx | 42 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss index 66336cb1cec35..57245f48caf87 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss @@ -35,7 +35,7 @@ $ComboBox-item-height: 36px; } .root { - @include ms-u-normalize; + @include ms-normalize; @include ms-font-m; color: $ms-color-neutralPrimary; diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 60bf14bcfc810..0cf5cfac75de6 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -48,10 +48,10 @@ export interface IComboBoxState { } enum SearchDirection { - backward = -1, - none = 0, - forward = 1 - } + backward = -1, + none = 0, + forward = 1 +} export class ComboBox extends BaseComponent { @@ -231,7 +231,7 @@ export class ComboBox extends BaseComponent { aria-autocomplete={ (!disabled && autoComplete) } role='combobox' aria-readonly={ ((allowFreeform || disabled) ? null : 'true') } - readOnly={ disabled } + readOnly={ disabled || !allowFreeform } aria-labelledby={ (label && (id + '-label')) } aria-label={ ((ariaLabel && !label) && ariaLabel) } aria-describedby={ (id + '-option') } @@ -312,7 +312,7 @@ export class ComboBox extends BaseComponent { // Since we are allowing freeform, if there is currently a nonempty pending value, use that // otherwise use the index determined above (falling back to '' if we did not get a valid index) return currentPendingValue !== '' ? currentPendingValue : - (this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : ''); + (this._indexWithinBounds(currentOptions, index) ? currentOptions[index].text : ''); } else { // If we are not allowing freeform and have a @@ -494,7 +494,7 @@ export class ComboBox extends BaseComponent { // Should we continue looking for an index to select? if (searchDirection !== SearchDirection.none && ((newIndex !== 0 && searchDirection < SearchDirection.none) || - (newIndex !== currentOptions.length - 1 && searchDirection > SearchDirection.none))) { + (newIndex !== currentOptions.length - 1 && searchDirection > SearchDirection.none))) { newIndex = this._getNextSelectableIndex(newIndex, searchDirection); } else { // If we cannot perform a useful search just return the index we were given @@ -537,7 +537,7 @@ export class ComboBox extends BaseComponent { // if we have a new selected index, // clear all of the pending info - this._clearPendingInfo(); + this._clearPendingInfo(); } } @@ -632,8 +632,8 @@ export class ComboBox extends BaseComponent { // where the total lenght is equal to pending option length; update the state if (currentPendingValue.toLocaleLowerCase() === pendingOptionText || (pendingOptionText.indexOf(currentPendingValue.toLocaleLowerCase()) === 0 && - this._comboBox.isValueSelected && - currentPendingValue.length + (this._comboBox.selectionEnd - this._comboBox.selectionStart) === pendingOptionText.length)) { + this._comboBox.isValueSelected && + currentPendingValue.length + (this._comboBox.selectionEnd - this._comboBox.selectionStart) === pendingOptionText.length)) { this._setSelectedIndex(currentPendingValueValidIndex); this._clearPendingInfo(); return; @@ -801,7 +801,7 @@ export class ComboBox extends BaseComponent { selectedIndex } = this.state; return ((currentPendingValueValidIndex >= 0 || currentPendingValue !== '') ? - currentPendingValueValidIndex === index : selectedIndex === index); + currentPendingValueValidIndex === index : selectedIndex === index); } /** @@ -956,6 +956,8 @@ export class ComboBox extends BaseComponent { if (disabled) { this._handleKeyboardEventWhenDisabled(ev); + ev.stopPropagation(); + ev.preventDefault(); return; } @@ -978,7 +980,7 @@ export class ComboBox extends BaseComponent { case KeyCodes.escape: case KeyCodes.tab: // reset the selected index - this._resetSelectedIndex(); + this._resetSelectedIndex(); // Close the menu if opened if (isOpen) { @@ -1036,6 +1038,12 @@ export class ComboBox extends BaseComponent { } default: + // If we are not allowing freeform and + // allowing autoComplete, handle the input + // since we have marked the input as readonly + if (!allowFreeform && autoComplete) { + this._onInputChange(String.fromCharCode(ev.which)); + } return; } @@ -1103,11 +1111,11 @@ export class ComboBox extends BaseComponent { } } - /** - * Click handler for the button of the comboBox - * and the input when not allowing freeform. This - * toggles the expand/collapse state of the comboBox (if enbled) - */ + /** + * Click handler for the button of the comboBox + * and the input when not allowing freeform. This + * toggles the expand/collapse state of the comboBox (if enbled) + */ @autobind private _onComboBoxClick() { let { disabled } = this.props; From 33995661982fb7e960938e5e0effd3a4c6c8751c Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Thu, 8 Jun 2017 06:30:26 -0700 Subject: [PATCH 03/31] Update the PR with code review feedback. Simplified the code a lot, decoupled the shared props/memebers from comboBox and Dropdown, and extended BaseAutoFill which allowed DynamicAutoFill to be removed --- .../src/SelectableOption.ts | 1 + .../src/VisualTestState.ts | 1 + .../src/components/ComboBox/ComboBox.Props.ts | 13 +- .../src/components/ComboBox/ComboBox.test.tsx | 122 ++++++++++++-- .../src/components/ComboBox/ComboBox.tsx | 159 ++++++++++++------ .../ComboBox/ComboBox.visualtest.ts | 29 ++++ .../ComboBox/ComboBoxPage.visualtest.tsx | 29 ++++ .../examples/ComboBox.Basic.Example.tsx | 40 ++--- .../src/components/Dropdown/Dropdown.Props.ts | 124 +------------- .../src/components/Dropdown/Dropdown.tsx | 7 +- .../examples/Dropdown.Basic.Example.tsx | 10 +- .../pickers/AutoFill/BaseAutoFill.Props.ts | 17 ++ .../pickers/AutoFill/BaseAutoFill.tsx | 46 +++-- .../DynamicAutoFill/DynamicAutoFill.Props.ts | 13 -- .../DynamicAutoFill/DynamicAutoFill.test.tsx | 133 --------------- .../DynamicAutoFill/DynamicAutoFill.tsx | 110 ------------ .../src/components/pickers/index.ts | 2 - .../SelectableDroppableText.Props.ts | 93 ++++++++++ .../SelectableOption.Props.ts | 35 ++++ .../src/utilities/selectableOption/index.ts | 2 + 20 files changed, 493 insertions(+), 493 deletions(-) create mode 100644 packages/office-ui-fabric-react/src/SelectableOption.ts create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.visualtest.ts create mode 100644 packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.visualtest.tsx delete mode 100644 packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts delete mode 100644 packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx delete mode 100644 packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx create mode 100644 packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts create mode 100644 packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableOption.Props.ts create mode 100644 packages/office-ui-fabric-react/src/utilities/selectableOption/index.ts diff --git a/packages/office-ui-fabric-react/src/SelectableOption.ts b/packages/office-ui-fabric-react/src/SelectableOption.ts new file mode 100644 index 0000000000000..ec2ae3d50cbd7 --- /dev/null +++ b/packages/office-ui-fabric-react/src/SelectableOption.ts @@ -0,0 +1 @@ +export * from './utilities/selectableOption/index'; diff --git a/packages/office-ui-fabric-react/src/VisualTestState.ts b/packages/office-ui-fabric-react/src/VisualTestState.ts index 9dde96a3b2871..e434f34dfe53a 100644 --- a/packages/office-ui-fabric-react/src/VisualTestState.ts +++ b/packages/office-ui-fabric-react/src/VisualTestState.ts @@ -8,6 +8,7 @@ export const VisualTestState: IVisualTestState = { './Button/ButtonPage.visualtest', './Checkbox/CheckboxPage.visualtest', './ChoiceGroup/ChoiceGroupPage.visualtest', + './ComboBox/ComboBoxPage.visualtest', './CommandBar/CommandBarPage.visualtest', './ContextualMenu/ContextualMenuPage.visualtest', './DetailsList/DetailsListPage.visualtest', diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts index 31f7695fd69e1..766548cf10c51 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -1,11 +1,8 @@ -import { IDropdownOption, IDropdownProps } from '../../Dropdown'; import { IIconProps } from '../../Icon'; +import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../Utilities/selectableOption/SelectableDroppableText.Props'; -export interface IComboBox { - -} - -export interface IComboBoxProps extends IDropdownProps { +export interface IComboBoxProps extends ISelectableDroppableTextProps { /** * Collection of options for this ComboBox @@ -24,7 +21,7 @@ export interface IComboBoxProps extends IDropdownProps { * Callback issued when the options should be resolved, if they have been updated or * if they need to be passed in the first time */ - onResolveOptions?: () => IComboBoxOption[] | PromiseLike; + onResolveOptions?: (options: IComboBoxOption[]) => IComboBoxOption[] | PromiseLike; /** * Whether the ComboBox is free form, meaning that the user input is not bound to provided items. Defaults to false. @@ -48,7 +45,7 @@ export interface IComboBoxProps extends IDropdownProps { buttonIconProps?: IIconProps; } -export interface IComboBoxOption extends IDropdownOption { +export interface IComboBoxOption extends ISelectableOption { /** * Font-family associated with this option. */ diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index d430e420f28e1..7510be94b1357 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -4,7 +4,7 @@ import * as ReactDOM from 'react-dom'; /* tslint:enable:no-unused-variable */ import * as ReactTestUtils from 'react-addons-test-utils'; import { mount, shallow } from 'enzyme'; - +import { KeyCodes } from '../../Utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; @@ -41,7 +41,7 @@ describe('ComboBox', () => { label='testgroup' options={ DEFAULT_OPTIONS } />); - comboBoxRoot = wrapper.find('.ms-ComboBox'); + comboBoxRoot = wrapper.find('.ms-ComboBox'); expect(comboBoxRoot.find('.is-disabled').length).equals(1, `should be disabled`); expect(comboBoxRoot.find('[data-is-interactable=false]').length).equals(1, 'data-is-focusable="false"'); @@ -133,7 +133,6 @@ describe('ComboBox', () => { } }); -// Text cases for AutoComplete: True, AllowFreeform: True it('Can insert text in uncontrolled case with autoComplete and allowFreeform on', () => { let comboBoxRoot; let inputElement; @@ -144,11 +143,11 @@ describe('ComboBox', () => { defaultSelectedKey='1' options={ DEFAULT_OPTIONS2 } autoComplete={ true } - allowFreeform ={ true } + allowFreeform={ true } />); comboBoxRoot = wrapper.find('.ms-ComboBox'); inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: {value: 'f'} }); + inputElement.simulate('change', { target: { value: 'f' } }); } finally { inputElement = comboBoxRoot.find('input'); @@ -166,11 +165,11 @@ describe('ComboBox', () => { defaultSelectedKey='1' options={ DEFAULT_OPTIONS2 } autoComplete={ true } - allowFreeform ={ false } + allowFreeform={ false } />); comboBoxRoot = wrapper.find('.ms-ComboBox'); inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: {value: 'f'} }); + inputElement.simulate('change', { target: { value: 'f' } }); } finally { inputElement = comboBoxRoot.find('input'); @@ -188,11 +187,11 @@ describe('ComboBox', () => { defaultSelectedKey='1' options={ DEFAULT_OPTIONS2 } autoComplete={ false } - allowFreeform ={ true } + allowFreeform={ true } />); comboBoxRoot = wrapper.find('.ms-ComboBox'); inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: {value: 'f'} }); + inputElement.simulate('change', { target: { value: 'f' } }); } finally { inputElement = comboBoxRoot.find('input'); @@ -210,11 +209,73 @@ describe('ComboBox', () => { defaultSelectedKey='1' options={ DEFAULT_OPTIONS2 } autoComplete={ false } - allowFreeform ={ false } + allowFreeform={ false } + />); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: 'f' }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('One'); + } + }); + + it('Can change selected option with keyboard', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: KeyCodes.down }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('Foo'); + } + }); + + it('Cannot insert text while disabled', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: KeyCodes.a }); + } + finally { + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('One'); + } + }); + + it('Cannot change selected option with keyboard while disabled', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); comboBoxRoot = wrapper.find('.ms-ComboBox'); inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: {value: 'f'} }); + inputElement.simulate('keydown', { which: KeyCodes.down }); } finally { inputElement = comboBoxRoot.find('input'); @@ -222,4 +283,43 @@ describe('ComboBox', () => { } }); + it('Cannot expand the menu when clicking on the input while disabled', () => { + let comboBoxRoot; + let inputElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('click'); + } + finally { + expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); + } + }); + + it('Cannot expand the menu when clicking on the button while disabled', () => { + let comboBoxRoot; + let buttonElement; + try { + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + buttonElement = comboBoxRoot.find('button'); + buttonElement.simulate('click'); + } + finally { + expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); + } + }); }); \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 0cf5cfac75de6..de8293ca52860 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -1,6 +1,5 @@ import * as React from 'react'; import { IComboBoxProps, IComboBoxOption } from './ComboBox.Props'; -import { DropdownMenuItemType } from '../../Dropdown'; import { DirectionalHint } from '../../common/DirectionalHint'; import { Callout } from '../../Callout'; import { Label } from '../../Label'; @@ -8,16 +7,18 @@ import { CommandButton, IconButton } from '../../Button'; -import { DynamicAutoFill } from '../pickers/AutoFill/DynamicAutoFill/DynamicAutoFill'; import { BaseAutoFill } from '../pickers/AutoFill/BaseAutoFill'; +import { IBaseAutoFillProps } from '../pickers/AutoFill/BaseAutoFill.Props'; import { - BaseComponent, - KeyCodes, + assign, autobind, + BaseComponent, css, findIndex, - getId + getId, + KeyCodes } from '../../Utilities'; +import { SelectableOptionMenuItemType } from '../../Utilities/selectableOption/SelectableOption.Props'; import * as stylesImport from './ComboBox.scss'; const styles: any = stylesImport; @@ -68,7 +69,7 @@ export class ComboBox extends BaseComponent { }; // The input aspect of the comboBox - private _comboBox: DynamicAutoFill; + private _comboBox: BaseAutoFill; // The wrapping div of the input and button private _comboBoxWrapper: HTMLDivElement; @@ -98,7 +99,9 @@ export class ComboBox extends BaseComponent { super(props); this._warnMutuallyExclusive({ - 'defaultSelectedKey': 'selectedKey' + 'defaultSelectedKey': 'selectedKey', + 'value': 'defaultSelectedKey', + 'selectedKey': 'value' }); this._id = props.id || getId('ComboBox'); @@ -175,6 +178,23 @@ export class ComboBox extends BaseComponent { this._events.off(this._comboBoxWrapper); } + @autobind + private _onAutoFillComponentWillReceiveProps(nextProps: IBaseAutoFillProps): string { + if (this._comboBox === null || this._comboBox === undefined) { + return null; + } + + if (nextProps.defaultVisibleValue && nextProps.defaultVisibleValue !== '' && (nextProps.defaultVisibleValue !== nextProps.defaultVisibleValue || this._comboBox.value !== nextProps.defaultVisibleValue)) { + return nextProps.defaultVisibleValue; + } + + return this._comboBox.value; + } + + private _onAutoFillComponentDidUpdate(props: IBaseAutoFillProps): boolean { + return props.defaultVisibleValue === props.suggestedDisplayValue + } + // Primary Render public render() { let id = this._id; @@ -214,11 +234,11 @@ export class ComboBox extends BaseComponent { } ) } > - { aria-activedescendant={ (isOpen && selectedIndex >= 0 ? (id + '-list' + selectedIndex) : null) } aria-disabled={ disabled } aria-owns={ (id + '-list') } + spellCheck={ false } defaultVisibleValue={ this._getVisibleValue() } - suggestedDisplayValue={ suggestedDisplayValue } /> + suggestedDisplayValue={ suggestedDisplayValue } + onComponentWillReceiveProps={ this._onAutoFillComponentWillReceiveProps } + onComponentDidUpdate={ this._onAutoFillComponentDidUpdate } />
{ isOpen && ( - onRenderContainer(this.props, this._onRenderContainer) + onRenderContainer(assign({}, this.props), this._onRenderContainer) ) } { errorMessage && @@ -342,7 +366,6 @@ export class ComboBox extends BaseComponent { * @param index - the index to check * @returns {boolean} - true if the index is valid for the given options, false otherwise */ - @autobind private _indexWithinBounds(options: IComboBoxOption[], index: number): boolean { return index >= 0 && index < options.length; } @@ -353,6 +376,11 @@ export class ComboBox extends BaseComponent { */ @autobind private _onInputChange(updatedValue: string) { + if (this.props.disabled) { + this._handleInputWhenDisabled(null /* event */); + return; + } + this.props.allowFreeform ? this._processInputChangeWithFreeform(updatedValue) : this._processInputChangeWithoutFreeform(updatedValue); @@ -363,7 +391,6 @@ export class ComboBox extends BaseComponent { * allows freeform entry * @param updatedValue - the input's newly changed value */ - @autobind private _processInputChangeWithFreeform(updatedValue: string) { let { currentOptions @@ -386,7 +413,7 @@ export class ComboBox extends BaseComponent { if (this.props.autoComplete) { // If autoComplete is on, attempt to find a match where the text of an option starts with the updated value - let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== DropdownMenuItemType.Header && option.itemType !== DropdownMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase().indexOf(updatedValue) === 0); + let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== SelectableOptionMenuItemType.Header && option.itemType !== SelectableOptionMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase().indexOf(updatedValue) === 0); if (items.length > 0) { // If the user typed out the complete option text, we don't need any suggested display text anymore newSuggestedDisplayValue = items[0].text.toLocaleLowerCase() !== updatedValue ? items[0].text : ''; @@ -397,7 +424,7 @@ export class ComboBox extends BaseComponent { } else { // If autoComplete is off, attempt to find a match only when the value is exactly equal to the text of an option - let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== DropdownMenuItemType.Header && option.itemType !== DropdownMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase() === updatedValue); + let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== SelectableOptionMenuItemType.Header && option.itemType !== SelectableOptionMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase() === updatedValue); // if we fould a match remember the index if (items.length === 1) { @@ -445,16 +472,20 @@ export class ComboBox extends BaseComponent { updatedValue = updatedValue.toLocaleLowerCase(); // If autoComplete is on, attempt to find a match where the text of an option starts with the updated value - let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== DropdownMenuItemType.Header && option.itemType !== DropdownMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase().indexOf(updatedValue) === 0); + let items = currentOptions.map((item, index) => { return { ...item, index }; }).filter((option) => option.itemType !== SelectableOptionMenuItemType.Header && option.itemType !== SelectableOptionMenuItemType.Divider).filter((option) => option.text.toLocaleLowerCase().indexOf(updatedValue) === 0); // If we found a match, udpdate the state if (items.length > 0) { this._setPendingInfo(originalUpdatedValue, items[0].index, items[0].text); - - // Schedule a timeout to clear the pending value after the timeout span - this._lastReadOnlyAutoCompleteChangeTimeoutId = this._async.setTimeout(() => { this._lastReadOnlyAutoCompleteChangeTimeoutId = -1; }, this._readOnlyPendingAutoCompleteTimeout); - return; } + + // Schedule a timeout to clear the pending value after the timeout span + this._lastReadOnlyAutoCompleteChangeTimeoutId = + this._async.setTimeout( + () => { this._lastReadOnlyAutoCompleteChangeTimeoutId = -1; }, + this._readOnlyPendingAutoCompleteTimeout + ); + return; } } @@ -488,8 +519,8 @@ export class ComboBox extends BaseComponent { let option: IComboBoxOption = currentOptions[newIndex]; // attempt to skip headers and dividers - if ((option.itemType === DropdownMenuItemType.Header || - option.itemType === DropdownMenuItemType.Divider)) { + if ((option.itemType === SelectableOptionMenuItemType.Header || + option.itemType === SelectableOptionMenuItemType.Divider)) { // Should we continue looking for an index to select? if (searchDirection !== SearchDirection.none && @@ -564,23 +595,23 @@ export class ComboBox extends BaseComponent { if (this.props.onResolveOptions) { // get the options - let newOptions: IComboBoxOption[] | PromiseLike = this.props.onResolveOptions(); + let newOptions: IComboBoxOption[] | PromiseLike = this.props.onResolveOptions(assign({}, this.props)); // the options are either goingto be an array or a promise - let newOptionsArray: IComboBoxOption[] = newOptions as IComboBoxOption[]; - let newOptionsPromiseLike: PromiseLike = newOptions as PromiseLike; + // let newOptionsArray: IComboBoxOption[] = newOptions as IComboBoxOption[]; + // let newOptionsPromiseLike: PromiseLike = newOptions as PromiseLike; // Check to see if the returned value is an array, if it is update the state // If the returned value is not an array then check to see if it's a promise or PromiseLike. If it is then resolve it asynchronously. - if (Array.isArray(newOptionsArray)) { + if (Array.isArray(newOptions)) { this.setState({ - currentOptions: newOptionsArray + currentOptions: newOptions }); - } else if (newOptionsPromiseLike && newOptionsPromiseLike.then) { + } else if (newOptions && newOptions.then) { // Ensure that the promise will only use the callback if it was the most recent one // and update the state when the promise returns - let promise: PromiseLike = this.currentPromise = newOptionsPromiseLike; + let promise: PromiseLike = this.currentPromise = newOptions; promise.then((newOptionsFromPromise: IComboBoxOption[]) => { if (promise === this.currentPromise) { this.setState({ @@ -607,7 +638,6 @@ export class ComboBox extends BaseComponent { /** * Submit a pending value if there is one */ - @autobind private _submitPendingValue() { let { onChanged, @@ -697,7 +727,7 @@ export class ComboBox extends BaseComponent { setInitialFocus={ false } >
- { onRenderList(props, this._onRenderList) } + { onRenderList(assign({}, this.props), this._onRenderList) }
); @@ -729,9 +759,9 @@ export class ComboBox extends BaseComponent { @autobind private _onRenderItem(item: IComboBoxOption): JSX.Element { switch (item.itemType) { - case DropdownMenuItemType.Divider: + case SelectableOptionMenuItemType.Divider: return this._renderSeparator(item); - case DropdownMenuItemType.Header: + case SelectableOptionMenuItemType.Header: return this._renderHeader(item); default: return this._renderOption(item); @@ -793,7 +823,6 @@ export class ComboBox extends BaseComponent { * we do not have a valid index and we currently have a pending input value, * otherwise use the selected index * */ - @autobind private _isOptionSelected(index: number): boolean { let { currentPendingValueValidIndex, @@ -807,7 +836,6 @@ export class ComboBox extends BaseComponent { /** * Scroll the selected element into view */ - @autobind private _scrollIntoView() { if (this._selectedElement) { let alignToTop = true; @@ -873,19 +901,24 @@ export class ComboBox extends BaseComponent { * and setting the suggested display value to the last * selected state text */ - @autobind private _resetSelectedIndex() { + let { + selectedIndex, + currentOptions + } = this.state; this._comboBox.clear(); this._clearPendingInfo(); - this.setState({ - suggestedDisplayValue: this.state.currentOptions[this.state.selectedIndex].text - }); + + if (selectedIndex > 0 && selectedIndex < currentOptions.length) { + this.setState({ + suggestedDisplayValue: currentOptions[selectedIndex].text + }); + } } /** * Clears the pending info state */ - @autobind private _clearPendingInfo() { this._setPendingInfo('' /* suggestedDisplayValue */, -1 /* currentPendingValueValidIndex */, '' /* currentPendingValue */); } @@ -896,7 +929,6 @@ export class ComboBox extends BaseComponent { * @param currentPendingValueValidIndex - new pending value index to set * @param suggestedDisplayValue - new suggest display value to set */ - @autobind private _setPendingInfo(currentPendingValue: string, currentPendingValueValidIndex: number, suggestedDisplayValue) { this.setState({ currentPendingValue: currentPendingValue, @@ -909,10 +941,18 @@ export class ComboBox extends BaseComponent { * Set the pending info from the given index * @param index - the index to set the pending info from */ - @autobind private _setPendingInfoFromIndex(index: number) { - let option = this.state.currentOptions[index]; - this._setPendingInfo(option.text, index, option.text); + let { + currentOptions + } = this.state; + + if (index > 0 && index < currentOptions.length) { + let option = currentOptions[index]; + this._setPendingInfo(option.text, index, option.text); + } else { + this._clearPendingInfo(); + } + } /** @@ -921,7 +961,6 @@ export class ComboBox extends BaseComponent { * @param index - the index to search from * @param searchDirection - the direction to search */ - @autobind private _setInfoForIndexAndDirection(index: number, searchDirection: SearchDirection) { let { isOpen, @@ -955,9 +994,7 @@ export class ComboBox extends BaseComponent { } = this.state; if (disabled) { - this._handleKeyboardEventWhenDisabled(ev); - ev.stopPropagation(); - ev.preventDefault(); + this._handleInputWhenDisabled(ev); return; } @@ -1038,11 +1075,18 @@ export class ComboBox extends BaseComponent { } default: + + // are we processing a function key? if so bail out + if (ev.which < 112 /* F1 */ || ev.which > 123 /* F12 */) { + return; + } + // If we are not allowing freeform and - // allowing autoComplete, handle the input + // allowing autoComplete, handle the input here // since we have marked the input as readonly if (!allowFreeform && autoComplete) { this._onInputChange(String.fromCharCode(ev.which)); + break; } return; } @@ -1064,7 +1108,7 @@ export class ComboBox extends BaseComponent { } = this.props; if (disabled) { - this._handleKeyboardEventWhenDisabled(ev); + this._handleInputWhenDisabled(ev); return; } @@ -1094,8 +1138,7 @@ export class ComboBox extends BaseComponent { * eating the required key event when disabled * @param ev - the keyboard event that was fired */ - @autobind - private _handleKeyboardEventWhenDisabled(ev: React.KeyboardEvent) { + private _handleInputWhenDisabled(ev: React.KeyboardEvent) { // If we are disabled, close the menu (if needed) // and eat all keystokes other than TAB or ESC if (this.props.disabled) { @@ -1103,8 +1146,12 @@ export class ComboBox extends BaseComponent { this.setState({ isOpen: false }); } - if (ev.which !== KeyCodes.tab && - ev.which !== KeyCodes.escape) { + // When disabled stop propagation and prevent default + // of the event unless we have a tab, escape, or function key + if (ev !== null && + ev.which !== KeyCodes.tab && + ev.which !== KeyCodes.escape && + (ev.which < 112 /* F1 */ || ev.which > 123 /* F12 */)) { ev.stopPropagation(); ev.preventDefault(); } diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.visualtest.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.visualtest.ts new file mode 100644 index 0000000000000..300231665fe21 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.visualtest.ts @@ -0,0 +1,29 @@ +import { Casper } from '../../visualtest/PhantomCssInterface'; +import { baseUrl } from '../../common/VisualTest'; +import { defaultScreenshot, testRunner, mouseClickScreenshot, mouseMoveScreenshot } from '../../visualtest/RunVisualTest'; +import { IRunVisualTest } from '../../visualtest/IRunVisualTest'; +declare var casper: Casper; +let componentIds: IRunVisualTest[] = []; + +componentIds.push( + { + selector: '.' + 'ms-ComboBox-Input', + fileName: 'comboBox_input', + imageSelector: '.' + 'ms-ComboBox-Container', + commands: [defaultScreenshot, mouseClickScreenshot, mouseMoveScreenshot] + }, + { + selector: '.' + 'ms-ComboBox-Button', + fileName: 'comboBox_button', + imageSelector: '.' + 'ms-comboBox-Container', + commands: [defaultScreenshot, mouseClickScreenshot, mouseMoveScreenshot] + }, +); + +casper. + start(baseUrl + 'comboBox'). + then(() => { + testRunner(componentIds); + }); + +casper.run(() => { casper.test.done(); }); \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.visualtest.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.visualtest.tsx new file mode 100644 index 0000000000000..b7e35f4416d95 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.visualtest.tsx @@ -0,0 +1,29 @@ +import { ComboBox } from './index'; +/* tslint:disable:no-unused-variable */ +import * as React from 'react'; +/* tslint:enable:no-unused-variable */ +export default class ComboBoxVPage extends React.Component { + public render() { + return
+ +
; + } +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index ef521d73238f9..9198b60bd8566 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -1,27 +1,27 @@ import * as React from 'react'; import { ComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; import './ComboBox.Basic.Example.scss'; -import { DropdownMenuItemType } from '../../Dropdown'; import { autobind } from 'office-ui-fabric-react/lib/Utilities'; +import { SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/selectableOption/SelectableOption.Props'; export class ComboBoxBasicExample extends React.Component { - private _testOptions = - [ { key: 'Header', text: 'Theme Fonts', itemType: DropdownMenuItemType.Header }, - { key: 'A', text: 'Arial Black', fontFamily: '"Arial Black", "Arial Black_MSFontService", sans-serif' }, - { key: 'B', text: 'Time New Roman', fontFamily: '"Times New Roman", "Times New Roman_MSFontService", serif' }, - { key: 'C', text: 'Comic Sans MS', fontFamily: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy' }, - { key: 'divider_2', text: '-', itemType: DropdownMenuItemType.Divider }, - { key: 'Header1', text: 'Other Options', itemType: DropdownMenuItemType.Header }, - { key: 'D', text: 'Option d' }, - { key: 'E', text: 'Option e' }, - { key: 'F', text: 'Option f' }, - { key: 'G', text: 'Option g' }, - { key: 'H', text: 'Option h' }, - { key: 'I', text: 'Option i' }, - { key: 'J', text: 'Option j' }, - ]; + private _testOptions = + [{ key: 'Header', text: 'Theme Fonts', itemType: SelectableOptionMenuItemType.Header }, + { key: 'A', text: 'Arial Black', fontFamily: '"Arial Black", "Arial Black_MSFontService", sans-serif' }, + { key: 'B', text: 'Time New Roman', fontFamily: '"Times New Roman", "Times New Roman_MSFontService", serif' }, + { key: 'C', text: 'Comic Sans MS', fontFamily: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy' }, + { key: 'divider_2', text: '-', itemType: SelectableOptionMenuItemType.Divider }, + { key: 'Header1', text: 'Other Options', itemType: SelectableOptionMenuItemType.Header }, + { key: 'D', text: 'Option d' }, + { key: 'E', text: 'Option e' }, + { key: 'F', text: 'Option f' }, + { key: 'G', text: 'Option g' }, + { key: 'H', text: 'Option h' }, + { key: 'I', text: 'Option i' }, + { key: 'J', text: 'Option j' }, + ]; constructor() { super(); @@ -121,7 +121,7 @@ export class ComboBoxBasicExample extends React.Component { } @autobind - private _getOptions(): IComboBoxOption[] { + private _getOptions(currentOptions: IComboBoxOption[]): IComboBoxOption[] { if (this.state.options.length > 0) { return this.state.options; @@ -129,13 +129,13 @@ export class ComboBoxBasicExample extends React.Component { let newOptions = [ - { key: 'Header', text: 'Theme Fonts', itemType: DropdownMenuItemType.Header }, + { key: 'Header', text: 'Theme Fonts', itemType: SelectableOptionMenuItemType.Header }, { key: 'A', text: 'Arial Black', fontFamily: '"Arial Black", "Arial Black_MSFontService", sans-serif' }, { key: 'B', text: 'Time New Roman', fontFamily: '"Times New Roman", "Times New Roman_MSFontService", serif' }, { key: 'C', text: 'Comic Sans MS', fontFamily: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy' }, { key: 'C1', text: 'Calibri', fontFamily: 'Calibri, Calibri_MSFontService, sans-serif' }, - { key: 'divider_2', text: '-', itemType: DropdownMenuItemType.Divider }, - { key: 'Header1', text: 'Other Options', itemType: DropdownMenuItemType.Header }, + { key: 'divider_2', text: '-', itemType: SelectableOptionMenuItemType.Divider }, + { key: 'Header1', text: 'Other Options', itemType: SelectableOptionMenuItemType.Header }, { key: 'D', text: 'Option d' }, { key: 'E', text: 'Option e' }, { key: 'F', text: 'Option f' }, diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts index 504f8f92a25ed..b523682c67d53 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts @@ -1,66 +1,18 @@ -import * as React from 'react'; import { IRenderFunction } from '../../Utilities'; import { Dropdown } from './Dropdown'; -import { ICalloutProps } from '../../Callout'; - -export enum DropdownMenuItemType { - Normal = 0, - Divider = 1, - Header = 2 -} +import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../Utilities/selectableOption/SelectableDroppableText.Props'; export interface IDropdown { } -export interface IDropdownProps extends React.Props { - /** - * Optional callback to access the IDropdown interface. Use this instead of ref for accessing - * the public methods and properties of the component. - */ - componentRef?: (component: IDropdown) => void; - - /** - * Descriptive label for the Dropdown - */ - label?: string; - +export interface IDropdownProps extends ISelectableDroppableTextProps { /** * Input placeholder text. Displayed until option is selected. */ placeHolder?: string; - /** - * Aria Label for the Dropdown for screen reader users. - */ - ariaLabel?: string; - - /** - * Id of the drop down - */ - id?: string; - - /** - * If provided, additional class name to provide on the root element. - */ - className?: string; - - /** - * The key that will be initially used to set a selected item. - */ - defaultSelectedKey?: string | number; - - /** - * The key of the selected item. If you provide this, you must maintain selection - * state by observing onChange events and passing a new value in when changed. - */ - selectedKey?: string | number; - - /** - * Collection of options for this Dropdown - */ - options?: IDropdownOption[]; - /** * Callback issues when the selected option changes */ @@ -76,82 +28,14 @@ export interface IDropdownProps extends React.Props { */ onRenderTitle?: IRenderFunction; - /** - * Optional custom renderer for the dropdown container - */ - onRenderContainer?: IRenderFunction; - - /** - * Optional custom renderer for the dropdown list - */ - onRenderList?: IRenderFunction; - - /** - * Optional custom renderer for the dropdown options - */ - onRenderItem?: IRenderFunction; - - /** - * Optional custom renderer for the dropdown option content - */ - onRenderOption?: IRenderFunction; - - /** - * Whether or not the Dropdown is disabled. - */ - disabled?: boolean; - - /** - * Whether or not the Dropdown is required. - */ - required?: boolean; - - /** - * Custom properties for Dropdown's Callout used to render options. - */ - calloutProps?: ICalloutProps; - /** * Deprecated at v0.52.0, use 'disabled' instead. * @deprecated */ isDisabled?: boolean; - - /** - * Descriptive label for the Dropdown Error Message - */ - errorMessage?: string; } -export interface IDropdownOption { - /** - * Arbitrary string associated with this option. - */ - key: string | number; - - /** - * Text to render for this option - */ - text: string; - - /** - * Text to render for this option - */ - itemType?: DropdownMenuItemType; - - /** - * Index for this option - */ - index?: number; - - /** - * The aria label for the dropdown option. If not present, the `text` will be used. - */ - ariaLabel?: string; - - /** If option is selected. */ - selected?: boolean; - +export interface IDropdownOption extends ISelectableOption { /** * Deprecated at v.65.1, use 'selected' instead. * @deprecated diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx index e4642a6ef3a2c..edc2fc3a4becb 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IDropdownProps, IDropdownOption, DropdownMenuItemType } from './Dropdown.Props'; +import { IDropdownProps, IDropdownOption } from './Dropdown.Props'; import { DirectionalHint } from '../../common/DirectionalHint'; import { Callout } from '../../Callout'; import { Label } from '../../Label'; @@ -17,6 +17,7 @@ import { findIndex, getId } from '../../Utilities'; +import { SelectableOptionMenuItemType } from '../../Utilities/selectableOption/SelectableOption.Props'; import * as stylesImport from './Dropdown.scss'; const styles: any = stylesImport; @@ -282,9 +283,9 @@ export class Dropdown extends BaseComponent { constructor() { @@ -24,14 +24,14 @@ export class DropdownBasicExample extends React.Component { ariaLabel='Basic dropdown example' options={ [ - { key: 'Header', text: 'Actions', itemType: DropdownMenuItemType.Header }, + { key: 'Header', text: 'Actions', itemType: SelectableOptionMenuItemType.Header }, { key: 'A', text: 'Option a' }, { key: 'B', text: 'Option b' }, { key: 'C', text: 'Option c' }, { key: 'D', text: 'Option d' }, { key: 'E', text: 'Option e' }, - { key: 'divider_2', text: '-', itemType: DropdownMenuItemType.Divider }, - { key: 'Header', text: 'People', itemType: DropdownMenuItemType.Header }, + { key: 'divider_2', text: '-', itemType: SelectableOptionMenuItemType.Divider }, + { key: 'Header', text: 'People', itemType: SelectableOptionMenuItemType.Header }, { key: 'F', text: 'Option f' }, { key: 'G', text: 'Option g' }, { key: 'H', text: 'Option h' }, @@ -68,7 +68,7 @@ export class DropdownBasicExample extends React.Component { { key: 'B', text: 'Option b' }, { key: 'C', text: 'Option c' }, { key: 'D', text: 'Option d' }, - { key: 'divider_1', text: '-', itemType: DropdownMenuItemType.Divider }, + { key: 'divider_1', text: '-', itemType: SelectableOptionMenuItemType.Divider }, { key: 'E', text: 'Option e' }, { key: 'F', text: 'Option f' }, { key: 'G', text: 'Option g' }, diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts index b1e8fd7a696a3..7c1d59c744148 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts @@ -64,4 +64,21 @@ export interface IBaseAutoFillProps extends React.HTMLProps string; + + /** + * Handler for componentDidUpdate + * + * @param { IBaseAutoFillProps } props - the props that got passed in to the parent componentWillReceiveProps + * @returns {boolean} - should the full value of the input be selected? + */ + onComponentDidUpdate?: (props: IBaseAutoFillProps) => boolean; + } \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx index fa3ecd4279670..9f1e9b325a46c 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx @@ -1,6 +1,7 @@ import * as React from 'react'; import { IBaseAutoFillProps, IBaseAutoFill } from './BaseAutoFill.Props'; import { + assign, BaseComponent, KeyCodes, autobind, @@ -29,7 +30,7 @@ export class BaseAutoFill extends BaseComponent 0) { - this._inputElement.setSelectionRange(differenceIndex, suggestedDisplayValue.length, SELECTION_BACKWARD); + + if (shouldSelectFullRange) { + this._inputElement.setSelectionRange(0, this.props.suggestedDisplayValue.length, SELECTION_BACKWARD); + } else { + while (differenceIndex < value.length && value[differenceIndex].toLocaleLowerCase() === suggestedDisplayValue[differenceIndex].toLocaleLowerCase()) { + differenceIndex++; + } + if (differenceIndex > 0) { + this._inputElement.setSelectionRange(differenceIndex, suggestedDisplayValue.length, SELECTION_BACKWARD); + } } } } @@ -99,7 +117,7 @@ export class BaseAutoFill extends BaseComponent; } @@ -114,14 +132,18 @@ export class BaseAutoFill extends BaseComponent) { + private _onKeyDown(ev: React.KeyboardEvent) { + if (this.props.onKeyDown) { + this.props.onKeyDown(ev); + } + switch (ev.which) { case KeyCodes.backspace: this._autoFillEnabled = false; @@ -147,7 +169,7 @@ export class BaseAutoFill extends BaseComponent) { + private _onChange(ev: React.FormEvent) { let value: string = (ev.target as HTMLInputElement).value; if (value && (ev.target as HTMLInputElement).selectionStart === value.length && !this._autoFillEnabled && value.length > this._value.length) { this._autoFillEnabled = true; @@ -155,13 +177,13 @@ export class BaseAutoFill extends BaseComponent this._notifyInputChange(newValue)); } - protected _doesTextStartWith(text: string, startWith: string) { + private _doesTextStartWith(text: string, startWith: string) { if (!text || !startWith) { return false; } diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts deleted file mode 100644 index 0eca41eb7786b..0000000000000 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.Props.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { IBaseAutoFill, IBaseAutoFillProps } from '../BaseAutoFill.Props'; - -export interface IDynamicAutoFill extends IBaseAutoFill { - -} - -export interface IDynamicAutoFillProps extends IBaseAutoFillProps { - /** - * the default value to be visible - */ - defaultVisibleValue?: string; - -} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx deleted file mode 100644 index d5ede9b4c1bf0..0000000000000 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.test.tsx +++ /dev/null @@ -1,133 +0,0 @@ -/* tslint:disable:no-unused-variable */ -import * as React from 'react'; -/* tslint:enable:no-unused-variable */ - -import * as ReactDOM from 'react-dom'; -import * as ReactTestUtils from 'react-addons-test-utils'; -import { KeyCodes } from '../../../../Utilities'; -import { DynamicAutoFill } from './DynamicAutoFill'; - -let { assert } = chai; -describe('DynamicAutoFill', () => { - let autoFill: DynamicAutoFill; - let autoFillInput: HTMLInputElement; - let baseNode = document.createElement('div'); - document.body.appendChild(baseNode); - beforeEach(() => { - - let component = ReactDOM.render( - autoFill = c } - suggestedDisplayValue='hello' />, - baseNode - ); - autoFillInput = ReactDOM.findDOMNode(component as React.ReactInstance) as HTMLInputElement; - }); - - it('correctly autofills', (done) => { - ReactDOM.render( - autoFill = c } - onInputValueChange={ - (text) => { - assert(text === 'hel', 'text was ' + text); - assert(autoFill.value === 'hel', 'autoFill value was ' + autoFill.value); - done(); - } - } - suggestedDisplayValue='hello' />, - baseNode - ); - autoFillInput.value = 'hel'; - ReactTestUtils.Simulate.change(autoFillInput); - ReactDOM.render( - autoFill = c } - onInputValueChange={ - (text) => { - assert(text === 'hel', 'text was ' + text); - assert(autoFill.value === 'hel', 'autoFill value was ' + autoFill.value); - done(); - } - } - suggestedDisplayValue='hello' />, baseNode); - assert(autoFill.inputElement.value === 'hello'); - - }); - - it('does not autofill if suggestedDisplayValue does not match input', (done) => { - autoFillInput.value = 'hep'; - ReactTestUtils.Simulate.change(autoFillInput); - ReactDOM.render( - autoFill = c } - onInputValueChange={ - (text) => { - assert(autoFill.value === 'hep', 'text was ' + autoFill.value); - assert(text === 'hep', 'text was ' + text); - assert(autoFill.inputElement.value === 'hep'); - done(); - } - } - suggestedDisplayValue='hello' />, - baseNode - ); - ReactTestUtils.Simulate.change(autoFillInput); - }); - - it('does not autofill if left or right arrow has been pressed', () => { - autoFillInput.value = 'hel'; - ReactTestUtils.Simulate.change(autoFillInput); - - ReactDOM.render( - autoFill = c } - suggestedDisplayValue='hello' />, - baseNode - ); - - ReactTestUtils.Simulate.keyDown(autoFillInput, { keyCode: KeyCodes.left, which: KeyCodes.left }); - - // Because reacttestutils doesn't allow you to enter text normally we need to reset the autofillinput value to hel. - // If we don't the change event will cause the current input value, 'hello', to be set. - autoFillInput.value = 'hel'; - - ReactTestUtils.Simulate.change(autoFillInput); - - assert(autoFill.value === 'hel', 'text was ' + autoFill.value); - assert(autoFill.inputElement.value === 'hel'); - }); - - it('will autofill if keyCode up or down is pressed', () => { - autoFillInput.value = 'hel'; - ReactTestUtils.Simulate.change(autoFillInput); - - ReactDOM.render( - autoFill = c } - suggestedDisplayValue='hello' />, - baseNode - ); - - assert(autoFill.value === 'hel', 'text was ' + autoFill.value); - assert(autoFill.inputElement.value === 'hel'); - - ReactTestUtils.Simulate.keyDown(autoFillInput, { keyCode: KeyCodes.left, which: KeyCodes.left }); - - autoFillInput.value = 'hel'; - - ReactTestUtils.Simulate.keyDown(autoFillInput, { keyCode: KeyCodes.up, which: KeyCodes.up }); - autoFillInput.value = 'hel'; - ReactTestUtils.Simulate.change(autoFillInput); - - ReactDOM.render( - autoFill = c } - suggestedDisplayValue='hello' />, - baseNode - ); - - assert(autoFill.value === 'hel'); - assert(autoFill.inputElement.value === 'hello'); - }); -}); diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx deleted file mode 100644 index 162a9944c51b5..0000000000000 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/DynamicAutoFill/DynamicAutoFill.tsx +++ /dev/null @@ -1,110 +0,0 @@ -import * as React from 'react'; -import { IDynamicAutoFillProps, IDynamicAutoFill } from './DynamicAutoFill.Props'; -import { BaseAutoFill } from '../BaseAutoFill'; -import { IBaseAutoFillProps } from '../BaseAutoFill.Props'; -import { - KeyCodes, - autobind, - getNativeProps, - inputProperties -} from '../../../../Utilities'; - -export interface IDynamicAutoFillState { - displayValue?: string; -} - -const SELECTION_FORWARD = 'forward'; -const SELECTION_BACKWARD = 'backward'; - -export class DynamicAutoFill extends BaseAutoFill implements IDynamicAutoFill { - - public static defaultProps = { - enableAutoFillOnKeyPress: [KeyCodes.down, KeyCodes.up], - defaultVisibleValue: '' - }; - - constructor(props: IBaseAutoFillProps) { - super(props); - - this._value = ''; - this.state = { - displayValue: props.defaultVisibleValue - }; - } - - public componentWillReceiveProps(nextProps: IDynamicAutoFillProps) { - if (nextProps.defaultVisibleValue && nextProps.defaultVisibleValue !== '' && (nextProps.defaultVisibleValue !== nextProps.defaultVisibleValue || this._value !== nextProps.defaultVisibleValue)) { - this._value = nextProps.defaultVisibleValue; - } - if (this._autoFillEnabled && this._doesTextStartWith(nextProps.suggestedDisplayValue, this._value)) { - this.setState({ displayValue: nextProps.suggestedDisplayValue }); - } - } - - public componentDidUpdate() { - let value = this._value; - let { suggestedDisplayValue } = this.props; - let differenceIndex = 0; - if (this._autoFillEnabled && value && suggestedDisplayValue && this._doesTextStartWith(suggestedDisplayValue, value)) { - if (this.props.defaultVisibleValue === this.props.suggestedDisplayValue) { - this._inputElement.setSelectionRange(0, suggestedDisplayValue.length, SELECTION_BACKWARD); - } else { - while (differenceIndex < value.length && value[differenceIndex].toLocaleLowerCase() === suggestedDisplayValue[differenceIndex].toLocaleLowerCase()) { - differenceIndex++; - } - if (differenceIndex > 0) { - this._inputElement.setSelectionRange(differenceIndex, suggestedDisplayValue.length, SELECTION_BACKWARD); - } - } - } - } - - public render() { - let { - displayValue - } = this.state; - - const nativeProps = getNativeProps(this.props, inputProperties); - return ; - } - - @autobind - protected _onKeyDown(ev: React.KeyboardEvent) { - if (this.props.onKeyDown) { - this.props.onKeyDown(ev); - } - - switch (ev.which) { - case KeyCodes.backspace: - this._autoFillEnabled = false; - break; - case KeyCodes.left: - if (this._autoFillEnabled) { - this._autoFillEnabled = false; - } - break; - case KeyCodes.right: - if (this._autoFillEnabled) { - this._autoFillEnabled = false; - } - break; - default: - if (!this._autoFillEnabled) { - if (this.props.enableAutoFillOnKeyPress.indexOf(ev.which) !== -1) { - this._autoFillEnabled = true; - } - } - break; - } - - } -} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/index.ts b/packages/office-ui-fabric-react/src/components/pickers/index.ts index 25e8450044d4c..5c3371b8fde4a 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/index.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/index.ts @@ -3,8 +3,6 @@ export * from './Suggestions/Suggestions.Props'; export * from './Suggestions/SuggestionsController'; export * from './AutoFill/BaseAutoFill'; export * from './AutoFill/BaseAutoFill.Props'; -export * from './AutoFill/DynamicAutoFill/DynamicAutoFill'; -export * from './AutoFill/DynamicAutoFill/DynamicAutoFill.Props'; export * from './BasePicker'; export * from './BasePicker.Props'; export * from './PickerItem.Props'; diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts new file mode 100644 index 0000000000000..7619e3ffed827 --- /dev/null +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts @@ -0,0 +1,93 @@ +import * as React from 'react'; +import { IRenderFunction } from '../../Utilities'; +import { ICalloutProps } from '../../Callout'; +import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; + +export interface ISelectableDroppableTextProps extends React.Props { + /** + * Optional callback to access the IDropdown interface. Use this instead of ref for accessing + * the public methods and properties of the component. + */ + componentRef?: (component: T) => void; + + /** + * Descriptive label for the Dropdown + */ + label?: string; + + /** + * Aria Label for the Dropdown for screen reader users. + */ + ariaLabel?: string; + + /** + * Id of the drop down + */ + id?: string; + + /** + * If provided, additional class name to provide on the root element. + */ + className?: string; + + /** + * The key that will be initially used to set a selected item. + */ + defaultSelectedKey?: string | number; + + /** + * The key of the selected item. If you provide this, you must maintain selection + * state by observing onChange events and passing a new value in when changed. + */ + selectedKey?: string | number; + + /** + * Collection of options for this Dropdown + */ + options?: ISelectableOption[]; + + /** + * Callback issues when the selected option changes + */ + onChanged?: (option: ISelectableOption, index?: number) => void; + + /** + * Optional custom renderer for the dropdown container + */ + onRenderContainer?: IRenderFunction>; + + /** + * Optional custom renderer for the dropdown list + */ + onRenderList?: IRenderFunction>; + + /** + * Optional custom renderer for the dropdown options + */ + onRenderItem?: IRenderFunction; + + /** + * Optional custom renderer for the dropdown option content + */ + onRenderOption?: IRenderFunction; + + /** + * Whether or not the Dropdown is disabled. + */ + disabled?: boolean; + + /** + * Whether or not the Dropdown is required. + */ + required?: boolean; + + /** + * Custom properties for Dropdown's Callout used to render options. + */ + calloutProps?: ICalloutProps; + + /** + * Descriptive label for the Dropdown Error Message + */ + errorMessage?: string; +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableOption.Props.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableOption.Props.ts new file mode 100644 index 0000000000000..c58caa9c512ce --- /dev/null +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableOption.Props.ts @@ -0,0 +1,35 @@ +export interface ISelectableOption { + /** + * Arbitrary string associated with this option. + */ + key: string | number; + + /** + * Text to render for this option + */ + text: string; + + /** + * Text to render for this option + */ + itemType?: SelectableOptionMenuItemType; + + /** + * Index for this option + */ + index?: number; + + /** + * The aria label for the dropdown option. If not present, the `text` will be used. + */ + ariaLabel?: string; + + /** If option is selected. */ + selected?: boolean; +} + +export enum SelectableOptionMenuItemType { + Normal = 0, + Divider = 1, + Header = 2 +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/index.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/index.ts new file mode 100644 index 0000000000000..4c2fe6688b993 --- /dev/null +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/index.ts @@ -0,0 +1,2 @@ +export * from './SelectableOption.Props'; +export * from './SelectableDroppableText.Props'; \ No newline at end of file From e43ac7baaae62d8dfe9360d7fb3e7ef592d420d8 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Thu, 8 Jun 2017 07:01:56 -0700 Subject: [PATCH 04/31] Fixing some build warnings --- .../src/components/ComboBox/ComboBox.tsx | 47 ++++++++++++------- .../pickers/AutoFill/BaseAutoFill.Props.ts | 4 +- 2 files changed, 32 insertions(+), 19 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index de8293ca52860..750ab7b35833c 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -178,23 +178,6 @@ export class ComboBox extends BaseComponent { this._events.off(this._comboBoxWrapper); } - @autobind - private _onAutoFillComponentWillReceiveProps(nextProps: IBaseAutoFillProps): string { - if (this._comboBox === null || this._comboBox === undefined) { - return null; - } - - if (nextProps.defaultVisibleValue && nextProps.defaultVisibleValue !== '' && (nextProps.defaultVisibleValue !== nextProps.defaultVisibleValue || this._comboBox.value !== nextProps.defaultVisibleValue)) { - return nextProps.defaultVisibleValue; - } - - return this._comboBox.value; - } - - private _onAutoFillComponentDidUpdate(props: IBaseAutoFillProps): boolean { - return props.defaultVisibleValue === props.suggestedDisplayValue - } - // Primary Render public render() { let id = this._id; @@ -297,6 +280,36 @@ export class ComboBox extends BaseComponent { } } + /** + * componentWillReceiveProps handler for the auto fill component + * Checks/updates the iput value to set, if needed + * @param {IBaseAutoFillProps} nextProps - the props that got passed in to the auto fill's componentWillReceiveProps + * @returns {string} - the updated value to set, if needed + */ + @autobind + private _onAutoFillComponentWillReceiveProps(nextProps: IBaseAutoFillProps): string { + if (this._comboBox === null || this._comboBox === undefined) { + return null; + } + + if (nextProps.defaultVisibleValue && nextProps.defaultVisibleValue !== '' && (nextProps.defaultVisibleValue !== nextProps.defaultVisibleValue || this._comboBox.value !== nextProps.defaultVisibleValue)) { + return nextProps.defaultVisibleValue; + } + + return this._comboBox.value; + } + + /** + * componentDidUpdate handler for the auto fill component + * + * @param { IBaseAutoFillProps } props - the current props in the auto fill's componentDidUpdate + * @returns {boolean} - should the full value of the input be selected? + * True if the defaultVisibleValue equals the suggestedDisplayValue, false otherwise + */ + private _onAutoFillComponentDidUpdate(props: IBaseAutoFillProps): boolean { + return props.defaultVisibleValue === props.suggestedDisplayValue; + } + /** * Get the correct value to pass to the input * to show to the user based off of the current props and state diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts index 7c1d59c744148..9782e267fe1d3 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts @@ -68,7 +68,7 @@ export interface IBaseAutoFillProps extends React.HTMLProps string; @@ -76,7 +76,7 @@ export interface IBaseAutoFillProps extends React.HTMLProps boolean; From ac35bdc23a664c26b89e43146bd8f1e62a80d992 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Thu, 8 Jun 2017 07:36:49 -0700 Subject: [PATCH 05/31] Update my example page to explicitly not use two mutually exclusive props --- .../examples/ComboBox.Basic.Example.tsx | 38 +++++++++++++------ 1 file changed, 26 insertions(+), 12 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index 9198b60bd8566..e2a3f5d453c77 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -103,18 +103,32 @@ export class ComboBoxBasicExample extends React.Component { disabled={ true } /> - + { value ? + + : + + } + ); From 6b2ef292c1580348ca20b2c2ec6e5a91f4ac9f3a Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Thu, 8 Jun 2017 12:20:07 -0700 Subject: [PATCH 06/31] The npm-shrinkwrap.json file automatically updated... commiting --- common/config/rush/npm-shrinkwrap.json | 639 +++++++++++++++++++++++-- 1 file changed, 604 insertions(+), 35 deletions(-) diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index bb33295801cc4..07393b8dce687 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -570,9 +570,9 @@ "integrity": "sha1-DldYaol/ddjdDAKdKVtBapg4f5w=", "dependencies": { "@microsoft/api-extractor": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-2.0.6.tgz", - "integrity": "sha1-BVN2DgbyuUGn9zbjP+wVK1/BQoQ=" + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-2.0.7.tgz", + "integrity": "sha1-zmAg7skaKVy1JlmXu58gIqmBPWk=" }, "@microsoft/gulp-core-build-sass": { "version": "3.1.2", @@ -709,7 +709,7 @@ } }, "@rush-temp/example-app-base": { - "version": "file:projects/example-app-base", + "version": "file:projects\\example-app-base", "dependencies": { "async": { "version": "0.9.2", @@ -746,7 +746,7 @@ } }, "@rush-temp/example-component": { - "version": "file:projects/example-component", + "version": "file:projects\\example-component", "dependencies": { "acorn": { "version": "5.0.3", @@ -859,7 +859,7 @@ } }, "@rush-temp/fabric-website": { - "version": "file:projects/fabric-website", + "version": "file:projects\\fabric-website", "dependencies": { "async": { "version": "0.9.2", @@ -900,7 +900,7 @@ } }, "@rush-temp/office-ui-fabric-react": { - "version": "file:projects/office-ui-fabric-react", + "version": "file:projects\\office-ui-fabric-react", "dependencies": { "acorn": { "version": "5.0.3", @@ -1037,10 +1037,10 @@ } }, "@rush-temp/office-ui-fabric-react-tslint": { - "version": "file:projects/office-ui-fabric-react-tslint" + "version": "file:projects\\office-ui-fabric-react-tslint" }, "@rush-temp/ssr-tests": { - "version": "file:projects/ssr-tests", + "version": "file:projects\\ssr-tests", "dependencies": { "acorn": { "version": "5.0.3", @@ -1153,7 +1153,7 @@ } }, "@rush-temp/styling": { - "version": "file:projects/styling", + "version": "file:projects\\styling", "dependencies": { "@uifabric/utilities": { "version": "1.10.2", @@ -1298,7 +1298,7 @@ } }, "@rush-temp/test-bundle-button": { - "version": "file:projects/test-bundle-button", + "version": "file:projects\\test-bundle-button", "dependencies": { "async": { "version": "0.9.2", @@ -1315,10 +1315,10 @@ } }, "@rush-temp/todo-app": { - "version": "file:projects/todo-app" + "version": "file:projects\\todo-app" }, "@rush-temp/utilities": { - "version": "file:projects/utilities", + "version": "file:projects\\utilities", "dependencies": { "async": { "version": "0.9.2", @@ -1627,14 +1627,14 @@ } }, "@uifabric/styling": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-0.6.1.tgz", - "integrity": "sha512-+wDTHWsDpuQuzeOiyg5Dkw7h1X35e0//CLYiKKeePv3q43QV3rbFPBgAIuozkBKCsq3egxPwPCBcVScf4DIZxA==" + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-0.7.1.tgz", + "integrity": "sha512-PZ0ryYqj1Wu4Ojsb7e2VesvgsiY9Lghz9ByEXirywk0im/83fB58exrLtLUBsBU6H037kp8q2RaO2wuaQEnwMw==" }, "@uifabric/utilities": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-4.1.1.tgz", - "integrity": "sha512-kfg+F6zQbCE4xtBK+CN5tGZVENoiQ9jnsCNI/0O2YSnLByY2AGxnTO0c4+HFXhdgcH0a+QR1J4ehWkz1oaeKTg==" + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-4.1.2.tgz", + "integrity": "sha512-tugHCTt9MTccNv4vx9t3QfAowSEZRKDykbuVn23DG1VMUmTOzCs6zozHk3cqb5WFFvjLIsirXU/zPsFABxjApw==" }, "abbrev": { "version": "1.0.9", @@ -2281,9 +2281,9 @@ "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=" }, "caniuse-db": { - "version": "1.0.30000679", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000679.tgz", - "integrity": "sha1-3XvhLxZXfl1q5tuIDG1hnnfco2U=" + "version": "1.0.30000680", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000680.tgz", + "integrity": "sha1-126+quuC49mVK/3FwjHE+DzUgUQ=" }, "capture-stack-trace": { "version": "1.0.0", @@ -3768,6 +3768,581 @@ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, + "fsevents": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.1.1.tgz", + "integrity": "sha1-8Z/Sj0Pur3YWgOUZogPE0LPTGv8=", + "optional": true, + "dependencies": { + "abbrev": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "ansi-regex": { + "version": "2.1.1", + "bundled": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "optional": true + }, + "aproba": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "are-we-there-yet": { + "version": "1.1.2", + "bundled": true, + "optional": true + }, + "asn1": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "assert-plus": { + "version": "0.2.0", + "bundled": true, + "optional": true + }, + "asynckit": { + "version": "0.4.0", + "bundled": true, + "optional": true + }, + "aws-sign2": { + "version": "0.6.0", + "bundled": true, + "optional": true + }, + "aws4": { + "version": "1.6.0", + "bundled": true, + "optional": true + }, + "balanced-match": { + "version": "0.4.2", + "bundled": true + }, + "bcrypt-pbkdf": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "block-stream": { + "version": "0.0.9", + "bundled": true + }, + "boom": { + "version": "2.10.1", + "bundled": true + }, + "brace-expansion": { + "version": "1.1.6", + "bundled": true + }, + "buffer-shims": { + "version": "1.0.0", + "bundled": true + }, + "caseless": { + "version": "0.11.0", + "bundled": true, + "optional": true + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "optional": true + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true + }, + "combined-stream": { + "version": "1.0.5", + "bundled": true + }, + "commander": { + "version": "2.9.0", + "bundled": true, + "optional": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true + }, + "console-control-strings": { + "version": "1.1.0", + "bundled": true + }, + "core-util-is": { + "version": "1.0.2", + "bundled": true + }, + "cryptiles": { + "version": "2.0.5", + "bundled": true, + "optional": true + }, + "dashdash": { + "version": "1.14.1", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "debug": { + "version": "2.2.0", + "bundled": true, + "optional": true + }, + "deep-extend": { + "version": "0.4.1", + "bundled": true, + "optional": true + }, + "delayed-stream": { + "version": "1.0.0", + "bundled": true + }, + "delegates": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "ecc-jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "extend": { + "version": "3.0.0", + "bundled": true, + "optional": true + }, + "extsprintf": { + "version": "1.0.2", + "bundled": true + }, + "forever-agent": { + "version": "0.6.1", + "bundled": true, + "optional": true + }, + "form-data": { + "version": "2.1.2", + "bundled": true, + "optional": true + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true + }, + "fstream": { + "version": "1.0.10", + "bundled": true + }, + "fstream-ignore": { + "version": "1.0.5", + "bundled": true, + "optional": true + }, + "gauge": { + "version": "2.7.3", + "bundled": true, + "optional": true + }, + "generate-function": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "generate-object-property": { + "version": "1.2.0", + "bundled": true, + "optional": true + }, + "getpass": { + "version": "0.1.6", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "glob": { + "version": "7.1.1", + "bundled": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true + }, + "graceful-readlink": { + "version": "1.0.1", + "bundled": true, + "optional": true + }, + "har-validator": { + "version": "2.0.6", + "bundled": true, + "optional": true + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "has-unicode": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "hawk": { + "version": "3.1.3", + "bundled": true, + "optional": true + }, + "hoek": { + "version": "2.16.3", + "bundled": true + }, + "http-signature": { + "version": "1.1.1", + "bundled": true, + "optional": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true + }, + "inherits": { + "version": "2.0.3", + "bundled": true + }, + "ini": { + "version": "1.3.4", + "bundled": true, + "optional": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true + }, + "is-my-json-valid": { + "version": "2.15.0", + "bundled": true, + "optional": true + }, + "is-property": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "is-typedarray": { + "version": "1.0.0", + "bundled": true, + "optional": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true + }, + "isstream": { + "version": "0.1.2", + "bundled": true, + "optional": true + }, + "jodid25519": { + "version": "1.0.2", + "bundled": true, + "optional": true + }, + "jsbn": { + "version": "0.1.1", + "bundled": true, + "optional": true + }, + "json-schema": { + "version": "0.2.3", + "bundled": true, + "optional": true + }, + "json-stringify-safe": { + "version": "5.0.1", + "bundled": true, + "optional": true + }, + "jsonpointer": { + "version": "4.0.1", + "bundled": true, + "optional": true + }, + "jsprim": { + "version": "1.3.1", + "bundled": true, + "optional": true + }, + "mime-db": { + "version": "1.26.0", + "bundled": true + }, + "mime-types": { + "version": "2.1.14", + "bundled": true + }, + "minimatch": { + "version": "3.0.3", + "bundled": true + }, + "minimist": { + "version": "0.0.8", + "bundled": true + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true + }, + "ms": { + "version": "0.7.1", + "bundled": true, + "optional": true + }, + "node-pre-gyp": { + "version": "0.6.33", + "bundled": true, + "optional": true + }, + "nopt": { + "version": "3.0.6", + "bundled": true, + "optional": true + }, + "npmlog": { + "version": "4.0.2", + "bundled": true, + "optional": true + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true + }, + "oauth-sign": { + "version": "0.8.2", + "bundled": true, + "optional": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "optional": true + }, + "once": { + "version": "1.4.0", + "bundled": true + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "optional": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "process-nextick-args": { + "version": "1.0.7", + "bundled": true + }, + "punycode": { + "version": "1.4.1", + "bundled": true, + "optional": true + }, + "qs": { + "version": "6.3.1", + "bundled": true, + "optional": true + }, + "rc": { + "version": "1.1.7", + "bundled": true, + "optional": true, + "dependencies": { + "minimist": { + "version": "1.2.0", + "bundled": true, + "optional": true + } + } + }, + "readable-stream": { + "version": "2.2.2", + "bundled": true, + "optional": true + }, + "request": { + "version": "2.79.0", + "bundled": true, + "optional": true + }, + "rimraf": { + "version": "2.5.4", + "bundled": true + }, + "semver": { + "version": "5.3.0", + "bundled": true, + "optional": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "optional": true + }, + "sntp": { + "version": "1.0.9", + "bundled": true, + "optional": true + }, + "sshpk": { + "version": "1.10.2", + "bundled": true, + "optional": true, + "dependencies": { + "assert-plus": { + "version": "1.0.0", + "bundled": true, + "optional": true + } + } + }, + "string_decoder": { + "version": "0.10.31", + "bundled": true + }, + "string-width": { + "version": "1.0.2", + "bundled": true + }, + "stringstream": { + "version": "0.0.5", + "bundled": true, + "optional": true + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true + }, + "strip-json-comments": { + "version": "2.0.1", + "bundled": true, + "optional": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "optional": true + }, + "tar": { + "version": "2.2.1", + "bundled": true + }, + "tar-pack": { + "version": "3.3.0", + "bundled": true, + "optional": true, + "dependencies": { + "once": { + "version": "1.3.3", + "bundled": true, + "optional": true + }, + "readable-stream": { + "version": "2.1.5", + "bundled": true, + "optional": true + } + } + }, + "tough-cookie": { + "version": "2.3.2", + "bundled": true, + "optional": true + }, + "tunnel-agent": { + "version": "0.4.3", + "bundled": true, + "optional": true + }, + "tweetnacl": { + "version": "0.14.5", + "bundled": true, + "optional": true + }, + "uid-number": { + "version": "0.0.6", + "bundled": true, + "optional": true + }, + "util-deprecate": { + "version": "1.0.2", + "bundled": true + }, + "uuid": { + "version": "3.0.1", + "bundled": true, + "optional": true + }, + "verror": { + "version": "1.3.6", + "bundled": true, + "optional": true + }, + "wide-align": { + "version": "1.1.0", + "bundled": true, + "optional": true + }, + "wrappy": { + "version": "1.0.2", + "bundled": true + }, + "xtend": { + "version": "4.0.1", + "bundled": true, + "optional": true + } + } + }, "fstream": { "version": "1.0.11", "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.11.tgz", @@ -5531,12 +6106,6 @@ "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz", "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=" }, - "jodid25519": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", - "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", - "optional": true - }, "js-base64": { "version": "2.1.9", "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.1.9.tgz", @@ -6795,9 +7364,9 @@ "integrity": "sha1-5rl2yY1wuKoAvVAGTIgOE3dFiY4=" }, "office-ui-fabric-react": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-4.4.0.tgz", - "integrity": "sha512-xINePkVXmXFpxnF6FU5nxedOMaMAm9xIzPZdKr1SK5q2Ht8gu/+mwxdDsM18qyReq2qj9wZtfrSRuL8PowXT7A==" + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-4.4.2.tgz", + "integrity": "sha512-K9kBVRhnSRuXzwfN4YiNkYvCTrnJuvWUPVuYsbyFugRr+XpGw+j0x9pyBJgKiCr/GZgNmnpfJhVLoQQcXnJkTg==" }, "on-finished": { "version": "2.3.0", @@ -8483,9 +9052,9 @@ "integrity": "sha1-Nr54Mgr+WAH2zqPueLblqrlA6gw=" }, "sshpk": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", - "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", + "version": "1.13.1", + "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.1.tgz", + "integrity": "sha1-US322mKHFEMW3EwY/hzx2UBzm+M=", "dependencies": { "assert-plus": { "version": "1.0.0", @@ -9924,4 +10493,4 @@ "integrity": "sha1-RCs4+denr3rke/z8NWhz6GFq0bc=" } } -} \ No newline at end of file +} From aad70fb8bed7feb832f1846dca9d6cef1f5141fe Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Thu, 8 Jun 2017 13:20:17 -0700 Subject: [PATCH 07/31] Had a bad copy paste, fixing up the incorrectly logic --- .../office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 750ab7b35833c..d1003cc821dee 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -1090,7 +1090,7 @@ export class ComboBox extends BaseComponent { default: // are we processing a function key? if so bail out - if (ev.which < 112 /* F1 */ || ev.which > 123 /* F12 */) { + if (ev.which >= 112 /* F1 */ && ev.which <= 123 /* F12 */) { return; } From 1612bccc50d5be308d5a958cd5842352c80363d7 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Fri, 9 Jun 2017 08:21:31 -0700 Subject: [PATCH 08/31] Removing the fontFamily aspect of the comboBox and utilizing the onRenderOption instead. The fontFamily aspect was too specific for the generic comboBox --- .../src/components/ComboBox/ComboBox.Props.ts | 13 +-- .../src/components/ComboBox/ComboBox.test.tsx | 8 +- .../src/components/ComboBox/ComboBox.tsx | 50 +++++------- .../examples/ComboBox.Basic.Example.scss | 10 +++ .../examples/ComboBox.Basic.Example.tsx | 80 +++++++++++++------ 5 files changed, 91 insertions(+), 70 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts index 766548cf10c51..f5f7abeb25aee 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -7,7 +7,7 @@ export interface IComboBoxProps extends ISelectableDroppableTextProps void; + onChanged?: (option?: ISelectableOption, index?: number, value?: string) => void; /** * Callback issued when the options should be resolved, if they have been updated or * if they need to be passed in the first time */ - onResolveOptions?: (options: IComboBoxOption[]) => IComboBoxOption[] | PromiseLike; + onResolveOptions?: (options: ISelectableOption[]) => ISelectableOption[] | PromiseLike; /** * Whether the ComboBox is free form, meaning that the user input is not bound to provided items. Defaults to false. @@ -44,10 +44,3 @@ export interface IComboBoxProps extends ISelectableDroppableTextProps { private _lastReadOnlyAutoCompleteChangeTimeoutId: number; // Promise used when resolving the comboBox options - private currentPromise: PromiseLike; + private currentPromise: PromiseLike; constructor(props?: IComboBoxProps) { super(props); @@ -379,7 +379,7 @@ export class ComboBox extends BaseComponent { * @param index - the index to check * @returns {boolean} - true if the index is valid for the given options, false otherwise */ - private _indexWithinBounds(options: IComboBoxOption[], index: number): boolean { + private _indexWithinBounds(options: ISelectableOption[], index: number): boolean { return index >= 0 && index < options.length; } @@ -529,7 +529,7 @@ export class ComboBox extends BaseComponent { newIndex = Math.max(0, Math.min(currentOptions.length - 1, newIndex)); - let option: IComboBoxOption = currentOptions[newIndex]; + let option: ISelectableOption = currentOptions[newIndex]; // attempt to skip headers and dividers if ((option.itemType === SelectableOptionMenuItemType.Header || @@ -567,7 +567,7 @@ export class ComboBox extends BaseComponent { // Are we at a new index? If so, update the state, otherwise // there is nothing to do if (index !== selectedIndex) { - let option: IComboBoxOption = currentOptions[index]; + let option: ISelectableOption = currentOptions[index]; // Set the selected option this.setState({ @@ -608,7 +608,7 @@ export class ComboBox extends BaseComponent { if (this.props.onResolveOptions) { // get the options - let newOptions: IComboBoxOption[] | PromiseLike = this.props.onResolveOptions(assign({}, this.props)); + let newOptions: ISelectableOption[] | PromiseLike = this.props.onResolveOptions(assign({}, this.props)); // the options are either goingto be an array or a promise // let newOptionsArray: IComboBoxOption[] = newOptions as IComboBoxOption[]; @@ -624,8 +624,8 @@ export class ComboBox extends BaseComponent { // Ensure that the promise will only use the callback if it was the most recent one // and update the state when the promise returns - let promise: PromiseLike = this.currentPromise = newOptions; - promise.then((newOptionsFromPromise: IComboBoxOption[]) => { + let promise: PromiseLike = this.currentPromise = newOptions; + promise.then((newOptionsFromPromise: ISelectableOption[]) => { if (promise === this.currentPromise) { this.setState({ currentOptions: newOptionsFromPromise @@ -683,21 +683,9 @@ export class ComboBox extends BaseComponent { } } - // If we did not find an exact match - // update the state with the pending value - let newFontFamily: string = ''; - if (currentPendingValue.indexOf(' ') > -1) { - newFontFamily = '"' + currentPendingValue + '"'; - } else { - newFontFamily = currentPendingValue; - } - - // add a default fallback font - newFontFamily += ',"Segoe UI",Tahoma,Sans-Serif;'; - // Create a new option - let newOption: IComboBoxOption = { key: currentPendingValue, text: currentPendingValue, fontFamily: newFontFamily }; - let newOptions: IComboBoxOption[] = [...currentOptions, newOption]; + let newOption: ISelectableOption = { key: currentPendingValue, text: currentPendingValue }; + let newOptions: ISelectableOption[] = [...currentOptions, newOption]; let newSelectedIndex: number = this._getSelectedIndex(newOptions, currentPendingValue); this.setState({ @@ -770,7 +758,7 @@ export class ComboBox extends BaseComponent { // Render items @autobind - private _onRenderItem(item: IComboBoxOption): JSX.Element { + private _onRenderItem(item: ISelectableOption): JSX.Element { switch (item.itemType) { case SelectableOptionMenuItemType.Divider: return this._renderSeparator(item); @@ -782,7 +770,7 @@ export class ComboBox extends BaseComponent { } // Render separator - private _renderSeparator(item: IComboBoxOption): JSX.Element { + private _renderSeparator(item: ISelectableOption): JSX.Element { let { index, key } = item; if (index > 0) { return
{ return null; } - private _renderHeader(item: IComboBoxOption): JSX.Element { + private _renderHeader(item: ISelectableOption): JSX.Element { let { onRenderOption = this._onRenderOption } = this.props; return (
@@ -803,7 +791,7 @@ export class ComboBox extends BaseComponent { // Render menu item @autobind - private _renderOption(item: IComboBoxOption): JSX.Element { + private _renderOption(item: ISelectableOption): JSX.Element { let { onRenderOption = this._onRenderOption } = this.props; let id = this._id; let isSelected: boolean = this._isOptionSelected(item.index); @@ -867,8 +855,8 @@ export class ComboBox extends BaseComponent { // Render content of item @autobind - private _onRenderOption(item: IComboBoxOption): JSX.Element { - return { item.text }; + private _onRenderOption(item: ISelectableOption): JSX.Element { + return { item.text }; } /** @@ -904,7 +892,7 @@ export class ComboBox extends BaseComponent { * @param selectedKey - the known selected key to find * @returns {number} - the index of the selected option, -1 if not found */ - private _getSelectedIndex(options: IComboBoxOption[], selectedKey: string | number): number { + private _getSelectedIndex(options: ISelectableOption[], selectedKey: string | number): number { return findIndex(options, (option => (option.isSelected || option.selected || (selectedKey != null) && option.key === selectedKey))); } diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss index 4e86e9a791396..f71fcb3e8911c 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.scss @@ -2,4 +2,14 @@ .ms-ComboBoxBasicExample { max-width: 300px; } + .ms-ComboBox-optionText { + overflow: hidden; + white-space: nowrap; + text-overflow: ellipsis; + min-width: 0px; + max-width: 100%; + word-wrap: break-word; + overflow-wrap: break-word; + margin: 1px; + } } \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index e2a3f5d453c77..e77362f40e29d 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -1,17 +1,18 @@ import * as React from 'react'; -import { ComboBox, IComboBoxOption } from 'office-ui-fabric-react/lib/ComboBox'; +import { ComboBox } from 'office-ui-fabric-react/lib/ComboBox'; import './ComboBox.Basic.Example.scss'; import { + assign, autobind } from 'office-ui-fabric-react/lib/Utilities'; -import { SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption, SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/selectableOption/SelectableOption.Props'; export class ComboBoxBasicExample extends React.Component { private _testOptions = [{ key: 'Header', text: 'Theme Fonts', itemType: SelectableOptionMenuItemType.Header }, - { key: 'A', text: 'Arial Black', fontFamily: '"Arial Black", "Arial Black_MSFontService", sans-serif' }, - { key: 'B', text: 'Time New Roman', fontFamily: '"Times New Roman", "Times New Roman_MSFontService", serif' }, - { key: 'C', text: 'Comic Sans MS', fontFamily: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy' }, + { key: 'A', text: 'Arial Black' }, + { key: 'B', text: 'Time New Roman' }, + { key: 'C', text: 'Comic Sans MS' }, { key: 'divider_2', text: '-', itemType: SelectableOptionMenuItemType.Divider }, { key: 'Header1', text: 'Other Options', itemType: SelectableOptionMenuItemType.Header }, { key: 'D', text: 'Option d' }, @@ -23,6 +24,13 @@ export class ComboBoxBasicExample extends React.Component { { key: 'J', text: 'Option j' }, ]; + private _fontMapping: { [key: string]: string } = { + ['Arial Black']: '"Arial Black", "Arial Black_MSFontService", sans-serif', + ['Time New Roman']: '"Times New Roman", "Times New Roman_MSFontService", serif', + ['Comic Sans MS']: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy', + ['Calibri']: 'Calibri, Calibri_MSFontService, sans-serif' + } + constructor() { super(); this.state = { @@ -46,6 +54,7 @@ export class ComboBoxBasicExample extends React.Component { allowFreeform={ true } autoComplete={ true } options={ this._testOptions } + onRenderOption={ this._onRenderFontOption } /> { allowFreeform={ true } autoComplete={ false } options={ this._testOptions } + onRenderOption={ this._onRenderFontOption } /> { allowFreeform={ false } autoComplete={ true } options={ this._testOptions } + onRenderOption={ this._onRenderFontOption } /> { allowFreeform={ false } autoComplete={ false } options={ this._testOptions } + onRenderOption={ this._onRenderFontOption } /> { ariaLabel='Basic ComboBox example' errorMessage='Error! Here is some text!' options={ this._testOptions } + onRenderOption={ this._onRenderFontOption } /> { onChanged={ this._onChanged } onResolveOptions={ this._getOptions } value={ value && value } + onRenderOption={ this._onRenderFontOption } /> : { options={ options } onChanged={ this._onChanged } onResolveOptions={ this._getOptions } + onRenderOption={ this._onRenderFontOption } /> } @@ -134,8 +149,35 @@ export class ComboBoxBasicExample extends React.Component { ); } + // Render content of item @autobind - private _getOptions(currentOptions: IComboBoxOption[]): IComboBoxOption[] { + private _onRenderFontOption(item: ISelectableOption): JSX.Element { + + if (item.itemType === SelectableOptionMenuItemType.Header || + item.itemType === SelectableOptionMenuItemType.Divider) { + return { item.text }; + } + + let fontFamily = this._fontMapping[item.text]; + + if (fontFamily === null || fontFamily === undefined) { + let newFontFamily: string = item.text; + if (newFontFamily.indexOf(' ') > -1) { + newFontFamily = '"' + newFontFamily + '"'; + } + + // add a default fallback font + newFontFamily += ',"Segoe UI",Tahoma,Sans-Serif'; + + this._fontMapping = assign({}, this._fontMapping, { [fontFamily]: newFontFamily }); + fontFamily = newFontFamily; + } + + return { item.text }; + } + + @autobind + private _getOptions(currentOptions: ISelectableOption[]): ISelectableOption[] { if (this.state.options.length > 0) { return this.state.options; @@ -173,7 +215,7 @@ export class ComboBoxBasicExample extends React.Component { } @autobind - private _onChanged(option: IComboBoxOption, index: number, value: string) { + private _onChanged(option: ISelectableOption, index: number, value: string) { if (option != null) { this.setState({ selectedOptionKey: option.key, @@ -185,25 +227,13 @@ export class ComboBoxBasicExample extends React.Component { value: null }); } else if (value !== null) { - if (!this._optionsContainsKey(value)) { - let newFontFamily: string = ''; - if (value.indexOf(' ') > -1) { - newFontFamily = '"' + value + '"'; - } else { - newFontFamily = value; - } - - // add a default fallback font - newFontFamily += ',"Segoe UI",Tahoma,Sans-Serif;'; + let newOption: ISelectableOption = { key: value, text: value }; - let newOption: IComboBoxOption = { key: value, text: value, fontFamily: newFontFamily }; - - this.setState({ - options: [...this.state.options, newOption], - selectedOptionKey: newOption.key, - value: null - }); - } + this.setState({ + options: [...this.state.options, newOption], + selectedOptionKey: newOption.key, + value: null + }); } } } From 3941b202af3ece6384dca06fbb5cb0583b2b20ac Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Fri, 9 Jun 2017 10:36:24 -0700 Subject: [PATCH 09/31] Update based on feedback --- .../src/components/ComboBox/ComboBox.tsx | 51 +++++++++++-------- .../src/components/ComboBox/ComboBoxPage.tsx | 2 +- .../examples/ComboBox.Basic.Example.tsx | 5 -- .../pickers/AutoFill/BaseAutoFill.Props.ts | 15 +++--- .../pickers/AutoFill/BaseAutoFill.tsx | 25 +++++---- .../SelectableDroppableText.Props.ts | 26 +++++----- 6 files changed, 66 insertions(+), 58 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 1a723192baa2e..7eeb320fe0a09 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -10,7 +10,6 @@ import { import { BaseAutoFill } from '../pickers/AutoFill/BaseAutoFill'; import { IBaseAutoFillProps } from '../pickers/AutoFill/BaseAutoFill.Props'; import { - assign, autobind, BaseComponent, css, @@ -93,7 +92,10 @@ export class ComboBox extends BaseComponent { private _lastReadOnlyAutoCompleteChangeTimeoutId: number; // Promise used when resolving the comboBox options - private currentPromise: PromiseLike; + private _currentPromise: PromiseLike; + + //The current visible value sent to the auto fill on render + private _currentVisibleValue; constructor(props?: IComboBoxProps) { super(props); @@ -174,6 +176,8 @@ export class ComboBox extends BaseComponent { } public componentWillUnmount() { + super.componentWillUnmount(); + // remove the eventHanlder that was added in componentDidMount this._events.off(this._comboBoxWrapper); } @@ -184,7 +188,6 @@ export class ComboBox extends BaseComponent { let { className, label, - options, disabled, ariaLabel, required, @@ -194,13 +197,13 @@ export class ComboBox extends BaseComponent { autoComplete, buttonIconProps } = this.props; - let { isOpen, selectedIndex, focused, suggestedDisplayValue, currentOptions } = this.state; - let selectedOption = currentOptions[selectedIndex]; + let { isOpen, selectedIndex, focused, suggestedDisplayValue } = this.state; + this._currentVisibleValue = this._getVisibleValue(); return (
{ label && ( - + ) }
{ aria-disabled={ disabled } aria-owns={ (id + '-list') } spellCheck={ false } - defaultVisibleValue={ this._getVisibleValue() } + defaultVisibleValue={ this._currentVisibleValue } suggestedDisplayValue={ suggestedDisplayValue } - onComponentWillReceiveProps={ this._onAutoFillComponentWillReceiveProps } - onComponentDidUpdate={ this._onAutoFillComponentDidUpdate } /> + updateValueInWillReceiceProps={ this._onUpdateValueInAutoFillWillReceiceProps } + shouldSelectFullInputValueInComponentDidUpdate={ this._onShouldSelectFullInputValueInAutoFillComponentDidUpdate } /> {
{ isOpen && ( - onRenderContainer(assign({}, this.props), this._onRenderContainer) + onRenderContainer({ ...this.props }, this._onRenderContainer) ) } { errorMessage && @@ -283,17 +286,18 @@ export class ComboBox extends BaseComponent { /** * componentWillReceiveProps handler for the auto fill component * Checks/updates the iput value to set, if needed - * @param {IBaseAutoFillProps} nextProps - the props that got passed in to the auto fill's componentWillReceiveProps + * @param {IBaseAutoFillProps} defaultVisibleValue - the defaultVisibleValue that got passed + * in to the auto fill's componentWillReceiveProps * @returns {string} - the updated value to set, if needed */ @autobind - private _onAutoFillComponentWillReceiveProps(nextProps: IBaseAutoFillProps): string { + private _onUpdateValueInAutoFillWillReceiceProps(): string { if (this._comboBox === null || this._comboBox === undefined) { return null; } - if (nextProps.defaultVisibleValue && nextProps.defaultVisibleValue !== '' && (nextProps.defaultVisibleValue !== nextProps.defaultVisibleValue || this._comboBox.value !== nextProps.defaultVisibleValue)) { - return nextProps.defaultVisibleValue; + if (this._currentVisibleValue && this._currentVisibleValue !== '' && this._comboBox.value !== this._currentVisibleValue) { + return this._currentVisibleValue; } return this._comboBox.value; @@ -302,12 +306,14 @@ export class ComboBox extends BaseComponent { /** * componentDidUpdate handler for the auto fill component * - * @param { IBaseAutoFillProps } props - the current props in the auto fill's componentDidUpdate + * @param { string } defaultVisibleValue - the current defaultVisibleValue in the auto fill's componentDidUpdate + * @param { string } suggestedDisplayValue - the current suggestedDisplayValue in the auto fill's componentDidUpdate * @returns {boolean} - should the full value of the input be selected? * True if the defaultVisibleValue equals the suggestedDisplayValue, false otherwise */ - private _onAutoFillComponentDidUpdate(props: IBaseAutoFillProps): boolean { - return props.defaultVisibleValue === props.suggestedDisplayValue; + @autobind + private _onShouldSelectFullInputValueInAutoFillComponentDidUpdate(): boolean { + return this._currentVisibleValue === this.state.suggestedDisplayValue; } /** @@ -503,7 +509,7 @@ export class ComboBox extends BaseComponent { } // If we get here, either autoComplete is on or we did not find a match with autoComplete on. - // Remeber we are not allowing freeform, so at this point, if we have a pending valid value index + // Remember we are not allowing freeform, so at this point, if we have a pending valid value index // use that; otherwise use the selectedIndex let index = currentPendingValueValidIndex >= 0 ? currentPendingValueValidIndex : selectedIndex; @@ -608,7 +614,8 @@ export class ComboBox extends BaseComponent { if (this.props.onResolveOptions) { // get the options - let newOptions: ISelectableOption[] | PromiseLike = this.props.onResolveOptions(assign({}, this.props)); + let newOptions: ISelectableOption[] | PromiseLike = + this.props.onResolveOptions({ ...this.state.currentOptions }); // the options are either goingto be an array or a promise // let newOptionsArray: IComboBoxOption[] = newOptions as IComboBoxOption[]; @@ -624,9 +631,9 @@ export class ComboBox extends BaseComponent { // Ensure that the promise will only use the callback if it was the most recent one // and update the state when the promise returns - let promise: PromiseLike = this.currentPromise = newOptions; + let promise: PromiseLike = this._currentPromise = newOptions; promise.then((newOptionsFromPromise: ISelectableOption[]) => { - if (promise === this.currentPromise) { + if (promise === this._currentPromise) { this.setState({ currentOptions: newOptionsFromPromise }); @@ -728,7 +735,7 @@ export class ComboBox extends BaseComponent { setInitialFocus={ false } >
- { onRenderList(assign({}, this.props), this._onRenderList) } + { onRenderList({ ...this.props }, this._onRenderList) }
); diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx index 5ff0a9a76aa46..2179418e30cd4 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBoxPage.tsx @@ -30,7 +30,7 @@ export class ComboBoxPage extends React.Component { overview={

- A ComboBox is a list in which the selected item is always visible, and the others are visible on demand by clicking a drop-down button. They are used to simplify the design and make a choice within the UI. When closed, only the selected item is visible. When users click the drop-down button, all the options become visible. To change the value, users open the list and click another value or use the arrow keys (up and down) to select a new value. + A ComboBox is a list in which the selected item is always visible, and the others are visible on demand by clicking a drop-down button or by typing in the input (unless allowFreeform and autoComplete are both false). They are used to simplify the design and make a choice within the UI. When closed, only the selected item is visible. When users click the drop-down button, all the options become visible. To change the value, users open the list and click another value or use the arrow keys (up and down) to select a new value. When collapsed if autoComplete and/or allowFreeform are true, the user can select a new value by typing.

} diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index e77362f40e29d..697b46348b9cd 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -209,11 +209,6 @@ export class ComboBoxBasicExample extends React.Component { return newOptions; } - @autobind - private _optionsContainsKey(key: string): boolean { - return false; - } - @autobind private _onChanged(option: ISelectableOption, index: number, value: string) { if (option != null) { diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts index 9782e267fe1d3..07cbb636e78b6 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts @@ -65,20 +65,21 @@ export interface IBaseAutoFillProps extends React.HTMLProps string; + updateValueInWillReceiceProps?: () => string; /** - * Handler for componentDidUpdate + * Handler for checking if the full value of the input should + * be seleced in componentDidUpdate * - * @param { IBaseAutoFillProps } props - the current props in the auto fill's componentDidUpdate * @returns {boolean} - should the full value of the input be selected? */ - onComponentDidUpdate?: (props: IBaseAutoFillProps) => boolean; + shouldSelectFullInputValueInComponentDidUpdate?: () => boolean; } \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx index 9f1e9b325a46c..d56a0ea9fc2bd 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx @@ -22,9 +22,9 @@ export class BaseAutoFill extends BaseComponent) { + private _onKeyDown(ev: React.KeyboardEvent) { if (this.props.onKeyDown) { this.props.onKeyDown(ev); } diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts index 7619e3ffed827..fb4ab0c0f4360 100644 --- a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts @@ -5,23 +5,23 @@ import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOp export interface ISelectableDroppableTextProps extends React.Props { /** - * Optional callback to access the IDropdown interface. Use this instead of ref for accessing + * Optional callback to access the ISelectableDroppableText interface. Use this instead of ref for accessing * the public methods and properties of the component. */ componentRef?: (component: T) => void; /** - * Descriptive label for the Dropdown + * Descriptive label for the ISelectableDroppableText */ label?: string; /** - * Aria Label for the Dropdown for screen reader users. + * Aria Label for the ISelectableDroppableText for screen reader users. */ ariaLabel?: string; /** - * Id of the drop down + * Id of the ISelectableDroppableText */ id?: string; @@ -42,7 +42,7 @@ export interface ISelectableDroppableTextProps extends React.Props { selectedKey?: string | number; /** - * Collection of options for this Dropdown + * Collection of options for this ISelectableDroppableText */ options?: ISelectableOption[]; @@ -52,42 +52,42 @@ export interface ISelectableDroppableTextProps extends React.Props { onChanged?: (option: ISelectableOption, index?: number) => void; /** - * Optional custom renderer for the dropdown container + * Optional custom renderer for the ISelectableDroppableText container */ onRenderContainer?: IRenderFunction>; /** - * Optional custom renderer for the dropdown list + * Optional custom renderer for the ISelectableDroppableText list */ onRenderList?: IRenderFunction>; /** - * Optional custom renderer for the dropdown options + * Optional custom renderer for the ISelectableDroppableText options */ onRenderItem?: IRenderFunction; /** - * Optional custom renderer for the dropdown option content + * Optional custom renderer for the ISelectableDroppableText option content */ onRenderOption?: IRenderFunction; /** - * Whether or not the Dropdown is disabled. + * Whether or not the ISelectableDroppableText is disabled. */ disabled?: boolean; /** - * Whether or not the Dropdown is required. + * Whether or not the ISelectableDroppableText is required. */ required?: boolean; /** - * Custom properties for Dropdown's Callout used to render options. + * Custom properties for ISelectableDroppableText's Callout used to render options. */ calloutProps?: ICalloutProps; /** - * Descriptive label for the Dropdown Error Message + * Descriptive label for the ISelectableDroppableText Error Message */ errorMessage?: string; } \ No newline at end of file From 4b779c67241d00545b74a2667680624f41cc9c95 Mon Sep 17 00:00:00 2001 From: Christian Gonzalez Date: Mon, 12 Jun 2017 11:16:43 -0700 Subject: [PATCH 10/31] Jspurlin jspurlin/combo box (#2) * Enable no implicit any in the utilities package (#1970) * Fix no implicit anys in utilities package. * Rush change * Reverse all shrinkwrap changes except the typings one. * Fix bundle minification to exclude debug warnings. (#1973) * Updating shrinkwrap, rush, and making minify build have production flag to remove debug code. * Updates. * removing lockfile. * dropping to 7. Moving back to npm run build. * Downgrading rush. * Applying package updates. * Website: Update dev.office.com header (#1966) * Use Fabric Core 7.1.0 for the website * Adjust position of caret's in header so that they spin around central axis * Update to latest icon font from dev.office.com for header and move outside out :global{} to fix build issue * Remove a u- prefix that was missed earlier * Update dev.office.com header with the latest navigation links * Add change file * Make sure the quote rule is enabled for tsline (#1961) * With responsive mode error (#1956) * withResponsiveMode: Adding error handling around the case where window.innerWidth throws an exception * adding change log file * Create withResponsiveMode.tsx --- .travis.yml | 8 +- apps/fabric-website/CHANGELOG.json | 25 + apps/fabric-website/CHANGELOG.md | 9 +- apps/fabric-website/package.json | 4 +- .../src/components/DOIcon/DOIcon.scss | 24 +- .../components/DONavigation/DONavigation.scss | 8 +- .../components/DONavigation/DONavigation.tsx | 2 +- .../src/data/DONavigationLinkData.json | 62 +- apps/fabric-website/src/root.tsx | 2 +- apps/ssr-tests/package.json | 4 +- apps/test-bundle-button/package.json | 2 +- apps/todo-app/package.json | 2 +- ...oggle-cursor-pointer_2017-06-09-21-07.json | 11 - ...-props-to-attributes_2017-06-09-20-27.json | 11 - ...bsite-update-header_2017-06-09-19-15.json} | 6 +- ...-props-to-attributes_2017-06-09-20-27.json | 11 - ...oggle-cursor-pointer_2017-06-09-21-07.json | 11 - ...-props-to-attributes_2017-06-09-20-27.json | 11 - ...oggle-cursor-pointer_2017-06-09-21-07.json | 11 - ...-props-to-attributes_2017-06-09-20-27.json | 11 - ...oggle-cursor-pointer_2017-06-09-21-07.json | 11 - ...-props-to-attributes_2017-06-09-20-27.json | 11 - ...hResponsiveModeError_2017-06-08-12-44.json | 11 + common/config/rush/npm-shrinkwrap.json | 102 +- package-lock.json | 2148 ----------------- packages/example-app-base/CHANGELOG.json | 22 + packages/example-app-base/CHANGELOG.md | 9 +- packages/example-app-base/package.json | 4 +- packages/example-component/package.json | 6 +- .../office-ui-fabric-react-tslint/tslint.json | 1 + .../office-ui-fabric-react/CHANGELOG.json | 42 + packages/office-ui-fabric-react/CHANGELOG.md | 15 +- packages/office-ui-fabric-react/package.json | 6 +- .../decorators/withResponsiveMode.tsx | 9 +- .../webpack.lib.config.js | 16 +- packages/styling/CHANGELOG.json | 19 + packages/styling/CHANGELOG.md | 9 +- packages/styling/package.json | 2 +- packages/utilities/CHANGELOG.json | 14 + packages/utilities/CHANGELOG.md | 9 +- packages/utilities/api/utilities.api.ts | 9 +- packages/utilities/package.json | 3 +- packages/utilities/src/Async.ts | 20 +- packages/utilities/src/BaseComponent.test.tsx | 4 +- packages/utilities/src/BaseComponent.ts | 8 +- packages/utilities/src/Customizer.tsx | 4 +- packages/utilities/src/DelayedRender.tsx | 2 +- packages/utilities/src/EventGroup.ts | 8 +- packages/utilities/src/autobind.ts | 2 +- packages/utilities/src/css.ts | 2 +- packages/utilities/src/customizable.test.tsx | 2 +- packages/utilities/src/customizable.tsx | 2 +- packages/utilities/src/dom.ts | 2 +- packages/utilities/src/focus.test.tsx | 6 +- packages/utilities/src/hoist.ts | 2 +- packages/utilities/src/memoize.test.ts | 6 +- packages/utilities/src/memoize.ts | 4 +- packages/utilities/src/object.ts | 8 +- packages/utilities/src/properties.test.ts | 11 +- packages/utilities/src/resources.ts | 4 +- packages/utilities/src/string.ts | 26 +- .../utilities/src/test/injectWrapperMethod.ts | 4 +- packages/utilities/tsconfig.json | 1 + rush.json | 2 +- 64 files changed, 407 insertions(+), 2436 deletions(-) delete mode 100644 common/changes/@uifabric/example-app-base/toggle-cursor-pointer_2017-06-09-21-07.json delete mode 100644 common/changes/@uifabric/example-app-base/types-switch-from-props-to-attributes_2017-06-09-20-27.json rename common/changes/@uifabric/fabric-website/{toggle-cursor-pointer_2017-06-09-21-07.json => miwhea-website-update-header_2017-06-09-19-15.json} (53%) delete mode 100644 common/changes/@uifabric/fabric-website/types-switch-from-props-to-attributes_2017-06-09-20-27.json delete mode 100644 common/changes/@uifabric/styling/toggle-cursor-pointer_2017-06-09-21-07.json delete mode 100644 common/changes/@uifabric/styling/types-switch-from-props-to-attributes_2017-06-09-20-27.json delete mode 100644 common/changes/@uifabric/utilities/toggle-cursor-pointer_2017-06-09-21-07.json delete mode 100644 common/changes/@uifabric/utilities/types-switch-from-props-to-attributes_2017-06-09-20-27.json delete mode 100644 common/changes/office-ui-fabric-react/toggle-cursor-pointer_2017-06-09-21-07.json delete mode 100644 common/changes/office-ui-fabric-react/types-switch-from-props-to-attributes_2017-06-09-20-27.json create mode 100644 common/changes/office-ui-fabric-react/withResponsiveModeError_2017-06-08-12-44.json delete mode 100644 package-lock.json diff --git a/.travis.yml b/.travis.yml index 900094a3d1334..5907ae300a6e1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,7 @@ language: node_js node_js: - - '8' + - '7' before_script: - npm install -g gulp script: - - npm run buildfast -cache: - directories: - - node_modules - - common/node_modules + - npm run build \ No newline at end of file diff --git a/apps/fabric-website/CHANGELOG.json b/apps/fabric-website/CHANGELOG.json index e66c52e3a8e75..43e6ef83bbdb1 100644 --- a/apps/fabric-website/CHANGELOG.json +++ b/apps/fabric-website/CHANGELOG.json @@ -1,6 +1,31 @@ { "name": "@uifabric/fabric-website", "entries": [ + { + "version": "4.5.36", + "tag": "@uifabric/fabric-website_v4.5.36", + "date": "Mon, 12 Jun 2017 01:47:18 GMT", + "comments": { + "patch": [ + { + "author": "Christian Gonzalez ", + "commit": "de7c49d2bd5e85af8d7ae0aee97eb9afd297966d", + "comment": "Enable no implicit any in utilities package" + } + ], + "dependency": [ + { + "comment": "Updating dependency \"@uifabric/example-app-base\" from `3.0.0` to `3.0.1`" + }, + { + "comment": "Updating dependency \"@uifabric/utilities\" from `>=4.1.2 <5.0.0` to `>=4.1.2 <5.0.0`" + }, + { + "comment": "Updating dependency \"office-ui-fabric-react\" from `>=4.4.2 <5.0.0` to `>=4.4.2 <5.0.0`" + } + ] + } + }, { "version": "4.5.35", "tag": "@uifabric/fabric-website_v4.5.35", diff --git a/apps/fabric-website/CHANGELOG.md b/apps/fabric-website/CHANGELOG.md index 3ee5e76cfc0d7..b589fa364e4dd 100644 --- a/apps/fabric-website/CHANGELOG.md +++ b/apps/fabric-website/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @uifabric/fabric-website -This log was last generated on Thu, 08 Jun 2017 00:18:05 GMT and should not be manually modified. +This log was last generated on Mon, 12 Jun 2017 01:47:18 GMT and should not be manually modified. + +## 4.5.36 +Mon, 12 Jun 2017 01:47:18 GMT + +### Patches + +- Enable no implicit any in utilities package ## 4.5.35 Thu, 08 Jun 2017 00:18:05 GMT diff --git a/apps/fabric-website/package.json b/apps/fabric-website/package.json index d41a4f93ace80..3b40cd0157796 100644 --- a/apps/fabric-website/package.json +++ b/apps/fabric-website/package.json @@ -1,6 +1,6 @@ { "name": "@uifabric/fabric-website", - "version": "4.5.35", + "version": "4.5.36", "description": "Reusable React components for building experiences for Office 365.", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -47,7 +47,7 @@ "webpack-bundle-analyzer": "^2.2.1" }, "dependencies": { - "@uifabric/example-app-base": "3.0.0", + "@uifabric/example-app-base": "3.0.1", "@uifabric/utilities": ">=4.1.2 <5.0.0", "color-functions": "1.1.0", "json-loader": "^0.5.4", diff --git a/apps/fabric-website/src/components/DOIcon/DOIcon.scss b/apps/fabric-website/src/components/DOIcon/DOIcon.scss index a0cbfa7bffe19..3f5c726a803c9 100644 --- a/apps/fabric-website/src/components/DOIcon/DOIcon.scss +++ b/apps/fabric-website/src/components/DOIcon/DOIcon.scss @@ -1,12 +1,11 @@ -:global { - @font-face { - font-family: 'devoffice-product'; - src:url("../Content/Fonts/devoffice-products/devoffice-product.eot"); - src:url("../Content/Fonts/devoffice-products/devoffice-product.eot?#iefix") format("embedded-opentype"), url('data:application/octet-stream;base64,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') format("truetype"),url("../Content/Fonts/devoffice-products/devoffice-product.svg?#devoffice-product") format("svg"); - font-weight:normal; - font-style:normal - } +@font-face{ + font-family: 'devoffice-product'; + src: url('data:application/octet-stream;base64,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') format('truetype'); + font-weight:normal; + font-style:normal +} +:global { .od-Icon { -moz-osx-font-smoothing: grayscale; -webkit-font-smoothing: antialiased; @@ -54,15 +53,8 @@ .od-Icon--yammer:before { content: "" } - // @todo: If the Microsoft Teams logo is added to the 'devoffice-product' font, this inline SVG won't be necessary. .od-Icon--microsoftTeams:before { - background: url("data:image/svg+xml,%3Csvg%20id%3D%22Layer_1%22%20data-name%3D%22Layer%201%22%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2030%2030%22%3E%3Cdefs%3E%3Cstyle%3E.cls-1%7Bfill%3A%23fff%3B%7D%3C%2Fstyle%3E%3C%2Fdefs%3E%3Ctitle%3ETeams_Logo_Only_White_Small%3C%2Ftitle%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M38%2C18.5a2.43288%2C2.43288%2C0%2C0%2C1-.19531.97168%2C2.51908%2C2.51908%2C0%2C0%2C1-1.333%2C1.333%2C2.51471%2C2.51471%2C0%2C0%2C1-1.94336%2C0%2C2.51908%2C2.51908%2C0%2C0%2C1-1.333-1.333%2C2.51471%2C2.51471%2C0%2C0%2C1%2C0-1.94336%2C2.51908%2C2.51908%2C0%2C0%2C1%2C1.333-1.333%2C2.51471%2C2.51471%2C0%2C0%2C1%2C1.94336%2C0%2C2.51908%2C2.51908%2C0%2C0%2C1%2C1.333%2C1.333A2.43288%2C2.43288%2C0%2C0%2C1%2C38%2C18.5Z%22%20transform%3D%22translate(-8%20-8)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M38%2C27.40234a2.09377%2C2.09377%2C0%2C0%2C1-.35645%2C1.25%2C2.70805%2C2.70805%2C0%2C0%2C1-.90332.80078%2C3.95932%2C3.95932%2C0%2C0%2C1-1.21094.4248A7.07568%2C7.07568%2C0%2C0%2C1%2C34.25%2C30H33V22.5h5v4.90234Z%22%20transform%3D%22translate(-8%20-8)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M31.29272%2C16.03729a2.51286%2C2.51286%2C0%2C0%2C0-1.32959-1.32953%2C2.50863%2C2.50863%2C0%2C0%2C0-1.93829%2C0%2C2.51987%2C2.51987%2C0%2C0%2C0-.79407.53571A0.60652%2C0.60652%2C0%2C0%2C0%2C27%2C15.52289v2.96716a0.60652%2C0.60652%2C0%2C0%2C0%2C.23077.27942%2C2.51831%2C2.51831%2C0%2C0%2C0%2C.79395.53571%2C2.50813%2C2.50813%2C0%2C0%2C0%2C1.93839%2C0%2C2.51286%2C2.51286%2C0%2C0%2C0%2C1.32961-1.32953A2.50878%2C2.50878%2C0%2C0%2C0%2C31.29272%2C16.03729Z%22%20transform%3D%22translate(-8%20-8)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M27%2C21V31.967a0.45182%2C0.45182%2C0%2C0%2C0%2C.19128.08917%2C4.08179%2C4.08179%2C0%2C0%2C0%2C.75549.18018%2C5.34007%2C5.34007%2C0%2C0%2C0%2C.73816.05359%2C5.56675%2C5.56675%2C0%2C0%2C0%2C1.131-.12177%2C3.31069%2C3.31069%2C0%2C0%2C0%2C1.07045-.41882%2C2.46692%2C2.46692%2C0%2C0%2C0%2C.79853-0.79382A2.28623%2C2.28623%2C0%2C0%2C0%2C32%2C29.7088V21H27Z%22%20transform%3D%22translate(-8%20-8)%22%2F%3E%3Cpath%20class%3D%22cls-1%22%20d%3D%22M8%2C12V34l18%2C4V8Zm13%2C7.47089-3.40625.15491v9.73676L15%2C29.2369V19.74141l-3%2C.10527V17.489l9-.5705v2.55237Z%22%20transform%3D%22translate(-8%20-8)%22%2F%3E%3C%2Fsvg%3E") center center no-repeat; - background-color: #5558AF; - background-size: 16px 16px; - content: ""; - display: block; - height: 100%; - width: 100%; + content: ""; } .od-Icon--android:before { content: "" diff --git a/apps/fabric-website/src/components/DONavigation/DONavigation.scss b/apps/fabric-website/src/components/DONavigation/DONavigation.scss index a53bfd6d6c9c0..c2afcfab0bfde 100644 --- a/apps/fabric-website/src/components/DONavigation/DONavigation.scss +++ b/apps/fabric-website/src/components/DONavigation/DONavigation.scss @@ -29,6 +29,12 @@ $od-Navigation-columnPadding: 30px; background-color: $ms-color-black; vertical-align: top; + .ms-Icon { + height: auto; + position: relative; + top: -4px; + } + &.is-open { .od-Navigation-link { background-color: $ms-color-neutralPrimaryAlt; @@ -67,7 +73,7 @@ $od-Navigation-columnPadding: 30px; @media (min-width: $od-Header-bannerWidth) { position: fixed; - top: 50px; + top: $od-Header-height; left: 0; height: 404px; } diff --git a/apps/fabric-website/src/components/DONavigation/DONavigation.tsx b/apps/fabric-website/src/components/DONavigation/DONavigation.tsx index e56da4d563c1d..8f44764787421 100644 --- a/apps/fabric-website/src/components/DONavigation/DONavigation.tsx +++ b/apps/fabric-website/src/components/DONavigation/DONavigation.tsx @@ -94,7 +94,7 @@ export class DONavigation extends React.Component
-
+
{ this._getLinks(links.exploreLinks.firstMenuColumn) }
diff --git a/apps/fabric-website/src/data/DONavigationLinkData.json b/apps/fabric-website/src/data/DONavigationLinkData.json index a41ab4d4d37fa..10d0c250df3b8 100644 --- a/apps/fabric-website/src/data/DONavigationLinkData.json +++ b/apps/fabric-website/src/data/DONavigationLinkData.json @@ -1,27 +1,39 @@ { "exploreLinks": { - "firstMenuColumn": [ + "firstMenuColumn": [ { "label": "Why Office?", - "href": "http://dev.office.com/opportunity" + "href": "https://dev.office.com/opportunity" }, { "label": "Office UI Fabric", - "href": "http://dev.office.com/fabric" + "href": "https://dev.office.com/fabric" }, { "label": "Microsoft Graph", - "href": "http://graph.microsoft.io/" + "href": "https://developer.microsoft.com/graph" }, { "label": "Better with Office", - "href": "http://betterwith.office.com/" + "href": "https://betterwith.office.com/" + }, + { + "label": "Go To Market", + "href": "https://dev.office.com/go-to-market" + }, + { + "label": "Industry Verticals", + "href": "https://dev.office.com/industry-verticals" + }, + { + "label": "Business Platform", + "href": "https://businessplatform.microsoft.com/" } ], "secondMenuColumn": [ { "label": "Word", - "href": "http://dev.office.com/word", + "href": "https://dev.office.com/word", "icon": { "iconClass": "word", "bgColor": "blue" @@ -29,7 +41,7 @@ }, { "label": "Excel", - "href": "http://dev.office.com/excel", + "href": "https://dev.office.com/excel", "icon": { "iconClass": "excel", "bgColor": "green" @@ -37,7 +49,7 @@ }, { "label": "PowerPoint", - "href": "http://dev.office.com/powerpoint", + "href": "https://dev.office.com/powerpoint", "icon": { "iconClass": "powerpoint", "bgColor": "orangeLight" @@ -45,7 +57,7 @@ }, { "label": "Outlook", - "href": "http://dev.outlook.com/", + "href": "https://dev.outlook.com/", "icon": { "iconClass": "outlook", "bgColor": "blue" @@ -53,7 +65,7 @@ }, { "label": "OneDrive", - "href": "http://dev.onedrive.com/", + "href": "https://dev.onedrive.com/", "icon": { "iconClass": "onedrive", "bgColor": "blue" @@ -61,7 +73,7 @@ }, { "label": "OneNote", - "href": "http://dev.onenote.com/", + "href": "https://dev.onenote.com/", "icon": { "iconClass": "onenote", "bgColor": "magenta" @@ -69,7 +81,7 @@ }, { "label": "SharePoint", - "href": "https://msdn.microsoft.com/en-us/library/office/dn833469.aspx", + "href": "https://dev.office.com/sharepoint", "icon": { "iconClass": "sharepoint", "bgColor": "blueLight" @@ -77,7 +89,7 @@ }, { "label": "Skype", - "href": "http://dev.office.com/skype", + "href": "https://dev.office.com/skype", "icon": { "iconClass": "skype", "bgColor": "blueLight" @@ -85,7 +97,7 @@ }, { "label": "Access", - "href": "https://msdn.microsoft.com/en-us/library/office/dn605890.aspx", + "href": "https://msdn.microsoft.com/en-us/library/office/fp179695.aspx", "icon": { "iconClass": "access", "bgColor": "redDark" @@ -93,7 +105,7 @@ }, { "label": "Project", - "href": "https://msdn.microsoft.com/en-us/library/office/ms512767.aspx", + "href": "https://dev.office.com/project", "icon": { "iconClass": "project", "bgColor": "teal" @@ -101,7 +113,7 @@ }, { "label": "Yammer", - "href": "http://dev.yammer.com/", + "href": "https://dev.yammer.com/", "icon": { "iconClass": "yammer", "bgColor": "blue" @@ -112,28 +124,28 @@ "href": "https://dev.office.com/microsoft-teams", "icon": { "iconClass": "microsoftTeams", - "bgColor": "" + "bgColor": "blue" } } ], "thirdMenuColumn": [ { "label": "Android", - "href": "http://dev.office.com/android", + "href": "https://dev.office.com/android", "icon": { "iconClass": "android" } }, { "label": "ASP .NET", - "href": "http://dev.office.com/asp-dot-net", + "href": "https://dev.office.com/asp-dot-net", "icon": { "iconClass": "aspnet" } }, { "label": "iOS", - "href": "http://dev.office.com/ios", + "href": "https://dev.office.com/ios", "icon": { "iconClass": "ios", "bgColor": "" @@ -141,35 +153,35 @@ }, { "label": "JavaScript", - "href": "http://dev.office.com/js", + "href": "https://dev.office.com/js", "icon": { "iconClass": "javascript" } }, { "label": "Node.js", - "href": "http://dev.office.com/node", + "href": "https://dev.office.com/node", "icon": { "iconClass": "node" } }, { "label": "PHP (coming soon)", - "href": "https://graph.microsoft.io/getting-started", + "href": "https://developer.microsoft.com/graph/quick-start", "icon": { "iconClass": "php" } }, { "label": "Python (coming soon)", - "href": "https://graph.microsoft.io/getting-started", + "href": "https://developer.microsoft.com/graph/quick-start", "icon": { "iconClass": "python" } }, { "label": "Ruby (coming soon)", - "href": "https://graph.microsoft.io/getting-started", + "href": "https://developer.microsoft.com/graph/quick-start", "icon": { "iconClass": "ruby" } diff --git a/apps/fabric-website/src/root.tsx b/apps/fabric-website/src/root.tsx index f6b5de06fdd01..29129cf7419e5 100644 --- a/apps/fabric-website/src/root.tsx +++ b/apps/fabric-website/src/root.tsx @@ -134,4 +134,4 @@ function addCSSToHeader(fileName) { linkEl.href = fileName headEl.appendChild(linkEl) } -addCSSToHeader('https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/5.1.0/css/fabric.min.css') \ No newline at end of file +addCSSToHeader('https://static2.sharepointonline.com/files/fabric/office-ui-fabric-core/7.1.0/css/fabric.min.css') \ No newline at end of file diff --git a/apps/ssr-tests/package.json b/apps/ssr-tests/package.json index af27f745b69ae..3afdb813adb6b 100644 --- a/apps/ssr-tests/package.json +++ b/apps/ssr-tests/package.json @@ -2,9 +2,7 @@ "name": "ssr-tests", "version": "1.0.2", "description": "Reusable React components for building experiences for Office 365.", - "main": "lib/index.js", "private": true, - "typings": "lib/index.d.ts", "repository": { "type": "git", "url": "https://github.com/OfficeDev/office-ui-fabric-react" @@ -25,7 +23,7 @@ "@types/mocha": "^2.2.39", "@types/webpack-env": "^1.13.0", "mocha": "~3.3.0", - "office-ui-fabric-react": ">=4.4.1 <5.0.0", + "office-ui-fabric-react": ">=4.5.0 <5.0.0", "raw-loader": "^0.5.1", "react": "^15.4.2", "react-addons-test-utils": "^15.4.2", diff --git a/apps/test-bundle-button/package.json b/apps/test-bundle-button/package.json index a0ffb03203af5..581208a43d3af 100644 --- a/apps/test-bundle-button/package.json +++ b/apps/test-bundle-button/package.json @@ -33,7 +33,7 @@ "dependencies": { "react": "^0.14 || ^15.0.1-0 || ^16.0.0-0", "react-dom": "^0.14 || ^15.0.1-0 || ^16.0.0-0", - "office-ui-fabric-react": ">=4.4.1 <5.0.0", + "office-ui-fabric-react": ">=4.5.0 <5.0.0", "tslib": "^1.6.0" } } \ No newline at end of file diff --git a/apps/todo-app/package.json b/apps/todo-app/package.json index b6fdbfea23a6b..5c4b1f795b0ac 100644 --- a/apps/todo-app/package.json +++ b/apps/todo-app/package.json @@ -26,7 +26,7 @@ "es6-promise": "^4.1.0", "immutability-helper": "^2.0.0", "office-ui-fabric-core": ">=7.0.0 <8.0.0", - "office-ui-fabric-react": ">=4.4.1 <5.0.0", + "office-ui-fabric-react": ">=4.5.0 <5.0.0", "react": "^15.4.2", "react-dom": "^15.4.2", "typescript": "2.2.2", diff --git a/common/changes/@uifabric/example-app-base/toggle-cursor-pointer_2017-06-09-21-07.json b/common/changes/@uifabric/example-app-base/toggle-cursor-pointer_2017-06-09-21-07.json deleted file mode 100644 index 6e9ba724f56cc..0000000000000 --- a/common/changes/@uifabric/example-app-base/toggle-cursor-pointer_2017-06-09-21-07.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/example-app-base", - "type": "none" - } - ], - "packageName": "@uifabric/example-app-base", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/example-app-base/types-switch-from-props-to-attributes_2017-06-09-20-27.json b/common/changes/@uifabric/example-app-base/types-switch-from-props-to-attributes_2017-06-09-20-27.json deleted file mode 100644 index 6e9ba724f56cc..0000000000000 --- a/common/changes/@uifabric/example-app-base/types-switch-from-props-to-attributes_2017-06-09-20-27.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/example-app-base", - "type": "none" - } - ], - "packageName": "@uifabric/example-app-base", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/fabric-website/toggle-cursor-pointer_2017-06-09-21-07.json b/common/changes/@uifabric/fabric-website/miwhea-website-update-header_2017-06-09-19-15.json similarity index 53% rename from common/changes/@uifabric/fabric-website/toggle-cursor-pointer_2017-06-09-21-07.json rename to common/changes/@uifabric/fabric-website/miwhea-website-update-header_2017-06-09-19-15.json index 477fdb89846cb..ca46d03d32883 100644 --- a/common/changes/@uifabric/fabric-website/toggle-cursor-pointer_2017-06-09-21-07.json +++ b/common/changes/@uifabric/fabric-website/miwhea-website-update-header_2017-06-09-19-15.json @@ -1,11 +1,11 @@ { "changes": [ { - "comment": "", "packageName": "@uifabric/fabric-website", - "type": "none" + "comment": "Fix issues with dev.office.com header", + "type": "patch" } ], "packageName": "@uifabric/fabric-website", - "email": "miclo@microsoft.com" + "email": "miwhea@microsoft.com" } \ No newline at end of file diff --git a/common/changes/@uifabric/fabric-website/types-switch-from-props-to-attributes_2017-06-09-20-27.json b/common/changes/@uifabric/fabric-website/types-switch-from-props-to-attributes_2017-06-09-20-27.json deleted file mode 100644 index 477fdb89846cb..0000000000000 --- a/common/changes/@uifabric/fabric-website/types-switch-from-props-to-attributes_2017-06-09-20-27.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/fabric-website", - "type": "none" - } - ], - "packageName": "@uifabric/fabric-website", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/styling/toggle-cursor-pointer_2017-06-09-21-07.json b/common/changes/@uifabric/styling/toggle-cursor-pointer_2017-06-09-21-07.json deleted file mode 100644 index 1e116b37d633c..0000000000000 --- a/common/changes/@uifabric/styling/toggle-cursor-pointer_2017-06-09-21-07.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/styling", - "type": "none" - } - ], - "packageName": "@uifabric/styling", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/styling/types-switch-from-props-to-attributes_2017-06-09-20-27.json b/common/changes/@uifabric/styling/types-switch-from-props-to-attributes_2017-06-09-20-27.json deleted file mode 100644 index 1e116b37d633c..0000000000000 --- a/common/changes/@uifabric/styling/types-switch-from-props-to-attributes_2017-06-09-20-27.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/styling", - "type": "none" - } - ], - "packageName": "@uifabric/styling", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/utilities/toggle-cursor-pointer_2017-06-09-21-07.json b/common/changes/@uifabric/utilities/toggle-cursor-pointer_2017-06-09-21-07.json deleted file mode 100644 index eccc273e30dae..0000000000000 --- a/common/changes/@uifabric/utilities/toggle-cursor-pointer_2017-06-09-21-07.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/utilities", - "type": "none" - } - ], - "packageName": "@uifabric/utilities", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/@uifabric/utilities/types-switch-from-props-to-attributes_2017-06-09-20-27.json b/common/changes/@uifabric/utilities/types-switch-from-props-to-attributes_2017-06-09-20-27.json deleted file mode 100644 index eccc273e30dae..0000000000000 --- a/common/changes/@uifabric/utilities/types-switch-from-props-to-attributes_2017-06-09-20-27.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "comment": "", - "packageName": "@uifabric/utilities", - "type": "none" - } - ], - "packageName": "@uifabric/utilities", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/toggle-cursor-pointer_2017-06-09-21-07.json b/common/changes/office-ui-fabric-react/toggle-cursor-pointer_2017-06-09-21-07.json deleted file mode 100644 index 091bc2cd6b101..0000000000000 --- a/common/changes/office-ui-fabric-react/toggle-cursor-pointer_2017-06-09-21-07.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "office-ui-fabric-react", - "comment": "Toggle: add 'cursor: pointer'", - "type": "patch" - } - ], - "packageName": "office-ui-fabric-react", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/types-switch-from-props-to-attributes_2017-06-09-20-27.json b/common/changes/office-ui-fabric-react/types-switch-from-props-to-attributes_2017-06-09-20-27.json deleted file mode 100644 index 2fef18e362d6a..0000000000000 --- a/common/changes/office-ui-fabric-react/types-switch-from-props-to-attributes_2017-06-09-20-27.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "changes": [ - { - "packageName": "office-ui-fabric-react", - "comment": "Component properties now extend React.HTMLAttributes, rather than React.HTMLProps", - "type": "minor" - } - ], - "packageName": "office-ui-fabric-react", - "email": "miclo@microsoft.com" -} \ No newline at end of file diff --git a/common/changes/office-ui-fabric-react/withResponsiveModeError_2017-06-08-12-44.json b/common/changes/office-ui-fabric-react/withResponsiveModeError_2017-06-08-12-44.json new file mode 100644 index 0000000000000..d1aee3540cd18 --- /dev/null +++ b/common/changes/office-ui-fabric-react/withResponsiveModeError_2017-06-08-12-44.json @@ -0,0 +1,11 @@ +{ + "changes": [ + { + "packageName": "office-ui-fabric-react", + "comment": "withResponsiveMode: Adding error handling around the case where window.innerWidth throws an exception.", + "type": "patch" + } + ], + "packageName": "office-ui-fabric-react", + "email": "admitt@microsoft.com" +} \ No newline at end of file diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index 07393b8dce687..50bf39ecf082f 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -1399,6 +1399,11 @@ "resolved": "https://registry.npmjs.org/@types/es6-promise/-/es6-promise-0.0.32.tgz", "integrity": "sha1-O89E+x5Cnz33YYjIxth0Rjujcf0=" }, + "@types/es6-weak-map": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/es6-weak-map/-/es6-weak-map-1.2.0.tgz", + "integrity": "sha1-2fb4WDaaiLvGnvQCOAgfLWu8hhQ=" + }, "@types/express": { "version": "4.0.35", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.0.35.tgz", @@ -1627,9 +1632,9 @@ } }, "@uifabric/styling": { - "version": "0.7.1", - "resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-0.7.1.tgz", - "integrity": "sha512-PZ0ryYqj1Wu4Ojsb7e2VesvgsiY9Lghz9ByEXirywk0im/83fB58exrLtLUBsBU6H037kp8q2RaO2wuaQEnwMw==" + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-0.7.2.tgz", + "integrity": "sha512-HZLvm7OU+gXeXO+QHnAJXimzBtEq/UoQwCUTb+gdFJP0oU43gLm40G1G+tPDjS0+ZB2CIIrkH7v0jQ2paKprSg==" }, "@uifabric/utilities": { "version": "4.1.2", @@ -2281,9 +2286,9 @@ "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=" }, "caniuse-db": { - "version": "1.0.30000680", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000680.tgz", - "integrity": "sha1-126+quuC49mVK/3FwjHE+DzUgUQ=" + "version": "1.0.30000683", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000683.tgz", + "integrity": "sha1-WLV+0eC7naVOrxRimFFHu+Fmefo=" }, "capture-stack-trace": { "version": "1.0.0", @@ -2358,9 +2363,9 @@ "integrity": "sha1-s7026T3Uy/s5WjwmiWNSRFJlwFs=" }, "clean-css": { - "version": "3.4.26", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.26.tgz", - "integrity": "sha1-VTI7NE/zvO5oSi6sgck9+Ppz3us=", + "version": "3.4.27", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-3.4.27.tgz", + "integrity": "sha1-re91sxwWD/pdcvTeZ5ZuJmDBolU=", "dependencies": { "commander": { "version": "2.8.1", @@ -2442,9 +2447,9 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=" }, "coa": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.2.tgz", - "integrity": "sha1-K6n+w7SqQ9eknX5sNWHpIGG2vOw=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/coa/-/coa-1.0.3.tgz", + "integrity": "sha1-G1Sl4dz3fJkEVdTe6pjFZEFtyJM=" }, "code-point-at": { "version": "1.1.0", @@ -2758,9 +2763,9 @@ "integrity": "sha1-ElYDfsufDF9549bvE14wdwGEuYI=", "dependencies": { "lru-cache": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz", - "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==" } } }, @@ -2770,9 +2775,9 @@ "integrity": "sha1-hF/wwINKPe2dFg2sptOQkGuyiMw=", "dependencies": { "lru-cache": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.0.tgz", - "integrity": "sha512-aHGs865JXz6bkB4AHL+3AhyvTFKL3iZamKVWjIUKnXOXyasJvqPK8WAjOnAQKQZVpeXDVz19u1DD0r/12bWAdQ==" + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.1.tgz", + "integrity": "sha512-q4spe4KTfsAS1SUHLO0wz8Qiyf1+vMIAgpRYioFYDMNqKfHQbg+AVDH3i4fvpl71/P1L0dBl+fQi+P37UYf0ew==" } } }, @@ -3181,9 +3186,9 @@ "integrity": "sha1-R5Y2v6P+Ox3r1SCH8KyyBLTxnIg=" }, "electron-to-chromium": { - "version": "1.3.13", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.13.tgz", - "integrity": "sha1-GzperObgh7teJXoQCwy/6Bsokfw=" + "version": "1.3.14", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.14.tgz", + "integrity": "sha1-ZK8Pnv08PGrNV9cfg7Scp+6cS0M=" }, "elliptic": { "version": "6.4.0", @@ -5209,14 +5214,14 @@ "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=" }, "globule": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.1.0.tgz", - "integrity": "sha1-xJNS5NwYPYWJPuglOF65lLtt9F8=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=" }, "lodash": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", - "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" }, "node-sass": { "version": "3.13.1", @@ -7220,14 +7225,14 @@ "integrity": "sha1-hHIkZ3rbiHDWeSV+0ziP22HkAQU=" }, "globule": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-1.1.0.tgz", - "integrity": "sha1-xJNS5NwYPYWJPuglOF65lLtt9F8=" + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/globule/-/globule-1.2.0.tgz", + "integrity": "sha1-HcScaCLdnoovoAuiopUAboZkvQk=" }, "lodash": { - "version": "4.16.6", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.16.6.tgz", - "integrity": "sha1-0iyaxmAojzhD4Wun0rXQbMon13c=" + "version": "4.17.4", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.4.tgz", + "integrity": "sha1-eCA6TRwyiuHYbcpkYONptX9AVa4=" } } }, @@ -7359,9 +7364,9 @@ "integrity": "sha1-EEEktsYCxnlogaBCVB0220OlJk4=" }, "office-ui-fabric-core": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/office-ui-fabric-core/-/office-ui-fabric-core-7.0.0.tgz", - "integrity": "sha1-5rl2yY1wuKoAvVAGTIgOE3dFiY4=" + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/office-ui-fabric-core/-/office-ui-fabric-core-7.1.1.tgz", + "integrity": "sha1-hm7CYioSwEE7X2gTbvy5f66o8WY=" }, "office-ui-fabric-react": { "version": "4.4.2", @@ -8151,9 +8156,28 @@ "integrity": "sha1-T2ih3Arli9P7lYSMMDJNt11kNgs=" }, "randomatic": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz", - "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs=" + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.7.tgz", + "integrity": "sha512-D5JUjPyJbaJDkuAazpVnSfVkLlpeO3wDlPROTMLGKG1zMFNFRgrciKo1ltz/AzNTkqE0HzDx655QOL51N06how==", + "dependencies": { + "is-number": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz", + "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=" + } + } + }, + "kind-of": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", + "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=" + } + } }, "randombytes": { "version": "2.0.5", diff --git a/package-lock.json b/package-lock.json deleted file mode 100644 index e7573fca22684..0000000000000 --- a/package-lock.json +++ /dev/null @@ -1,2148 +0,0 @@ -{ - "name": "office-ui-fabric-react-repo", - "version": "1.0.0", - "lockfileVersion": 1, - "dependencies": { - "@microsoft/package-deps-hash": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/@microsoft/package-deps-hash/-/package-deps-hash-2.0.2.tgz", - "integrity": "sha1-e17NHr2RS2YIvwBUdKnQZ81MmUg=", - "dev": true - }, - "@microsoft/rush": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/rush/-/rush-3.0.7.tgz", - "integrity": "sha1-3fehsUYJ+0E1Aoej9anz70gVC7w=", - "dev": true - }, - "@microsoft/rush-lib": { - "version": "3.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/rush-lib/-/rush-lib-3.0.7.tgz", - "integrity": "sha1-bKgpmEgMMxDKcHqeWW+NXAA+MMQ=", - "dev": true - }, - "@microsoft/stream-collator": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@microsoft/stream-collator/-/stream-collator-2.0.0.tgz", - "integrity": "sha1-O4vFx28J03IFX8LtV+0bCKsChyM=", - "dev": true - }, - "@microsoft/ts-command-line": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@microsoft/ts-command-line/-/ts-command-line-2.0.0.tgz", - "integrity": "sha1-4+OOiCRkLlQAu0Sl9xs+hVT/e4s=", - "dev": true - }, - "@types/es6-collections": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@types/es6-collections/-/es6-collections-0.5.29.tgz", - "integrity": "sha1-7CKDH36N5zY3vxVmD52MomlGmA0=", - "dev": true - }, - "@types/fs-extra": { - "version": "0.0.37", - "resolved": "https://registry.npmjs.org/@types/fs-extra/-/fs-extra-0.0.37.tgz", - "integrity": "sha1-GV8RvNmhuX2eQSxrZombVFRxofc=", - "dev": true - }, - "@types/node": { - "version": "6.0.62", - "resolved": "https://registry.npmjs.org/@types/node/-/node-6.0.62.tgz", - "integrity": "sha1-hSIsB3tU8ltXQXu3CLn4d72jf4k=", - "dev": true - }, - "@types/z-schema": { - "version": "3.16.31", - "resolved": "https://registry.npmjs.org/@types/z-schema/-/z-schema-3.16.31.tgz", - "integrity": "sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw=", - "dev": true - }, - "agent-base": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-2.1.0.tgz", - "integrity": "sha1-GTRV5DR7ymsFhHy4HpObsyVEbag=", - "dev": true, - "dependencies": { - "semver": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.0.3.tgz", - "integrity": "sha1-d0Zt5YnNXTyV8TiqeLxWmjy10no=", - "dev": true - } - } - }, - "ajv": { - "version": "4.11.8", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", - "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", - "dev": true, - "optional": true - }, - "ansi-escapes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", - "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", - "dev": true - }, - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "archy": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=", - "dev": true - }, - "argparse": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.9.tgz", - "integrity": "sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY=", - "dev": true - }, - "arr-diff": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/arr-diff/-/arr-diff-2.0.0.tgz", - "integrity": "sha1-jzuCf5Vai9ZpaX5KQlasPOrjVs8=", - "dev": true - }, - "arr-flatten": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.0.3.tgz", - "integrity": "sha1-onTthawIhJtr14R8RYB0XcUa37E=", - "dev": true - }, - "array-differ": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/array-differ/-/array-differ-1.0.0.tgz", - "integrity": "sha1-7/UuN1gknTO+QCuLuOVkuytdQDE=", - "dev": true - }, - "array-uniq": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", - "integrity": "sha1-r2rId6Jcx/dOBYiUdThY39sk/bY=", - "dev": true - }, - "array-unique": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/array-unique/-/array-unique-0.2.1.tgz", - "integrity": "sha1-odl8yvy8JiXMcPrc6zalDFiwGlM=", - "dev": true - }, - "asap": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.5.tgz", - "integrity": "sha1-UidltQw1EEkOUtfc/ghe+bqWlY8=", - "dev": true - }, - "asn1": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.3.tgz", - "integrity": "sha1-2sh4dxPJlmhJ/IGAd36+nB3fO4Y=", - "dev": true, - "optional": true - }, - "assert-plus": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-0.2.0.tgz", - "integrity": "sha1-104bh+ev/A24qttwIfP+SBAasjQ=", - "dev": true, - "optional": true - }, - "asynckit": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", - "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", - "dev": true, - "optional": true - }, - "aws-sign2": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.6.0.tgz", - "integrity": "sha1-FDQt0428yU0OW4fXY81jYSwOeU8=", - "dev": true, - "optional": true - }, - "aws4": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.6.0.tgz", - "integrity": "sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=", - "dev": true, - "optional": true - }, - "balanced-match": { - "version": "0.4.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-0.4.2.tgz", - "integrity": "sha1-yz8+PHMtwPAe5wtAPzAuYddwmDg=", - "dev": true - }, - "bcrypt-pbkdf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz", - "integrity": "sha1-Y7xdy2EzG5K8Bf1SiVPDNGKgb40=", - "dev": true, - "optional": true - }, - "beeper": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/beeper/-/beeper-1.1.1.tgz", - "integrity": "sha1-5tXqjF2tABMEpwsiY4RH9pyy+Ak=", - "dev": true - }, - "boom": { - "version": "2.10.1", - "resolved": "https://registry.npmjs.org/boom/-/boom-2.10.1.tgz", - "integrity": "sha1-OciRjO/1eZ+D+UkqhI9iWt0Mdm8=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.7", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.7.tgz", - "integrity": "sha1-Pv/DxQ4ABTH7cg6v+A8K6O8jz1k=", - "dev": true - }, - "braces": { - "version": "1.8.5", - "resolved": "https://registry.npmjs.org/braces/-/braces-1.8.5.tgz", - "integrity": "sha1-uneWLhLf+WnWt2cR6RS3N4V79qc=", - "dev": true - }, - "buffer-shims": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/buffer-shims/-/buffer-shims-1.0.0.tgz", - "integrity": "sha1-mXjOMXOIxkmth5MCjDR37wRKi1E=", - "dev": true - }, - "builtin-modules": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", - "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", - "dev": true - }, - "builtins": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/builtins/-/builtins-1.0.3.tgz", - "integrity": "sha1-y5T662HIaWRR2zZTThQi+U8K7og=", - "dev": true - }, - "camelcase": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", - "integrity": "sha1-MvxLn82vhF/N9+c7uXysImHwqwo=", - "dev": true - }, - "caseless": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", - "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", - "dev": true, - "optional": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true - }, - "cli-cursor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", - "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", - "dev": true - }, - "cli-width": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.1.0.tgz", - "integrity": "sha1-sjTKIJsp72b8UY2bmNWEewDt8Ao=", - "dev": true - }, - "cliui": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-3.2.0.tgz", - "integrity": "sha1-EgYBU3qRbSmUD5NNo7SNWFo5IT0=", - "dev": true - }, - "clone": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.2.tgz", - "integrity": "sha1-Jgt6meux7f4kdTgXX3gyQ8sZ0Uk=", - "dev": true - }, - "clone-stats": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/clone-stats/-/clone-stats-0.0.1.tgz", - "integrity": "sha1-uI+UqCzzi4eR1YBG6kAprYjKmdE=", - "dev": true - }, - "co": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", - "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", - "dev": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", - "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true - }, - "colors": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/colors/-/colors-1.1.2.tgz", - "integrity": "sha1-FopHAXVran9RoSzgyXv6KMCE7WM=", - "dev": true - }, - "combined-stream": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.5.tgz", - "integrity": "sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=", - "dev": true - }, - "commander": { - "version": "2.9.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.9.0.tgz", - "integrity": "sha1-nJkJQXbhIkDLItbFFGCYQA/g99Q=", - "dev": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "concat-stream": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.0.tgz", - "integrity": "sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "cryptiles": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/cryptiles/-/cryptiles-2.0.5.tgz", - "integrity": "sha1-O9/s3GCBR8HGcgL6KR59ylnqo7g=", - "dev": true, - "optional": true - }, - "dashdash": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", - "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", - "dev": true, - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } - } - }, - "dateformat": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-2.0.0.tgz", - "integrity": "sha1-J0Pjq7XD/CRi5SfcpEXgTp9N7hc=", - "dev": true - }, - "debug": { - "version": "2.6.8", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.8.tgz", - "integrity": "sha1-5zFTHKLt4n0YgiJCfaF4IdaP9Pw=", - "dev": true - }, - "debuglog": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", - "integrity": "sha1-qiT/uaw9+aI1GDfPstJ5NgzXhJI=", - "dev": true - }, - "decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=", - "dev": true - }, - "defaults": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.3.tgz", - "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", - "dev": true - }, - "delayed-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", - "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true - }, - "deprecated": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/deprecated/-/deprecated-0.0.1.tgz", - "integrity": "sha1-+cmvVGSvoeepcUWKi97yqpTVuxk=", - "dev": true - }, - "detect-file": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/detect-file/-/detect-file-0.1.0.tgz", - "integrity": "sha1-STXe39lIhkjgBrASlWbpOGcR6mM=", - "dev": true - }, - "dezalgo": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz", - "integrity": "sha1-f3Qt4Gb8dIvI24IFad3c5Jvw1FY=", - "dev": true - }, - "duplexer2": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/duplexer2/-/duplexer2-0.0.2.tgz", - "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", - "dev": true, - "dependencies": { - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.1.14", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz", - "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - } - } - }, - "ecc-jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz", - "integrity": "sha1-D8c6ntXw1Tw4GTOYUj735UN3dQU=", - "dev": true, - "optional": true - }, - "end-of-stream": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-0.1.5.tgz", - "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", - "dev": true, - "dependencies": { - "once": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/once/-/once-1.3.3.tgz", - "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", - "dev": true - } - } - }, - "error-ex": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.1.tgz", - "integrity": "sha1-+FWobOYa3E6GIcPNoh56dhLDqNw=", - "dev": true - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "exit-hook": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", - "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", - "dev": true - }, - "expand-brackets": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/expand-brackets/-/expand-brackets-0.1.5.tgz", - "integrity": "sha1-3wcoTjQqgHzXM6xa9yQR5YHRF3s=", - "dev": true - }, - "expand-range": { - "version": "1.8.2", - "resolved": "https://registry.npmjs.org/expand-range/-/expand-range-1.8.2.tgz", - "integrity": "sha1-opnv/TNf4nIeuujiV+x5ZE/IUzc=", - "dev": true - }, - "expand-tilde": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-1.2.2.tgz", - "integrity": "sha1-C4HrqJflo9MdHD0QL48BRB5VlEk=", - "dev": true - }, - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "external-editor": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-1.1.1.tgz", - "integrity": "sha1-Etew24UPf/fnCBuvQAVwAGDEYAs=", - "dev": true - }, - "extglob": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/extglob/-/extglob-0.3.2.tgz", - "integrity": "sha1-Lhj/PS9JqydlzskCPwEdqo2DSaE=", - "dev": true - }, - "extsprintf": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.0.2.tgz", - "integrity": "sha1-4QgOBljjALBilJkMxw4VAiNf1VA=", - "dev": true - }, - "fancy-log": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/fancy-log/-/fancy-log-1.3.0.tgz", - "integrity": "sha1-Rb4X0Cu5kX1gzP/UmVyZnmyMmUg=", - "dev": true - }, - "figures": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", - "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", - "dev": true - }, - "filename-regex": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/filename-regex/-/filename-regex-2.0.1.tgz", - "integrity": "sha1-wcS5vuPglyXdsQa3XB4wH+LxiyY=", - "dev": true - }, - "fill-range": { - "version": "2.2.3", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-2.2.3.tgz", - "integrity": "sha1-ULd9/X5Gm8dJJHCWNpn+eoSFpyM=", - "dev": true - }, - "find-index": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/find-index/-/find-index-0.1.1.tgz", - "integrity": "sha1-Z101iyyjiS15Whq0cjL4tuLg3eQ=", - "dev": true - }, - "find-up": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", - "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", - "dev": true - }, - "findup-sync": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/findup-sync/-/findup-sync-0.4.3.tgz", - "integrity": "sha1-QAQ5Kee8YK3wt/SCfExudaDeyhI=", - "dev": true - }, - "fined": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/fined/-/fined-1.0.2.tgz", - "integrity": "sha1-WyhCS3YNdZiWC374SA3/itNmDpc=", - "dev": true - }, - "first-chunk-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/first-chunk-stream/-/first-chunk-stream-1.0.0.tgz", - "integrity": "sha1-Wb+1DNkF9g18OUzT2ayqtOatk04=", - "dev": true - }, - "flagged-respawn": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/flagged-respawn/-/flagged-respawn-0.3.2.tgz", - "integrity": "sha1-/xke3c1wiKZ1smEP/8l2vpuAdLU=", - "dev": true - }, - "follow-redirects": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-0.0.7.tgz", - "integrity": "sha1-NLkLqyqRGqNHVx2pDyK9NuzYqRk=", - "dev": true - }, - "for-in": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/for-in/-/for-in-1.0.2.tgz", - "integrity": "sha1-gQaNKVqBQuwKxybG4iAMMPttXoA=", - "dev": true - }, - "for-own": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/for-own/-/for-own-0.1.5.tgz", - "integrity": "sha1-UmXGgaTylNq78XyVCbZ2OqhFEM4=", - "dev": true - }, - "forever-agent": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", - "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true, - "optional": true - }, - "form-data": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.1.4.tgz", - "integrity": "sha1-M8GDrPGTJ27KqYFDpp6Uv+4XUNE=", - "dev": true, - "optional": true - }, - "fs-exists-sync": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz", - "integrity": "sha1-mC1ok6+RjnLQjeyehnP/K1qNat0=", - "dev": true - }, - "fs-extra": { - "version": "0.26.7", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-0.26.7.tgz", - "integrity": "sha1-muH92UiXeY7at20JGM9C0MMYT6k=", - "dev": true - }, - "fs.realpath": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", - "dev": true - }, - "gaze": { - "version": "0.5.2", - "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.5.2.tgz", - "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", - "dev": true - }, - "get-caller-file": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.2.tgz", - "integrity": "sha1-9wLmMSfn4jHBYKgMFVSstw1QR+U=", - "dev": true - }, - "getpass": { - "version": "0.1.7", - "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", - "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", - "dev": true, - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } - } - }, - "git-repo-info": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/git-repo-info/-/git-repo-info-1.1.4.tgz", - "integrity": "sha1-E0n6OIinZh6h/2OgR8L/Q7PglgI=", - "dev": true - }, - "github": { - "version": "7.3.2", - "resolved": "https://registry.npmjs.org/github/-/github-7.3.2.tgz", - "integrity": "sha1-/hDN5pZDUsXZHhGnYW0m1f+2+Hs=", - "dev": true - }, - "glob": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.0.6.tgz", - "integrity": "sha1-IRuvr0nlJbjNkyYNFKsTYVKz9Xo=", - "dev": true - }, - "glob-base": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/glob-base/-/glob-base-0.3.0.tgz", - "integrity": "sha1-27Fk9iIbHAscz4Kuoyi0l98Oo8Q=", - "dev": true - }, - "glob-escape": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/glob-escape/-/glob-escape-0.0.2.tgz", - "integrity": "sha1-nCf3gh7RwTd1gvPv2VWOP2dWKO0=", - "dev": true - }, - "glob-parent": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-2.0.0.tgz", - "integrity": "sha1-gTg9ctsFT8zPUzbaqQLxgvbtuyg=", - "dev": true - }, - "glob-stream": { - "version": "3.1.18", - "resolved": "https://registry.npmjs.org/glob-stream/-/glob-stream-3.1.18.tgz", - "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", - "dev": true, - "dependencies": { - "glob": { - "version": "4.5.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", - "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "minimatch": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", - "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true - } - } - }, - "glob-watcher": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-0.0.6.tgz", - "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", - "dev": true - }, - "glob2base": { - "version": "0.0.12", - "resolved": "https://registry.npmjs.org/glob2base/-/glob2base-0.0.12.tgz", - "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", - "dev": true - }, - "global-modules": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-0.2.3.tgz", - "integrity": "sha1-6lo77ULG1s6ZWk+KEmm12uIjgo0=", - "dev": true - }, - "global-prefix": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-0.1.5.tgz", - "integrity": "sha1-jTvGuNo8qBEqFg2NSW/wRiv+948=", - "dev": true - }, - "globule": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/globule/-/globule-0.1.0.tgz", - "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", - "dev": true, - "dependencies": { - "glob": { - "version": "3.1.21", - "resolved": "https://registry.npmjs.org/glob/-/glob-3.1.21.tgz", - "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", - "dev": true - }, - "graceful-fs": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-1.2.3.tgz", - "integrity": "sha1-FaSAaldUfLLS2/J/QuiajDRRs2Q=", - "dev": true - }, - "inherits": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-1.0.2.tgz", - "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", - "dev": true - }, - "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", - "dev": true - }, - "minimatch": { - "version": "0.2.14", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", - "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", - "dev": true - } - } - }, - "glogg": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/glogg/-/glogg-1.0.0.tgz", - "integrity": "sha1-f+DxmfV6yQbPUS/urY+Q7kooT8U=", - "dev": true - }, - "graceful-fs": { - "version": "4.1.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.1.11.tgz", - "integrity": "sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg=", - "dev": true - }, - "graceful-readlink": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/graceful-readlink/-/graceful-readlink-1.0.1.tgz", - "integrity": "sha1-TK+tdrxi8C+gObL5Tpo906ORpyU=", - "dev": true, - "optional": true - }, - "gulp": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/gulp/-/gulp-3.9.1.tgz", - "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", - "dev": true, - "dependencies": { - "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true - } - } - }, - "gulp-util": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/gulp-util/-/gulp-util-3.0.8.tgz", - "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", - "dev": true, - "dependencies": { - "object-assign": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-3.0.0.tgz", - "integrity": "sha1-m+3VygiXlJvKR+f/QIBi1Un1h/I=", - "dev": true - } - } - }, - "gulplog": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/gulplog/-/gulplog-1.0.0.tgz", - "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", - "dev": true - }, - "har-schema": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-1.0.5.tgz", - "integrity": "sha1-0mMTX0MwfALGAq/I/pWXDAFRNp4=", - "dev": true, - "optional": true - }, - "har-validator": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-4.2.1.tgz", - "integrity": "sha1-M0gdDxu/9gDdID11gSpqX7oALio=", - "dev": true, - "optional": true - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true - }, - "has-gulplog": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/has-gulplog/-/has-gulplog-0.1.0.tgz", - "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", - "dev": true - }, - "hawk": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/hawk/-/hawk-3.1.3.tgz", - "integrity": "sha1-B4REvXwWQLD+VA0sm3PVlnjo4cQ=", - "dev": true, - "optional": true - }, - "hoek": { - "version": "2.16.3", - "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", - "integrity": "sha1-ILt0A9POo5jpHcRxCo/xuCdKJe0=", - "dev": true - }, - "homedir-polyfill": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz", - "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", - "dev": true - }, - "hosted-git-info": { - "version": "2.4.2", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.4.2.tgz", - "integrity": "sha1-AHa59GonBQbduq6lZJaJdGBhKmc=", - "dev": true - }, - "http-signature": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.1.1.tgz", - "integrity": "sha1-33LiZwZs0Kxn+3at+OE0qPvPkb8=", - "dev": true, - "optional": true - }, - "https-proxy-agent": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-1.0.0.tgz", - "integrity": "sha1-NffabEjOTdv6JkiRrFk+5f+GceY=", - "dev": true - }, - "inflight": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", - "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, - "ini": { - "version": "1.3.4", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.4.tgz", - "integrity": "sha1-BTfLedr1m1mhpRff9wbIbsA5Fi4=", - "dev": true - }, - "inquirer": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-1.2.3.tgz", - "integrity": "sha1-TexvMvN+97sLLtPx0aXD9UUHSRg=", - "dev": true - }, - "interpret": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.0.3.tgz", - "integrity": "sha1-y8NcYu7uc/Gat7EKgBURQBr8D5A=", - "dev": true - }, - "invert-kv": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", - "integrity": "sha1-EEqOSqym09jNFXqO+L+rLXo//bY=", - "dev": true - }, - "is-absolute": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/is-absolute/-/is-absolute-0.2.6.tgz", - "integrity": "sha1-IN5p89uULvLYe5wto28XIjWxtes=", - "dev": true - }, - "is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", - "dev": true - }, - "is-buffer": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.5.tgz", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", - "dev": true - }, - "is-builtin-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-builtin-module/-/is-builtin-module-1.0.0.tgz", - "integrity": "sha1-VAVy0096wxGfj3bDDLwbHgN6/74=", - "dev": true - }, - "is-dotfile": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-dotfile/-/is-dotfile-1.0.2.tgz", - "integrity": "sha1-LBMjg/ORmfjtwmjKAbmwB9IFzE0=", - "dev": true - }, - "is-equal-shallow": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz", - "integrity": "sha1-IjgJj8Ih3gvPpdnqxMRdY4qhxTQ=", - "dev": true - }, - "is-extendable": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-0.1.1.tgz", - "integrity": "sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik=", - "dev": true - }, - "is-extglob": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-1.0.0.tgz", - "integrity": "sha1-rEaBd8SUNAWgkvyPKXYMb/xiBsA=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", - "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", - "dev": true - }, - "is-glob": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-2.0.1.tgz", - "integrity": "sha1-0Jb5JqPe1WAPP9/ZEZjLCIjC2GM=", - "dev": true - }, - "is-number": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-2.1.0.tgz", - "integrity": "sha1-Afy7s5NGOlSPL0ZszhbezknbkI8=", - "dev": true - }, - "is-posix-bracket": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz", - "integrity": "sha1-MzTceXdDaOkvAW5vvAqI9c1ua8Q=", - "dev": true - }, - "is-primitive": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-primitive/-/is-primitive-2.0.0.tgz", - "integrity": "sha1-IHurkWOEmcB7Kt8kCkGochADRXU=", - "dev": true - }, - "is-promise": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz", - "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=", - "dev": true - }, - "is-relative": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-relative/-/is-relative-0.2.1.tgz", - "integrity": "sha1-0n9MfVFtF1+2ENuEu+7yPDvJeqU=", - "dev": true - }, - "is-typedarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", - "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", - "dev": true, - "optional": true - }, - "is-unc-path": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/is-unc-path/-/is-unc-path-0.1.2.tgz", - "integrity": "sha1-arBTpyVzwQJQ/0FqOBTDUXivObk=", - "dev": true - }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - }, - "is-windows": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-0.2.0.tgz", - "integrity": "sha1-3hqm1j6indJIc3tp8f+LgALSEIw=", - "dev": true - }, - "isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", - "dev": true - }, - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - }, - "isobject": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/isobject/-/isobject-2.1.0.tgz", - "integrity": "sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk=", - "dev": true - }, - "isstream": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", - "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", - "dev": true, - "optional": true - }, - "jju": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/jju/-/jju-1.3.0.tgz", - "integrity": "sha1-2t2e8BkkvHKLA/L3l5vb1i96Kqo=", - "dev": true - }, - "jodid25519": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/jodid25519/-/jodid25519-1.0.2.tgz", - "integrity": "sha1-BtSRIlUJNBlHfUJWM2BuDpB4KWc=", - "dev": true, - "optional": true - }, - "jsbn": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", - "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", - "dev": true, - "optional": true - }, - "json-parse-helpfulerror": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/json-parse-helpfulerror/-/json-parse-helpfulerror-1.0.3.tgz", - "integrity": "sha1-E/FM4C7tTpgSl7ZOueO5MuLdE9w=", - "dev": true - }, - "json-schema": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", - "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", - "dev": true, - "optional": true - }, - "json-stable-stringify": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", - "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", - "dev": true, - "optional": true - }, - "json-stringify-safe": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", - "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", - "dev": true, - "optional": true - }, - "jsonfile": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz", - "integrity": "sha1-NzaitCi4e72gzIO1P6PWM6NcKug=", - "dev": true - }, - "jsonify": { - "version": "0.0.0", - "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", - "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", - "dev": true, - "optional": true - }, - "jsprim": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.0.tgz", - "integrity": "sha1-o7h+QCmNjDgFUtjMdiigu5WiKRg=", - "dev": true, - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } - } - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true - }, - "klaw": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/klaw/-/klaw-1.3.1.tgz", - "integrity": "sha1-QIhDO0azsbolnXh4XY6W9zugJDk=", - "dev": true - }, - "lcid": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lcid/-/lcid-1.0.0.tgz", - "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", - "dev": true - }, - "liftoff": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/liftoff/-/liftoff-2.3.0.tgz", - "integrity": "sha1-qY8v9nGD2Lp8+soQVIvX/wVQs4U=", - "dev": true - }, - "load-json-file": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-1.1.0.tgz", - "integrity": "sha1-lWkFcI1YtLq0wiYbBPWfMcmTdMA=", - "dev": true, - "dependencies": { - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", - "dev": true - } - } - }, - "lodash": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.15.0.tgz", - "integrity": "sha1-MWI5HY8BQKoiz49rPDTWt/Y9Oqk=", - "dev": true - }, - "lodash._basecopy": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basecopy/-/lodash._basecopy-3.0.1.tgz", - "integrity": "sha1-jaDmqHbPNEwK2KVIghEd08XHyjY=", - "dev": true - }, - "lodash._basetostring": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._basetostring/-/lodash._basetostring-3.0.1.tgz", - "integrity": "sha1-0YYdh3+CSlL2aYMtyvPuFVZqB9U=", - "dev": true - }, - "lodash._basevalues": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._basevalues/-/lodash._basevalues-3.0.0.tgz", - "integrity": "sha1-W3dXYoAr3j0yl1A+JjAIIP32Ybc=", - "dev": true - }, - "lodash._getnative": { - "version": "3.9.1", - "resolved": "https://registry.npmjs.org/lodash._getnative/-/lodash._getnative-3.9.1.tgz", - "integrity": "sha1-VwvH3t5G1hzc3mh9ZdPuy6o6r/U=", - "dev": true - }, - "lodash._isiterateecall": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/lodash._isiterateecall/-/lodash._isiterateecall-3.0.9.tgz", - "integrity": "sha1-UgOte6Ql+uhCRg5pbbnPPmqsBXw=", - "dev": true - }, - "lodash._reescape": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reescape/-/lodash._reescape-3.0.0.tgz", - "integrity": "sha1-Kx1vXf4HyKNVdT5fJ/rH8c3hYWo=", - "dev": true - }, - "lodash._reevaluate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reevaluate/-/lodash._reevaluate-3.0.0.tgz", - "integrity": "sha1-WLx0xAZklTrgsSTYBpltrKQx4u0=", - "dev": true - }, - "lodash._reinterpolate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz", - "integrity": "sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0=", - "dev": true - }, - "lodash._root": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/lodash._root/-/lodash._root-3.0.1.tgz", - "integrity": "sha1-+6HEUkwZ7ppfgTa0YJ8BfPTe1pI=", - "dev": true - }, - "lodash.assignwith": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", - "integrity": "sha1-EnqX8CrcQXUalU0ksN4X4QDgOOs=", - "dev": true - }, - "lodash.escape": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/lodash.escape/-/lodash.escape-3.2.0.tgz", - "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", - "dev": true - }, - "lodash.get": { - "version": "4.4.2", - "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", - "integrity": "sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=", - "dev": true - }, - "lodash.isarguments": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz", - "integrity": "sha1-L1c9hcaiQon/AGY7SRwdM4/zRYo=", - "dev": true - }, - "lodash.isarray": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/lodash.isarray/-/lodash.isarray-3.0.4.tgz", - "integrity": "sha1-eeTriMNqgSKvhvhEqpvNhRtfu1U=", - "dev": true - }, - "lodash.isempty": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.isempty/-/lodash.isempty-4.4.0.tgz", - "integrity": "sha1-b4bL7di+TsmHvpqvM8loTbGzHn4=", - "dev": true - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha1-fFJqUtibRcRcxpC4gWO+BJf1UMs=", - "dev": true - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=", - "dev": true - }, - "lodash.keys": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/lodash.keys/-/lodash.keys-3.1.2.tgz", - "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", - "dev": true - }, - "lodash.mapvalues": { - "version": "4.6.0", - "resolved": "https://registry.npmjs.org/lodash.mapvalues/-/lodash.mapvalues-4.6.0.tgz", - "integrity": "sha1-G6+lAF3p3W9PJmaMMMo3IwzJaJw=", - "dev": true - }, - "lodash.pick": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/lodash.pick/-/lodash.pick-4.4.0.tgz", - "integrity": "sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM=", - "dev": true - }, - "lodash.restparam": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/lodash.restparam/-/lodash.restparam-3.6.1.tgz", - "integrity": "sha1-k2pOMJ7zMKdkXtQUWYbIWuWyCAU=", - "dev": true - }, - "lodash.template": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/lodash.template/-/lodash.template-3.6.2.tgz", - "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", - "dev": true - }, - "lodash.templatesettings": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lodash.templatesettings/-/lodash.templatesettings-3.1.1.tgz", - "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", - "dev": true - }, - "lru-cache": { - "version": "2.7.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", - "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", - "dev": true - }, - "map-cache": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/map-cache/-/map-cache-0.2.2.tgz", - "integrity": "sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8=", - "dev": true - }, - "micromatch": { - "version": "2.3.11", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-2.3.11.tgz", - "integrity": "sha1-hmd8l9FyCzY0MdBNDRUpO9OMFWU=", - "dev": true - }, - "mime": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/mime/-/mime-1.3.6.tgz", - "integrity": "sha1-WR2E02U6awtKO5343lqoEI5y5eA=", - "dev": true - }, - "mime-db": { - "version": "1.27.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.27.0.tgz", - "integrity": "sha1-gg9XIpa70g7CXtVeW13oaeVDbrE=", - "dev": true - }, - "mime-types": { - "version": "2.1.15", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.15.tgz", - "integrity": "sha1-pOv1BkCUVpI3uM9wBGd20J/JKu0=", - "dev": true - }, - "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", - "dev": true - }, - "minimist": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", - "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", - "dev": true - }, - "mkdirp": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", - "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", - "dev": true, - "dependencies": { - "minimist": { - "version": "0.0.8", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", - "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true - } - } - }, - "ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", - "dev": true - }, - "multipipe": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/multipipe/-/multipipe-0.1.2.tgz", - "integrity": "sha1-Ko8t33Du1WTf8tV/HhoTfZ8FB4s=", - "dev": true - }, - "mute-stream": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.6.tgz", - "integrity": "sha1-SJYrGeFp/R38JAs/HnMXYnu8R9s=", - "dev": true - }, - "natives": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.0.tgz", - "integrity": "sha1-6f+EFBimsux6SV6TmYT3jxY+bjE=", - "dev": true - }, - "netrc": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/netrc/-/netrc-0.1.4.tgz", - "integrity": "sha1-a+lPysqNd63gqWcNxGCRTJRHJEQ=", - "dev": true - }, - "normalize-package-data": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.3.8.tgz", - "integrity": "sha1-2Bntoqne29H/pWPqQHHZNngilbs=", - "dev": true - }, - "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "dev": true - }, - "number-is-nan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", - "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true - }, - "oauth-sign": { - "version": "0.8.2", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.8.2.tgz", - "integrity": "sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=", - "dev": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true - }, - "object.omit": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/object.omit/-/object.omit-2.0.1.tgz", - "integrity": "sha1-Gpx0SCnznbuFjHbKNXmuKlTr0fo=", - "dev": true - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "orchestrator": { - "version": "0.3.8", - "resolved": "https://registry.npmjs.org/orchestrator/-/orchestrator-0.3.8.tgz", - "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", - "dev": true - }, - "ordered-read-streams": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/ordered-read-streams/-/ordered-read-streams-0.1.0.tgz", - "integrity": "sha1-/VZamvjrRHO6abbtijQ1LLVS8SY=", - "dev": true - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", - "dev": true - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "dev": true - }, - "os-shim": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/os-shim/-/os-shim-0.1.3.tgz", - "integrity": "sha1-a2LDeRz3kJ6jXtRuF2WLtBfLORc=", - "dev": true - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", - "dev": true - }, - "parse-filepath": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parse-filepath/-/parse-filepath-1.0.1.tgz", - "integrity": "sha1-FZ1hVdQ5BNFsEO9piRHaHpGWm3M=", - "dev": true - }, - "parse-glob": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/parse-glob/-/parse-glob-3.0.4.tgz", - "integrity": "sha1-ssN2z7EfNVE7rdFz7wu246OIORw=", - "dev": true - }, - "parse-json": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-2.2.0.tgz", - "integrity": "sha1-9ID0BDTvgHQfhGkJn43qGPVaTck=", - "dev": true - }, - "parse-passwd": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/parse-passwd/-/parse-passwd-1.0.0.tgz", - "integrity": "sha1-bVuTSkVpk7I9N/QKOC1vFmao5cY=", - "dev": true - }, - "path-exists": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", - "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", - "dev": true - }, - "path-is-absolute": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", - "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "path-root": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/path-root/-/path-root-0.1.1.tgz", - "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", - "dev": true - }, - "path-root-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/path-root-regex/-/path-root-regex-0.1.2.tgz", - "integrity": "sha1-v8zcjfWxLcUsi0PsONGNcsBLqW0=", - "dev": true - }, - "path-type": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/path-type/-/path-type-1.1.0.tgz", - "integrity": "sha1-WcRPfuSR2nBNpBXaWkBwuk+P5EE=", - "dev": true - }, - "performance-now": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-0.2.0.tgz", - "integrity": "sha1-M+8wxcd9TqIcWlOGnZG1bY8lVeU=", - "dev": true, - "optional": true - }, - "pify": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true - }, - "pinkie": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", - "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", - "dev": true - }, - "pinkie-promise": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", - "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", - "dev": true - }, - "preserve": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/preserve/-/preserve-0.2.0.tgz", - "integrity": "sha1-gV7R9uvGWSb4ZbMQwHE7yzMVzks=", - "dev": true - }, - "pretty-hrtime": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", - "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", - "dev": true - }, - "process-nextick-args": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-1.0.7.tgz", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, - "punycode": { - "version": "1.4.1", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", - "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4=", - "dev": true, - "optional": true - }, - "qs": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.4.0.tgz", - "integrity": "sha1-E+JtKK1rD/qpExLNO/cI7TUecjM=", - "dev": true, - "optional": true - }, - "randomatic": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/randomatic/-/randomatic-1.1.6.tgz", - "integrity": "sha1-EQ3Kv/OX6dz/fAeJzMCkmt8exbs=", - "dev": true - }, - "read-package-json": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.0.5.tgz", - "integrity": "sha1-+Tpk5kFSnfaKCMZN5GOJ6KP4iEU=", - "dev": true, - "dependencies": { - "glob": { - "version": "7.1.2", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", - "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", - "dev": true - } - } - }, - "read-package-tree": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/read-package-tree/-/read-package-tree-5.1.5.tgz", - "integrity": "sha1-rOfmOBx2hPlwqqmPx8XStmat2rY=", - "dev": true - }, - "read-pkg": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-1.1.0.tgz", - "integrity": "sha1-9f+qXs0pyzHAR0vKfXVra7KePyg=", - "dev": true - }, - "read-pkg-up": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-1.0.1.tgz", - "integrity": "sha1-nWPBMnbAZZGNV/ACpX9AobZD+wI=", - "dev": true - }, - "readable-stream": { - "version": "2.2.9", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.2.9.tgz", - "integrity": "sha1-z3jsb0ptHrQ9JkiMrJfwQudLf8g=", - "dev": true - }, - "readdir-scoped-modules": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz", - "integrity": "sha1-n6+jfShr5dksuuve4DDcm19AZ0c=", - "dev": true - }, - "rechoir": { - "version": "0.6.2", - "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", - "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", - "dev": true - }, - "regex-cache": { - "version": "0.4.3", - "resolved": "https://registry.npmjs.org/regex-cache/-/regex-cache-0.4.3.tgz", - "integrity": "sha1-mxpsNdTQ3871cRrmUejp09cRQUU=", - "dev": true - }, - "remove-trailing-separator": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.0.1.tgz", - "integrity": "sha1-YV67lq9VlVLUv0BXyENtSGq2PMQ=", - "dev": true - }, - "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", - "dev": true - }, - "repeat-string": { - "version": "1.6.1", - "resolved": "https://registry.npmjs.org/repeat-string/-/repeat-string-1.6.1.tgz", - "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=", - "dev": true - }, - "replace-ext": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-0.0.1.tgz", - "integrity": "sha1-KbvZIHinOfC8zitO5B6DeVNSKSQ=", - "dev": true - }, - "request": { - "version": "2.81.0", - "resolved": "https://registry.npmjs.org/request/-/request-2.81.0.tgz", - "integrity": "sha1-xpKJRqDgbF+Nb4qTM0af/aRimKA=", - "dev": true, - "optional": true - }, - "require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=", - "dev": true - }, - "require-main-filename": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", - "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=", - "dev": true - }, - "resolve": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.3.3.tgz", - "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", - "dev": true - }, - "resolve-dir": { - "version": "0.1.1", - "resolved": "https://registry.npmjs.org/resolve-dir/-/resolve-dir-0.1.1.tgz", - "integrity": "sha1-shklmlYC+sXFxJatiUpujMQwJh4=", - "dev": true - }, - "restore-cursor": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", - "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", - "dev": true - }, - "rimraf": { - "version": "2.5.4", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.5.4.tgz", - "integrity": "sha1-loAAk8vxoMhr2VtGJUZ1NcKd+gQ=", - "dev": true - }, - "run-async": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz", - "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=", - "dev": true - }, - "rx": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/rx/-/rx-4.1.0.tgz", - "integrity": "sha1-pfE/957zt0D+MKqAP7CfmIBdR4I=", - "dev": true - }, - "safe-buffer": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.0.1.tgz", - "integrity": "sha1-0mPKVGls2KMGtcplUekt5XkY++c=", - "dev": true - }, - "semver": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", - "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", - "dev": true - }, - "sequencify": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/sequencify/-/sequencify-0.0.7.tgz", - "integrity": "sha1-kM/xnQLgcCf9dn9erT57ldHnOAw=", - "dev": true - }, - "set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=", - "dev": true - }, - "sigmund": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", - "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", - "dev": true - }, - "sntp": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/sntp/-/sntp-1.0.9.tgz", - "integrity": "sha1-ZUEYTMkK7qbG57NeJlkIJEPGYZg=", - "dev": true, - "optional": true - }, - "sparkles": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/sparkles/-/sparkles-1.0.0.tgz", - "integrity": "sha1-Gsu/tZJDbRC76PeFt8xvgoFQEsM=", - "dev": true - }, - "spawn-sync": { - "version": "1.0.15", - "resolved": "https://registry.npmjs.org/spawn-sync/-/spawn-sync-1.0.15.tgz", - "integrity": "sha1-sAeZVX63+wyDdsKdROih6mfldHY=", - "dev": true - }, - "spdx-correct": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-1.0.2.tgz", - "integrity": "sha1-SzBz2TP/UfORLwOsVRlJikFQ20A=", - "dev": true - }, - "spdx-expression-parse": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz", - "integrity": "sha1-m98vIOH0DtRH++JzJmGR/O1RYmw=", - "dev": true - }, - "spdx-license-ids": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz", - "integrity": "sha1-yd96NCRZSt5r0RkA1ZZpbcBrrFc=", - "dev": true - }, - "sprintf-js": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", - "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", - "dev": true - }, - "sshpk": { - "version": "1.13.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.13.0.tgz", - "integrity": "sha1-/yo+T9BEl1Vf7Zezmg/YL6+zozw=", - "dev": true, - "optional": true, - "dependencies": { - "assert-plus": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", - "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true, - "optional": true - } - } - }, - "stream-consume": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/stream-consume/-/stream-consume-0.1.0.tgz", - "integrity": "sha1-pB6tGm1ggc63n2WwYZAbbY89HQ8=", - "dev": true - }, - "string_decoder": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.0.1.tgz", - "integrity": "sha1-YuIA8DmVWmgQ2N8KM//A8BNmLZg=", - "dev": true - }, - "string-width": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", - "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", - "dev": true - }, - "stringstream": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/stringstream/-/stringstream-0.0.5.tgz", - "integrity": "sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=", - "dev": true, - "optional": true - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true - }, - "strip-bom": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-1.0.0.tgz", - "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", - "dev": true - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", - "dev": true - }, - "through": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", - "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", - "dev": true - }, - "through2": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.3.tgz", - "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", - "dev": true - }, - "tildify": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/tildify/-/tildify-1.2.0.tgz", - "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", - "dev": true - }, - "time-stamp": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/time-stamp/-/time-stamp-1.1.0.tgz", - "integrity": "sha1-dkpaEa9QVhkhsTPztE5hhofg9cM=", - "dev": true - }, - "tmp": { - "version": "0.0.29", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.29.tgz", - "integrity": "sha1-8lEl/w3Z2jzLDC3Tce4SiLuRKMA=", - "dev": true - }, - "tough-cookie": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.3.2.tgz", - "integrity": "sha1-8IH3bkyFcg5sN6X6ztc3FQ2EByo=", - "dev": true, - "optional": true - }, - "tunnel-agent": { - "version": "0.6.0", - "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", - "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", - "dev": true, - "optional": true - }, - "tweetnacl": { - "version": "0.14.5", - "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", - "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", - "dev": true, - "optional": true - }, - "typedarray": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", - "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", - "dev": true - }, - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - }, - "unique-stream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/unique-stream/-/unique-stream-1.0.0.tgz", - "integrity": "sha1-1ZpKdUJ0R9mqbJHnAmP40mpLEEs=", - "dev": true - }, - "user-home": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/user-home/-/user-home-1.1.1.tgz", - "integrity": "sha1-K1viOjK2Onyd640PKNSFcko98ZA=", - "dev": true - }, - "util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "uuid": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.0.1.tgz", - "integrity": "sha1-ZUS7ot/ajBzxfmKaOjBeK7H+5sE=", - "dev": true, - "optional": true - }, - "v8flags": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-2.1.1.tgz", - "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", - "dev": true - }, - "validate-npm-package-license": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz", - "integrity": "sha1-KAS6vnEq0zeUWaz74kdGqywwP7w=", - "dev": true - }, - "validator": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/validator/-/validator-5.7.0.tgz", - "integrity": "sha1-eoelgUa2laxIYHEUHAxJ1n2gXlw=", - "dev": true - }, - "verror": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.3.6.tgz", - "integrity": "sha1-z/XfEpRtKX0rqu+qJoniW+AcAFw=", - "dev": true, - "optional": true - }, - "vinyl": { - "version": "0.5.3", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.5.3.tgz", - "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", - "dev": true - }, - "vinyl-fs": { - "version": "0.3.14", - "resolved": "https://registry.npmjs.org/vinyl-fs/-/vinyl-fs-0.3.14.tgz", - "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", - "dev": true, - "dependencies": { - "clone": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/clone/-/clone-0.2.0.tgz", - "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", - "dev": true - }, - "graceful-fs": { - "version": "3.0.11", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-3.0.11.tgz", - "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", - "dev": true - }, - "isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", - "dev": true - }, - "readable-stream": { - "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", - "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", - "dev": true - }, - "string_decoder": { - "version": "0.10.31", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz", - "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ=", - "dev": true - }, - "through2": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/through2/-/through2-0.6.5.tgz", - "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", - "dev": true - }, - "vinyl": { - "version": "0.4.6", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-0.4.6.tgz", - "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", - "dev": true - } - } - }, - "which": { - "version": "1.2.14", - "resolved": "https://registry.npmjs.org/which/-/which-1.2.14.tgz", - "integrity": "sha1-mofEN48D6CfOyvGs31bHNsAcFOU=", - "dev": true - }, - "which-module": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-1.0.0.tgz", - "integrity": "sha1-u6Y8qGGUiZT/MHc2CJ47lgJsKk8=", - "dev": true - }, - "wordwrap": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", - "integrity": "sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=", - "dev": true - }, - "wrap-ansi": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", - "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", - "dev": true - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true - }, - "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=", - "dev": true - }, - "yargs": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz", - "integrity": "sha1-eC7CHvQDNF+DCoCMo9UTr1YGUgg=", - "dev": true - }, - "yargs-parser": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-4.2.1.tgz", - "integrity": "sha1-KczqwNxPA8bIe0qfIX3RjJ90hxw=", - "dev": true - }, - "z-schema": { - "version": "3.17.0", - "resolved": "https://registry.npmjs.org/z-schema/-/z-schema-3.17.0.tgz", - "integrity": "sha1-RCs4+denr3rke/z8NWhz6GFq0bc=", - "dev": true - } - } -} diff --git a/packages/example-app-base/CHANGELOG.json b/packages/example-app-base/CHANGELOG.json index 014bea7face7c..08652b4c964b0 100644 --- a/packages/example-app-base/CHANGELOG.json +++ b/packages/example-app-base/CHANGELOG.json @@ -1,6 +1,28 @@ { "name": "@uifabric/example-app-base", "entries": [ + { + "version": "3.0.1", + "tag": "@uifabric/example-app-base_v3.0.1", + "date": "Mon, 12 Jun 2017 01:47:18 GMT", + "comments": { + "patch": [ + { + "author": "Christian Gonzalez ", + "commit": "de7c49d2bd5e85af8d7ae0aee97eb9afd297966d", + "comment": "Enable no implicit any in utilities package" + } + ], + "dependency": [ + { + "comment": "Updating dependency \"@uifabric/styling\" from `>=0.7.2 <1.0.0` to `>=0.7.3 <1.0.0`" + }, + { + "comment": "Updating dependency \"@uifabric/utilities\" from `>=4.1.2 <5.0.0` to `>=4.1.2 <5.0.0`" + } + ] + } + }, { "version": "3.0.0", "tag": "@uifabric/example-app-base_v3.0.0", diff --git a/packages/example-app-base/CHANGELOG.md b/packages/example-app-base/CHANGELOG.md index fcb21bd63f64f..a0b8d5cd8457c 100644 --- a/packages/example-app-base/CHANGELOG.md +++ b/packages/example-app-base/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @uifabric/example-app-base -This log was last generated on Thu, 08 Jun 2017 00:18:05 GMT and should not be manually modified. +This log was last generated on Mon, 12 Jun 2017 01:47:18 GMT and should not be manually modified. + +## 3.0.1 +Mon, 12 Jun 2017 01:47:18 GMT + +### Patches + +- Enable no implicit any in utilities package ## 3.0.0 Thu, 08 Jun 2017 00:18:05 GMT diff --git a/packages/example-app-base/package.json b/packages/example-app-base/package.json index 8afd6f378caeb..b949a84192da1 100644 --- a/packages/example-app-base/package.json +++ b/packages/example-app-base/package.json @@ -1,6 +1,6 @@ { "name": "@uifabric/example-app-base", - "version": "3.0.0", + "version": "3.0.1", "description": "Office UI Fabric example app base utilities for building example sites.", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -40,7 +40,7 @@ "react-dom": "^0.14 || ^15.0.1-0 || ^16.0.0-0" }, "dependencies": { - "@uifabric/styling": ">=0.7.1 <1.0.0", + "@uifabric/styling": ">=0.7.3 <1.0.0", "@uifabric/utilities": ">=4.1.2 <5.0.0", "highlight.js": "^9.9.0", "office-ui-fabric-core": ">=7.0.0 <8.0.0", diff --git a/packages/example-component/package.json b/packages/example-component/package.json index cb918d9219ba0..573a843754482 100644 --- a/packages/example-component/package.json +++ b/packages/example-component/package.json @@ -21,8 +21,8 @@ "@types/react-addons-test-utils": "^0.14.18", "@types/react-dom": "^15.5.0", "@types/webpack-env": "^1.13.0", - "@uifabric/example-app-base": "3.0.0", - "@uifabric/utilities": "4.1.2", + "@uifabric/example-app-base": "3.0.1", + "@uifabric/utilities": "4.2.0", "autoprefixer": "^6.7.6", "chai": "^3.5.0", "css-loader": "^0.28.0", @@ -49,7 +49,7 @@ "react-dom": "^0.14 || ^15.4.2 || ^16.0.0-0" }, "dependencies": { - "office-ui-fabric-react": ">=4.4.2 <5.0.0", + "office-ui-fabric-react": ">=4.5.0 <5.0.0", "tslib": "^1.6.0" } } \ No newline at end of file diff --git a/packages/office-ui-fabric-react-tslint/tslint.json b/packages/office-ui-fabric-react-tslint/tslint.json index 495fcbff25dbd..1ce8cc1f7b45d 100644 --- a/packages/office-ui-fabric-react-tslint/tslint.json +++ b/packages/office-ui-fabric-react-tslint/tslint.json @@ -20,6 +20,7 @@ ], "prefer-const": false, "quotemark": [ + true, "single" ], "no-inferrable-types": [ diff --git a/packages/office-ui-fabric-react/CHANGELOG.json b/packages/office-ui-fabric-react/CHANGELOG.json index 9254db031de53..0d375df8d87dc 100644 --- a/packages/office-ui-fabric-react/CHANGELOG.json +++ b/packages/office-ui-fabric-react/CHANGELOG.json @@ -1,6 +1,48 @@ { "name": "office-ui-fabric-react", "entries": [ + { + "version": "4.5.0", + "tag": "office-ui-fabric-react_v4.5.0", + "date": "Mon, 12 Jun 2017 01:47:18 GMT", + "comments": { + "patch": [ + { + "author": "David Zearing ", + "commit": "4629fbd8565a845375b025a15bda92f02792c4fd", + "comment": "Updating minified bundle to exclude debug warnings." + }, + { + "author": "Christian Gonzalez ", + "commit": "de7c49d2bd5e85af8d7ae0aee97eb9afd297966d", + "comment": "Enable no implicit any in utilities package" + }, + { + "author": "Michael Loughry ", + "commit": "b2026ec598638fc1a6f179326cdaca230b699882", + "comment": "Toggle: add 'cursor: pointer'" + } + ], + "minor": [ + { + "author": "Michael Loughry ", + "commit": "9273411491a3c88f5a55b8b979d708d6cd8bf59c", + "comment": "Component properties now extend React.HTMLAttributes, rather than React.HTMLProps" + } + ], + "dependency": [ + { + "comment": "Updating dependency \"@uifabric/utilities\" from `>=4.1.2 <5.0.0` to `>=4.1.2 <5.0.0`" + }, + { + "comment": "Updating dependency \"@uifabric/styling\" from `>=0.7.2 <1.0.0` to `>=0.7.3 <1.0.0`" + }, + { + "comment": "Updating dependency \"@uifabric/example-app-base\" from `3.0.0` to `3.0.1`" + } + ] + } + }, { "version": "4.4.2", "tag": "office-ui-fabric-react_v4.4.2", diff --git a/packages/office-ui-fabric-react/CHANGELOG.md b/packages/office-ui-fabric-react/CHANGELOG.md index 702e373faf416..c90548dc78575 100644 --- a/packages/office-ui-fabric-react/CHANGELOG.md +++ b/packages/office-ui-fabric-react/CHANGELOG.md @@ -1,6 +1,19 @@ # Change Log - office-ui-fabric-react -This log was last generated on Thu, 08 Jun 2017 10:20:07 GMT and should not be manually modified. +This log was last generated on Mon, 12 Jun 2017 01:47:18 GMT and should not be manually modified. + +## 4.5.0 +Mon, 12 Jun 2017 01:47:18 GMT + +### Minor changes + +- Component properties now extend React.HTMLAttributes, rather than React.HTMLProps + +### Patches + +- Updating minified bundle to exclude debug warnings. +- Enable no implicit any in utilities package +- Toggle: add 'cursor: pointer' ## 4.4.2 Thu, 08 Jun 2017 10:20:07 GMT diff --git a/packages/office-ui-fabric-react/package.json b/packages/office-ui-fabric-react/package.json index 71b138f970a2e..da8ddd3719fbb 100644 --- a/packages/office-ui-fabric-react/package.json +++ b/packages/office-ui-fabric-react/package.json @@ -1,6 +1,6 @@ { "name": "office-ui-fabric-react", - "version": "4.4.2", + "version": "4.5.0", "description": "Reusable React components for building experiences for Office 365.", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -29,7 +29,7 @@ "@types/resemblejs": "~1.3.28", "@types/sinon": "^2.2.2", "@types/webpack-env": "^1.13.0", - "@uifabric/example-app-base": "3.0.0", + "@uifabric/example-app-base": "3.0.1", "autoprefixer": "^6.7.6", "chai": "^3.5.0", "css-loader": "^0.28.0", @@ -72,7 +72,7 @@ "dependencies": { "@microsoft/load-themed-styles": "^1.2.2", "@uifabric/utilities": ">=4.1.2 <5.0.0", - "@uifabric/styling": ">=0.7.1 <1.0.0", + "@uifabric/styling": ">=0.7.3 <1.0.0", "prop-types": "^15.5.10", "tslib": "^1.6.0" }, diff --git a/packages/office-ui-fabric-react/src/utilities/decorators/withResponsiveMode.tsx b/packages/office-ui-fabric-react/src/utilities/decorators/withResponsiveMode.tsx index 85c0318046908..3680f2b278c72 100644 --- a/packages/office-ui-fabric-react/src/utilities/decorators/withResponsiveMode.tsx +++ b/packages/office-ui-fabric-react/src/utilities/decorators/withResponsiveMode.tsx @@ -75,8 +75,13 @@ export function withResponsiveMode

RESPONSIVE_MAX_CONSTRAINT[responsiveMode]) { - responsiveMode++; + try { + while (win.innerWidth > RESPONSIVE_MAX_CONSTRAINT[responsiveMode]) { + responsiveMode++; + } + } catch (e) { + // Return a best effort result in cases where we're in the browser but it throws on getting innerWidth. + responsiveMode = ResponsiveMode.large; } } else { if (_defaultMode !== undefined) { diff --git a/packages/office-ui-fabric-react/webpack.lib.config.js b/packages/office-ui-fabric-react/webpack.lib.config.js index dd4627e887685..8da3d58b8f788 100644 --- a/packages/office-ui-fabric-react/webpack.lib.config.js +++ b/packages/office-ui-fabric-react/webpack.lib.config.js @@ -60,12 +60,16 @@ function createConfig(isProduction) { }; if (isProduction) { - webpackConfig.plugins.push(new webpack.optimize.UglifyJsPlugin({ - minimize: true, - compress: { - warnings: false - } - })); + webpackConfig.plugins.push( + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': JSON.stringify('production') + }), + new webpack.optimize.UglifyJsPlugin({ + minimize: true, + compress: { + warnings: false + } + })); } return webpackConfig; diff --git a/packages/styling/CHANGELOG.json b/packages/styling/CHANGELOG.json index c756715a37118..19e1528a1e160 100644 --- a/packages/styling/CHANGELOG.json +++ b/packages/styling/CHANGELOG.json @@ -1,6 +1,25 @@ { "name": "@uifabric/styling", "entries": [ + { + "version": "0.7.3", + "tag": "@uifabric/styling_v0.7.3", + "date": "Mon, 12 Jun 2017 01:47:18 GMT", + "comments": { + "patch": [ + { + "author": "Christian Gonzalez ", + "commit": "de7c49d2bd5e85af8d7ae0aee97eb9afd297966d", + "comment": "Enable no implicit any in utilities package" + } + ], + "dependency": [ + { + "comment": "Updating dependency \"@uifabric/utilities\" from `>=4.1.2 <5.0.0` to `>=4.1.2 <5.0.0`" + } + ] + } + }, { "version": "0.7.1", "tag": "@uifabric/styling_v0.7.1", diff --git a/packages/styling/CHANGELOG.md b/packages/styling/CHANGELOG.md index 1f68f2a20e75e..7184d546076d4 100644 --- a/packages/styling/CHANGELOG.md +++ b/packages/styling/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @uifabric/styling -This log was last generated on Thu, 08 Jun 2017 10:20:07 GMT and should not be manually modified. +This log was last generated on Mon, 12 Jun 2017 01:47:18 GMT and should not be manually modified. + +## 0.7.3 +Mon, 12 Jun 2017 01:47:18 GMT + +### Patches + +- Enable no implicit any in utilities package ## 0.7.1 Thu, 08 Jun 2017 10:20:07 GMT diff --git a/packages/styling/package.json b/packages/styling/package.json index c92c38f6814b6..cdd29e6dd2826 100644 --- a/packages/styling/package.json +++ b/packages/styling/package.json @@ -1,6 +1,6 @@ { "name": "@uifabric/styling", - "version": "0.7.1", + "version": "0.7.3", "description": "Defines the core Office UI Fabric styles through a JavaScript interface.", "main": "lib/index.js", "typings": "lib/index.d.ts", diff --git a/packages/utilities/CHANGELOG.json b/packages/utilities/CHANGELOG.json index b826b10ec5427..6048be86463b8 100644 --- a/packages/utilities/CHANGELOG.json +++ b/packages/utilities/CHANGELOG.json @@ -1,6 +1,20 @@ { "name": "@uifabric/utilities", "entries": [ + { + "version": "4.2.0", + "tag": "@uifabric/utilities_v4.2.0", + "date": "Mon, 12 Jun 2017 01:47:18 GMT", + "comments": { + "minor": [ + { + "author": "Christian Gonzalez ", + "commit": "de7c49d2bd5e85af8d7ae0aee97eb9afd297966d", + "comment": "Enable no implicit any in utilities package" + } + ] + } + }, { "version": "4.1.2", "tag": "@uifabric/utilities_v4.1.2", diff --git a/packages/utilities/CHANGELOG.md b/packages/utilities/CHANGELOG.md index e9d85faf0022f..bf4b0cb3cca75 100644 --- a/packages/utilities/CHANGELOG.md +++ b/packages/utilities/CHANGELOG.md @@ -1,6 +1,13 @@ # Change Log - @uifabric/utilities -This log was last generated on Thu, 08 Jun 2017 00:18:05 GMT and should not be manually modified. +This log was last generated on Mon, 12 Jun 2017 01:47:18 GMT and should not be manually modified. + +## 4.2.0 +Mon, 12 Jun 2017 01:47:18 GMT + +### Minor changes + +- Enable no implicit any in utilities package ## 4.1.2 Thu, 08 Jun 2017 00:18:05 GMT diff --git a/packages/utilities/api/utilities.api.ts b/packages/utilities/api/utilities.api.ts index 7780172e49b96..9565f97f7ff08 100644 --- a/packages/utilities/api/utilities.api.ts +++ b/packages/utilities/api/utilities.api.ts @@ -1,3 +1,5 @@ +export function assign(target: any, ...args: any[]): any; + // WARNING: dispose has incomplete type information // WARNING: clearImmediate has incomplete type information // WARNING: clearInterval has incomplete type information @@ -50,10 +52,11 @@ class BaseComponent

extends React.Component { // WARNING: contextTypes has incomplete type information // WARNING: childContextTypes has incomplete type information -// WARNING: __constructor has incomplete type information // WARNING: componentWillReceiveProps has incomplete type information // WARNING: render has incomplete type information class Customizer extends BaseComponent { + // (undocumented) + constructor(props: any, context: any); public static addChangeListener(onChanged: IChangeListener): void; // (undocumented) public getChildContext(): any; @@ -150,6 +153,8 @@ export function hasOverflow(element: HTMLElement): boolean; export function hasVerticalOverflow(element: HTMLElement): boolean; +export function hoistMethods(destination: any, source: any, exclusions: string[] = REACT_LIFECYCLE_EXCLUSIONS): string[]; + // (undocumented) interface IBaseProps { // (undocumented) @@ -374,10 +379,8 @@ export function warnMutuallyExclusive < P >(componentName: string, // WARNING: getWindow has incomplete type information // WARNING: getDocument has incomplete type information // WARNING: doesElementContainFocus has incomplete type information -// WARNING: hoistMethods has incomplete type information // WARNING: setLanguage has incomplete type information // WARNING: shallowCompare has incomplete type information -// WARNING: assign has incomplete type information // WARNING: filteredAssign has incomplete type information // WARNING: Unsupported export: baseElementEvents // WARNING: Unsupported export: baseElementProperties diff --git a/packages/utilities/package.json b/packages/utilities/package.json index aa1ff5d323cbc..051da41bc9e3a 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -1,6 +1,6 @@ { "name": "@uifabric/utilities", - "version": "4.1.2", + "version": "4.2.0", "description": "Office UI Fabric utilities for building React components.", "main": "lib/index.js", "typings": "lib/index.d.ts", @@ -20,6 +20,7 @@ "@types/chai": "^3.4.35", "@types/enzyme": "^2.7.5", "@types/es6-promise": "^0.0.32", + "@types/es6-weak-map": "1.2.0", "@types/mocha": "^2.2.39", "@types/prop-types": "^15.5.1", "@types/react": "^15.0.25", diff --git a/packages/utilities/src/Async.ts b/packages/utilities/src/Async.ts index 16701ed41a85a..d9d065a181a9b 100644 --- a/packages/utilities/src/Async.ts +++ b/packages/utilities/src/Async.ts @@ -10,9 +10,9 @@ declare function setTimeout(cb: Function, delay: number): number; declare function setInterval(cb: Function, delay: number): number; export class Async { - private _timeoutIds = null; - private _immediateIds = null; - private _intervalIds = null; + private _timeoutIds: any = null; + private _immediateIds: any = null; + private _intervalIds: any = null; private _animationFrameIds: { [id: number]: boolean } = null; private _isDisposed = false; private _parent: any; @@ -38,7 +38,7 @@ export class Async { if (this._timeoutIds) { for (id in this._timeoutIds) { if (this._timeoutIds.hasOwnProperty(id)) { - this.clearTimeout(id); + this.clearTimeout(parseInt(id, 10)); } } @@ -49,7 +49,7 @@ export class Async { if (this._immediateIds) { for (id in this._immediateIds) { if (this._immediateIds.hasOwnProperty(id)) { - this.clearImmediate(id); + this.clearImmediate(parseInt(id, 10)); } } @@ -60,7 +60,7 @@ export class Async { if (this._intervalIds) { for (id in this._intervalIds) { if (this._intervalIds.hasOwnProperty(id)) { - this.clearInterval(id); + this.clearInterval(parseInt(id, 10)); } } this._intervalIds = null; @@ -70,7 +70,7 @@ export class Async { if (this._animationFrameIds) { for (id in this._animationFrameIds) { if (this._animationFrameIds.hasOwnProperty(id)) { - this.cancelAnimationFrame(id); + this.cancelAnimationFrame(parseInt(id, 10)); } } @@ -254,7 +254,7 @@ export class Async { let leading = true; let trailing = true; let lastExecuteTime = 0; - let lastResult; + let lastResult: any; let lastArgs: any[]; let timeoutId: number = null; @@ -320,10 +320,10 @@ export class Async { let waitMS = wait || 0; let leading = false; let trailing = true; - let maxWait = null; + let maxWait: any = null; let lastCallTime = 0; let lastExecuteTime = (new Date).getTime(); - let lastResult; + let lastResult: any; let lastArgs: any[]; let timeoutId: number = null; diff --git a/packages/utilities/src/BaseComponent.test.tsx b/packages/utilities/src/BaseComponent.test.tsx index a2f13026430c2..3301e49b86e00 100644 --- a/packages/utilities/src/BaseComponent.test.tsx +++ b/packages/utilities/src/BaseComponent.test.tsx @@ -85,7 +85,7 @@ describe('BaseComponent', () => { }); }); -function _buildTestFor(methodName) { +function _buildTestFor(methodName: string) { it(`calls the error logger on ${methodName} exception`, () => { let lastErrorMessage = null; @@ -93,7 +93,7 @@ function _buildTestFor(methodName) { let c = new TestComponent(); - c[methodName](); + (c as any)[methodName](); assert(lastErrorMessage !== null, 'Error callback not called'); }); diff --git a/packages/utilities/src/BaseComponent.ts b/packages/utilities/src/BaseComponent.ts index f9a70fdd0f64f..6054348f953ee 100644 --- a/packages/utilities/src/BaseComponent.ts +++ b/packages/utilities/src/BaseComponent.ts @@ -146,7 +146,7 @@ export class BaseComponent

extends React.Component { - return this[refName] = ref; + return (this as any)[refName] = ref; }; } @@ -202,11 +202,11 @@ function _makeAllSafe(obj: BaseComponent, prototype: Object, methodNam } function _makeSafe(obj: BaseComponent, prototype: Object, methodName: string) { - let classMethod = obj[methodName]; - let prototypeMethod = prototype[methodName]; + let classMethod = (obj as any)[methodName]; + let prototypeMethod = (prototype as any)[methodName]; if (classMethod || prototypeMethod) { - obj[methodName] = function () { + (obj as any)[methodName] = function () { let retVal; try { diff --git a/packages/utilities/src/Customizer.tsx b/packages/utilities/src/Customizer.tsx index d7b576f3bb161..e778b3feaa845 100644 --- a/packages/utilities/src/Customizer.tsx +++ b/packages/utilities/src/Customizer.tsx @@ -77,7 +77,7 @@ export class Customizer extends BaseComponent(target: any, key: string, descripto return fnBound; }, - set(newValue) { + set(newValue: any) { Object.defineProperty(this, key, { configurable: true, writable: true, diff --git a/packages/utilities/src/css.ts b/packages/utilities/src/css.ts index 006cf4fddebfe..e16c39e8d1e07 100644 --- a/packages/utilities/src/css.ts +++ b/packages/utilities/src/css.ts @@ -20,7 +20,7 @@ export function css(...args: ICssInput[]) { classes.push(arg.toString()); } else { for (let key in arg as any) { - if (arg[key]) { + if ((arg as any)[key]) { classes.push(key); } } diff --git a/packages/utilities/src/customizable.test.tsx b/packages/utilities/src/customizable.test.tsx index 41454c8afed01..8ef656a1e5a5c 100644 --- a/packages/utilities/src/customizable.test.tsx +++ b/packages/utilities/src/customizable.test.tsx @@ -10,7 +10,7 @@ class Foo extends React.Component<{ field: string; }, {}> { public name: any; public render() { - return

{ this.props[this.props.field] }
; + return
{ (this.props as any)[this.props.field] }
; } } diff --git a/packages/utilities/src/customizable.tsx b/packages/utilities/src/customizable.tsx index 6b5400a9a6e4c..a590468340c1a 100644 --- a/packages/utilities/src/customizable.tsx +++ b/packages/utilities/src/customizable.tsx @@ -29,7 +29,7 @@ export function customizable

(fields: string[]) { let defaultProps = {}; for (let propName of fields) { - defaultProps[propName] = (this.context.injectedProps) ? + (defaultProps as any)[propName] = (this.context.injectedProps) ? this.context.injectedProps[propName] : Customizer.getDefault(propName); } diff --git a/packages/utilities/src/dom.ts b/packages/utilities/src/dom.ts index b114db5c24f72..1f3ac4740abe6 100644 --- a/packages/utilities/src/dom.ts +++ b/packages/utilities/src/dom.ts @@ -106,7 +106,7 @@ let _isSSR = false; /** * Helper to set ssr mode to simulate no window object returned from getWindow helper. */ -export function setSSR(isEnabled) { +export function setSSR(isEnabled: boolean) { _isSSR = isEnabled; } diff --git a/packages/utilities/src/focus.test.tsx b/packages/utilities/src/focus.test.tsx index d04874f42b405..3d60e27f07190 100644 --- a/packages/utilities/src/focus.test.tsx +++ b/packages/utilities/src/focus.test.tsx @@ -8,9 +8,9 @@ let { expect } = chai; import { isElementVisible, isElementTabbable } from './focus'; -let _hiddenElement; -let _visibleElement; -let _element; +let _hiddenElement: HTMLElement | undefined; +let _visibleElement: HTMLElement | undefined; +let _element: HTMLElement | undefined; function renderIntoDocument(element: React.ReactElement): HTMLElement { const component = ReactTestUtils.renderIntoDocument(element); diff --git a/packages/utilities/src/hoist.ts b/packages/utilities/src/hoist.ts index bdd468fc55e94..9724aee142783 100644 --- a/packages/utilities/src/hoist.ts +++ b/packages/utilities/src/hoist.ts @@ -18,7 +18,7 @@ const REACT_LIFECYCLE_EXCLUSIONS = [ * @param exclusions - (Optional) What methods to exclude from being hoisted. * @returns An array of names of methods that were hoisted. */ -export function hoistMethods(destination, source, exclusions: string[] = REACT_LIFECYCLE_EXCLUSIONS): string[] { +export function hoistMethods(destination: any, source: any, exclusions: string[] = REACT_LIFECYCLE_EXCLUSIONS): string[] { let hoisted: string[] = []; for (let methodName in source) { if ( diff --git a/packages/utilities/src/memoize.test.ts b/packages/utilities/src/memoize.test.ts index 7b1fe5e8518a9..b100ae39bf526 100644 --- a/packages/utilities/src/memoize.test.ts +++ b/packages/utilities/src/memoize.test.ts @@ -1,10 +1,10 @@ import { memoize, memoizeFunction, setMemoizeWeakMap } from './memoize'; -import * as weakMapPolyfill from 'es6-weak-map/polyfill'; +import weakMapPolyfill = require('es6-weak-map'); let { expect } = chai; describe('memoizeFunction', () => { - before(()=> { + before(() => { setMemoizeWeakMap(weakMapPolyfill); }); @@ -95,7 +95,7 @@ describe('memoizeFunction', () => { }); describe('memoize', () => { - before(()=> { + before(() => { setMemoizeWeakMap(weakMapPolyfill); }); diff --git a/packages/utilities/src/memoize.ts b/packages/utilities/src/memoize.ts index 3205cdbb69066..8353f85cccc24 100644 --- a/packages/utilities/src/memoize.ts +++ b/packages/utilities/src/memoize.ts @@ -1,11 +1,11 @@ declare class WeakMap { public get(key: any): any; public set(key: any, value: any): void; - public has(key: any); + public has(key: any): boolean; } const _emptyObject = { empty: true }; -const _dictionary = {}; +const _dictionary: any = {}; let _weakMap = (typeof WeakMap === 'undefined') ? null : WeakMap; interface IMemoizeNode { diff --git a/packages/utilities/src/object.ts b/packages/utilities/src/object.ts index 400d75ee2767f..339700c34969f 100644 --- a/packages/utilities/src/object.ts +++ b/packages/utilities/src/object.ts @@ -9,7 +9,7 @@ if (_global[CURRENT_ID_PROPERTY] === undefined) { _global[CURRENT_ID_PROPERTY] = 0; } -function checkProperties(a, b) { +function checkProperties(a: any, b: any) { for (let propName in a) { if (a.hasOwnProperty(propName)) { if (!b.hasOwnProperty(propName) || (b[propName] !== a[propName])) { @@ -22,7 +22,7 @@ function checkProperties(a, b) { } // Compare a to b and b to a -export function shallowCompare(a, b) { +export function shallowCompare(a: any, b: any) { return checkProperties(a, b) && checkProperties(b, a); } @@ -35,7 +35,7 @@ export function shallowCompare(a, b) { * @param args - One or more objects that will be mixed into the target in the order they are provided. * @returns Resulting merged target. */ -export function assign(target: any, ...args): any { +export function assign(target: any, ...args: any[]): any { return filteredAssign.apply(this, [null, target].concat(args)); } @@ -50,7 +50,7 @@ export function assign(target: any, ...args): any { * @param args - One or more objects that will be mixed into the target in the order they are provided. * @returns Resulting merged target. */ -export function filteredAssign(isAllowed: (propName: string) => boolean, target: any, ...args) { +export function filteredAssign(isAllowed: (propName: string) => boolean, target: any, ...args: any[]) { target = target || {}; for (let sourceObject of args) { diff --git a/packages/utilities/src/properties.test.ts b/packages/utilities/src/properties.test.ts index d3aa465be5b5b..b3b8621cdceed 100644 --- a/packages/utilities/src/properties.test.ts +++ b/packages/utilities/src/properties.test.ts @@ -11,14 +11,14 @@ describe('getNativeProps', () => { let result = getNativeProps>({ 'data-automation-id': 1 }, divProperties); - expect(result['data-automation-id']).equals(1); + expect((result as any)['data-automation-id']).equals(1); }); it('can pass through aria tags', () => { let result = getNativeProps>({ 'aria-label': 1 }, divProperties); - expect(result['aria-label']).equals(1); + expect((result as any)['aria-label']).equals(1); }); it('can pass through basic div properties and events', () => { @@ -29,7 +29,8 @@ describe('getNativeProps', () => { }, divProperties); expect(result.className).equals('foo'); expect(result.onClick).is.instanceof(Function, 'onClick not function'); - expect(result['onClickCapture']).is.instanceof(Function, 'onClickCapture not function'); // tslint:disable-line:no-string-literal + // tslint:disable-next-line:no-string-literal + expect((result as any)['onClickCapture']).is.instanceof(Function, 'onClickCapture not function'); }); it('can remove unexpected properties', () => { @@ -38,11 +39,11 @@ describe('getNativeProps', () => { className: 'hi' }, divProperties); expect(result.className).equals('hi'); - expect(result['foobar']).equals(undefined); // tslint:disable-line:no-string-literal + expect((result as any)['foobar']).equals(undefined); // tslint:disable-line:no-string-literal }); it('can exclude properties', () => { - let result = getNativeProps({ a: 1, b: 2}, ['a', 'b'], ['b']); + let result = getNativeProps({ a: 1, b: 2 }, ['a', 'b'], ['b']); expect(result.a).to.exist; expect(result.b).to.not.exist; diff --git a/packages/utilities/src/resources.ts b/packages/utilities/src/resources.ts index 7db0806d03cc9..f1aedeee24a45 100644 --- a/packages/utilities/src/resources.ts +++ b/packages/utilities/src/resources.ts @@ -1,11 +1,11 @@ let _baseUrl = ''; /** Sets the current base url used for fetching images. */ -export function getResourceUrl(url) { +export function getResourceUrl(url: string) { return _baseUrl + url; } /** Gets the current base url used for fetching images. */ -export function setBaseUrl(baseUrl) { +export function setBaseUrl(baseUrl: string) { _baseUrl = baseUrl; } diff --git a/packages/utilities/src/string.ts b/packages/utilities/src/string.ts index ca0b126d8a8f5..0de594d55bdfc 100644 --- a/packages/utilities/src/string.ts +++ b/packages/utilities/src/string.ts @@ -11,20 +11,20 @@ const FORMAT_REGEX = /\{\d+\}/g; * Example "I love {0} every {1}".format("CXP") will result in a Debug Exception. */ export function format(s: string, ...values: any[]): string { - 'use strict'; + 'use strict'; - let args = values; - // Callback match function - function replace_func(match: string) { - // looks up in the args - let replacement = args[match.replace(FORMAT_ARGS_REGEX, '')]; + let args = values; + // Callback match function + function replace_func(match: string) { + // looks up in the args + let replacement = args[match.replace(FORMAT_ARGS_REGEX, '') as any]; - // catches undefined in nondebug and null in debug and nondebug - if (replacement === null || replacement === undefined) { - replacement = ''; - } - - return replacement; + // catches undefined in nondebug and null in debug and nondebug + if (replacement === null || replacement === undefined) { + replacement = ''; } - return (s.replace(FORMAT_REGEX, replace_func)); + + return replacement; + } + return (s.replace(FORMAT_REGEX, replace_func)); } \ No newline at end of file diff --git a/packages/utilities/src/test/injectWrapperMethod.ts b/packages/utilities/src/test/injectWrapperMethod.ts index 72fb2118276b2..b683b2cf1445e 100644 --- a/packages/utilities/src/test/injectWrapperMethod.ts +++ b/packages/utilities/src/test/injectWrapperMethod.ts @@ -7,13 +7,13 @@ import { ReactWrapper } from 'enzyme'; * @param fn - The function to run prior to the call of the original method */ export function injectWrapperMethod(wrapper: ReactWrapper, methodName: string, fn: () => void) { - const originalMethod = wrapper.instance()[methodName]; + const originalMethod = (wrapper.instance() as any)[methodName]; if (typeof originalMethod !== 'function') { throw new Error(`Tried to override the method ${methodName} on a ReactWrapper that does not have that function`); } - wrapper.instance()[methodName] = function (prevProps: any) { + (wrapper.instance() as any)[methodName] = function (prevProps: any) { fn(); originalMethod.call(this, prevProps); }; diff --git a/packages/utilities/tsconfig.json b/packages/utilities/tsconfig.json index df37502ab300d..c86ca82c9d7db 100644 --- a/packages/utilities/tsconfig.json +++ b/packages/utilities/tsconfig.json @@ -11,6 +11,7 @@ "sourceMap": true, "experimentalDecorators": true, "importHelpers": true, + "noImplicitAny": true, "moduleResolution": "node", "types": [ "chai", diff --git a/rush.json b/rush.json index 6a2859648d2d5..82d8eb5ad17b6 100644 --- a/rush.json +++ b/rush.json @@ -63,4 +63,4 @@ "shouldPublish": false } ] -} +} \ No newline at end of file From 8a0a24c8452572c3ab3ee3ae8774f1ddf2b48afe Mon Sep 17 00:00:00 2001 From: Boris Emorine Date: Thu, 8 Jun 2017 15:56:17 -0700 Subject: [PATCH 11/31] New Component: Stepper (#1759) * First step at stepper implementation. * Add first implementation of stepper. * Add functionality to stepper * Refine the Stepper class and add tests * let's make sure to put focus back on the text field when submitting via enter * Added documentation to Stepper. * Add flexibility to current stepper implementation. * Modified example implementations. * Add aria-valuemax. * Change Stepper to SpinButton. * Add example with unit. * Implement color scheme in the ContextualMenu control to enable alternative theming. * Improvements to SpinButton. * Fix increment function calls. * Add new width optional parameter. * Add label direction. * Fix border. * Add Position enum. * `defaultValue` is now the deciding prop for using the default implementation or not. * onBlur is now onValidate. * Fix tests. * Fix warnings. * Add implementation for labelGap. * Put some polish on the styling, added some icon support, and added some more example spinButtons * Implement the bar and unit tests and component page * Add the ability for the spinButton buttons to look pressed when spinning via keyboard * Revert "Implement color scheme in the ContextualMenu control to enable alternative theming." This reverts commit 4f830cd8c55269368e73bfc6d0cd69430776b977. * Don't render an empty icon for an icon-less header menu item. * Revert "Implement Document Title Bar" * update some CSS for high contrast in ff and use css utility instead of concatenating string classnames * Fix quotation issue * Fix Spin Button properties table. * Fix Spin Button example code * Use iconProps instead of string * Extracted `spinning` out of state * Add autobind instead of manually binding private functions to this * Change `+` syntax for more explicit `Number()` * Remove unnecessary cast * Fix typos * `incrementButtonIcon` and `decrementButtonIcon` are now IIconProps * Add KeyboardSpinDirection enum * Fix test description * Fix SpinButton tests * Remove unused onChange callback from SpinButton * Revert onChange * Remove old Stepper.ts file * Use module css instead of global * Fix missing word in comment * Callback functions now allow for void return (state to be updated outside) * Use `_async` instead of window * Fix minor rendering issue with browser zoom * Rename `_spinning` to `_spinningByMouse` for clarity * Fix tests * Fix extra space before label * Remove width outside of SpinButton component and fix styling * Add more tests to SpinButton * Fix SpinButton documentation * Fix typo * Fix AppDefinition for SpinButton and Spinner * Add missing documentation to SpinButton title prop * Various SpinButton fixes * Fix SpinButton path for properties * Fix SpinButton styling issues * Remove labelGap property from SpinButton --- .../Components/SpinButtonComponentPage.tsx | 37 ++ .../office-ui-fabric-react/src/SpinButton.ts | 1 + .../components/SpinButton/SpinButton.Props.ts | 107 ++++ .../src/components/SpinButton/SpinButton.scss | 226 +++++++ .../components/SpinButton/SpinButton.test.tsx | 554 ++++++++++++++++++ .../src/components/SpinButton/SpinButton.tsx | 428 ++++++++++++++ .../components/SpinButton/SpinButtonPage.tsx | 97 +++ .../examples/SpinButton.Basic.Example.tsx | 18 + .../SpinButton.BasicDisabled.Example.tsx | 19 + ...pinButton.BasicWithEndPosition.Example.tsx | 21 + .../SpinButton.BasicWithIcon.Example.tsx | 19 + .../examples/SpinButton.Stateful.Example.tsx | 46 ++ .../src/components/SpinButton/index.ts | 2 + .../src/demo/AppDefinition.tsx | 6 + packages/office-ui-fabric-react/src/index.ts | 1 + .../src/utilities/positioning.ts | 7 + 16 files changed, 1589 insertions(+) create mode 100644 apps/fabric-website/src/pages/Components/SpinButtonComponentPage.tsx create mode 100644 packages/office-ui-fabric-react/src/SpinButton.ts create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.Props.ts create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.scss create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.test.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/SpinButtonPage.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Basic.Example.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicDisabled.Example.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithEndPosition.Example.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithIcon.Example.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Stateful.Example.tsx create mode 100644 packages/office-ui-fabric-react/src/components/SpinButton/index.ts diff --git a/apps/fabric-website/src/pages/Components/SpinButtonComponentPage.tsx b/apps/fabric-website/src/pages/Components/SpinButtonComponentPage.tsx new file mode 100644 index 0000000000000..9da1685a03e5f --- /dev/null +++ b/apps/fabric-website/src/pages/Components/SpinButtonComponentPage.tsx @@ -0,0 +1,37 @@ +import * as React from 'react'; +import { SpinButtonPage } from 'office-ui-fabric-react/lib/components/SpinButton/SpinButtonPage'; +import { PageHeader } from '../../components/PageHeader/PageHeader'; +import { ComponentPage } from '../../components/ComponentPage/ComponentPage'; + +export class SpinButtonComponentPage extends React.Component { + public render() { + return ( +

+ + + + +
+ ); + } +} diff --git a/packages/office-ui-fabric-react/src/SpinButton.ts b/packages/office-ui-fabric-react/src/SpinButton.ts new file mode 100644 index 0000000000000..25568549f09b5 --- /dev/null +++ b/packages/office-ui-fabric-react/src/SpinButton.ts @@ -0,0 +1 @@ +export * from './components/SpinButton/index'; diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.Props.ts b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.Props.ts new file mode 100644 index 0000000000000..ac8a3233a54d5 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.Props.ts @@ -0,0 +1,107 @@ +import { Position } from '../../utilities/positioning'; +import { IIconProps } from '../../Icon'; + +export interface ISpinButtonProps { + + /** + * The initial value of the SpinButton. Use this if you intend for the SpinButton to be an uncontrolled component. + * This value is mutually exclusive to value. Use one or the other. + * @default 0 + */ + defaultValue?: string; + + /** + * The value of the SpinButton. Use this if you intend to pass in a new value as a result of onChange events. + * This value is mutually exclusive to defaultValue. Use one or the other. + */ + value?: string; + + /** + * The min value of the SpinButton. + * @default 0 + */ + min?: number; + + /** + * The max value of the SpinButton. + * @default 10 + */ + max?: number; + + /** + * The difference between the two adjacent values of the SpinButton. + * @default 1 + */ + step?: number; + + /** + * A description of the SpinButton for the benefit of screen readers. + */ + ariaLabel?: string; + + /** + * A title for the SpinButton used for a more descriptive name that's also visible on its tooltip. + */ + title?: string; + + /** + * Whether or not the SpinButton is disabled. + */ + disabled?: boolean; + + /** + * Optional className for SpinButton. + */ + className?: string; + + /** + * Descriptive label for the SpinButton. + */ + label: string; + + /** + * @default: Left + */ + labelPosition?: Position; + + /** + * Icon that goes along with the label for the whole SpinButton + */ + iconProps?: IIconProps; + + /** + * This callback is triggered when the value inside the SpinButton should be validated. + * @return {string | void} If a string is returned, it will be used as the value of the SpinButton. + */ + onValidate?: (value: string) => string | void; + + /** + * This callback is triggered when the increment button is pressed or if the user presses up arrow with focus on the input of the spinButton + * @return {string | void} If a string is returned, it will be used as the value of the SpinButton. + */ + onIncrement?: (value: string) => string | void; + + /** + * This callback is triggered when the decrement button is pressed or if the user presses down arrow with focus on the input of the spinButton + * @return {string | void} If a string is returned, it will be used as the value of the SpinButton. + */ + onDecrement?: (value: string) => string | void; + + /** + * Icon for the increment button of the spinButton + */ + incrementButtonIcon?: IIconProps; + + /** + * Icon for the decrement button of the spinButton + */ + decrementButtonIcon?: IIconProps; +} + +export interface ISpinButton { + /** + * The value of the SpinButton. Use this if you intend to pass in a new value as a result of onChange events. + * This value is mutually exclusive to defaultValue. Use one or the other. + */ + value?: string; +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.scss b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.scss new file mode 100644 index 0000000000000..90ea34b50ff0e --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.scss @@ -0,0 +1,226 @@ +@import '../../common/common'; +@import '../Label/LabelMixins.scss'; + +.ArrowBox, .Input, +.UpButton, .DownButton, .SpinButtonContainer { + outline: none; + font-size: 12px; +} + +.SpinButtonLabel { + pointer-events: none; + padding: 2px 0px; +} + +.SpinButtonContainer { + width: 100%; + min-width: 86px; + padding: 2px; +} + + .ArrowBox { + display: block; + float: left; + height: 100%; + border: 1px solid $ms-color-neutralTertiaryAlt; + border-left-width: 0px; + cursor: default; + padding: 0px; + box-sizing: border-box; + } + + .SpinButtonWrapper:hover .ArrowBox, .SpinButtonWrapper:hover .Input { + border-color: $ms-color-neutralSecondaryAlt; + outline: 2px dashed transparent; + + @media screen and (-ms-high-contrast: active) { + border-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border-color: $ms-color-contrastWhiteSelected; + } + } + + .labelWrapper { + display: inline-flex; + + &.start { + float: left; + margin-right: 10px; + } + + &.end { + float: right; + margin-left: 10px; + } + + &.top { + margin-bottom: 10px; + } + + &.bottom { + margin-top: 10px; + } + } + + .SpinButtonIcon { + padding: 2px 5px; + font-size: 20px; + } + + .SpinButtonWrapper { + display: flex; + height: 26px; + min-width: 86px; + } + + .SpinButtonWrapper.topBottom { + width: 100%; + } + + .Input:focus + .ArrowBox { + border-color: $ms-color-themePrimary; + outline: 2px dashed transparent; + + @media screen and (-ms-high-contrast: active) { + border-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border-color: $ms-color-contrastWhiteSelected; + } + } + + .Input.disabled + .ArrowBox { + background-color: $ms-color-neutralLighter; + border-color: $ms-color-neutralLighter; + pointer-events: none; + cursor: default; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackDisabled; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteDisabled; + } + } + + .SpinButtonWrapper:hover .Input:focus { + border-color: $ms-color-themePrimary; + } + + .Input { + @include ms-normalize; + border: 1px solid $ms-color-neutralTertiaryAlt; + border-radius: 0; + font-weight: $ms-font-weight-regular; + font-size: $ms-font-size-m; + color: $ms-color-neutralPrimary; + height: 100%; + @include padding(3px, 3px, 4px, 4px); + outline: 0; + text-overflow: ellipsis; + display: block; + float: left; + width: calc(100% - 14px); + min-width: 72px; + border-right-width: 0px; + overflow: hidden; + cursor: text; + user-select: text; + + &:focus { + border-color: $ms-color-themePrimary; + outline: 2px dashed transparent; + + @media screen and (-ms-high-contrast: active) { + border-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + border-color: $ms-color-contrastWhiteSelected; + } + } + + &::selection { + background-color: $ms-color-themePrimary; + color: $ms-color-white; + } + } + + .SpinButtonWrapper:hover .Input.disabled, + .Input.disabled { + background-color: $ms-color-neutralLighter; + border-color: $ms-color-neutralLighter; + pointer-events: none; + cursor: default; + color: $ms-color-neutralTertiaryAlt; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackDisabled; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteDisabled; + } + } + + .Input:hover .UpButton, .Input:hover .DownButton { + background-color: $ms-color-neutralLighter; + } + + .SpinButtonWrapper .UpButton, .SpinButtonWrapper .DownButton { + display: block; + height: 50%; + width: 12px; + padding: 0px; + background-color: transparent; + text-align: center; + cursor: default; + font-size: 6px; + color: $ms-color-neutralPrimary; + + &:hover { + background-color: $ms-color-neutralLight; + } + + &:active { + background-color: $ms-color-themePrimary; + color: $ms-color-white; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + } + } + } + + .UpButton:disabled, .DownButton:disabled { + opacity: 0; + + @media screen and (-ms-high-contrast: active) { + color: $ms-color-contrastBlackDisabled; + } + + @media screen and (-ms-high-contrast: black-on-white) { + color: $ms-color-contrastWhiteDisabled; + } + } + + .SpinButtonWrapper .UpButton.active, .SpinButtonWrapper .DownButton.active { + background-color: $ms-color-themePrimary; + color: $ms-color-white; + + @media screen and (-ms-high-contrast: active) { + background-color: $ms-color-contrastBlackSelected; + } + + @media screen and (-ms-high-contrast: black-on-white) { + background-color: $ms-color-contrastWhiteSelected; + } + } diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.test.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.test.tsx new file mode 100644 index 0000000000000..64d48a3cf5c17 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.test.tsx @@ -0,0 +1,554 @@ +import { Promise } from 'es6-promise'; +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import * as ReactTestUtils from 'react-addons-test-utils'; +import { SpinButton } from './SpinButton'; +import { KeyCodes } from '../../Utilities'; + +const expect: Chai.ExpectStatic = chai.expect; + +describe('SpinButton', () => { + function renderIntoDocument(element: React.ReactElement): HTMLElement { + const component = ReactTestUtils.renderIntoDocument(element); + const renderedDOM: Element = ReactDOM.findDOMNode(component as React.ReactInstance); + return renderedDOM as HTMLElement; + } + + function mockEvent(targetValue: string = ''): ReactTestUtils.SyntheticEventData { + const target: EventTarget = { value: targetValue } as HTMLInputElement; + const event: ReactTestUtils.SyntheticEventData = { target }; + return event; + } + + it('should render a spinner with the default value on the input element', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + const labelDOM: HTMLLabelElement = renderedDOM.getElementsByTagName('label')[0]; + + expect(inputDOM.value).to.equal(exampleDefaultValue); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleDefaultValue)); + expect(inputDOM.getAttribute('aria-labelledby')).to.equals(labelDOM.id); + + // Assert on the label element. + expect(labelDOM.textContent).to.equal(exampleLabelValue); + expect(labelDOM.htmlFor).to.equal(inputDOM.id); + }); + + it('should increment the value in the spin button via the up button', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + const buttonDOM: Element = renderedDOM.getElementsByClassName('ms-UpButton')[0]; + + expect(buttonDOM.tagName).to.equal('BUTTON'); + + ReactTestUtils.Simulate.mouseDown(buttonDOM, + { + type: 'mousedown', + clientX: 0, + clientY: 0 + }); + + ReactTestUtils.Simulate.mouseUp(buttonDOM, + { + type: 'mouseup', + clientX: 0, + clientY: 0 + }); + + expect(inputDOM.value).to.equal('13'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal('13'); + + }); + + it('should decrement the value in the spin button by the down button', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + const buttonDOM: Element = renderedDOM.getElementsByClassName('ms-DownButton')[0]; + + expect(buttonDOM.tagName).to.equal('BUTTON'); + + ReactTestUtils.Simulate.mouseDown(buttonDOM, + { + type: 'mousedown', + clientX: 0, + clientY: 0 + }); + + ReactTestUtils.Simulate.mouseUp(buttonDOM, + { + type: 'mouseup', + clientX: 0, + clientY: 0 + }); + + expect(inputDOM.value).to.equal('11'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal('11'); + + }); + + it('should increment the value in the spin button by the up arrow', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + + ReactTestUtils.Simulate.keyDown(inputDOM, + { + which: KeyCodes.up + }); + + ReactTestUtils.Simulate.keyUp(inputDOM, + { + which: KeyCodes.up + }); + + expect(inputDOM.value).to.equal('13'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal('13'); + + }); + + it('should decrement the value in the spin button by the down arrow', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + + ReactTestUtils.Simulate.keyDown(inputDOM, + { + which: KeyCodes.down + }); + + ReactTestUtils.Simulate.keyUp(inputDOM, + { + which: KeyCodes.down + }); + + expect(inputDOM.value).to.equal('11'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal('11'); + + }); + + it('should increment the value in the spin button by a step value of 2', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + + ReactTestUtils.Simulate.keyDown(inputDOM, + { + which: KeyCodes.up + }); + + ReactTestUtils.Simulate.keyUp(inputDOM, + { + which: KeyCodes.up + }); + + expect(inputDOM.value).to.equal('14'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal('14'); + + }); + + it('should decrement the value in the spin button by a step value of 2', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + + ReactTestUtils.Simulate.keyDown(inputDOM, + { + which: KeyCodes.down + }); + + ReactTestUtils.Simulate.keyUp(inputDOM, + { + which: KeyCodes.down + }); + + expect(inputDOM.value).to.equal('10'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal('10'); + + }); + + it('should set the value of the spin button by manual entry', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleNewValue: string = '21'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + ReactTestUtils.Simulate.input(inputDOM, mockEvent(exampleNewValue)); + ReactTestUtils.Simulate.blur(inputDOM); + + expect(inputDOM.value).to.equal(exampleNewValue); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleNewValue)); + }); + + it('should reset the value of the spin button with invalid manual entry', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleNewValue: string = 'garbage'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + ReactTestUtils.Simulate.input(inputDOM, mockEvent(exampleNewValue)); + ReactTestUtils.Simulate.blur(inputDOM); + + expect(inputDOM.value).to.equal(exampleDefaultValue); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleDefaultValue)); + }); + + it('should revert to max value when input value is higher than the max of the spin button', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleNewValue: string = '23'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + ReactTestUtils.Simulate.input(inputDOM, mockEvent(exampleNewValue)); + ReactTestUtils.Simulate.blur(inputDOM); + + expect(inputDOM.value).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleMaxValue)); + }); + + it('should revert existing value when input value is lower than the min of the spin button', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleNewValue: string = '0'; + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + ReactTestUtils.Simulate.input(inputDOM, mockEvent(String(exampleNewValue))); + ReactTestUtils.Simulate.blur(inputDOM); + + expect(inputDOM.value).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleMinValue)); + }); + + it('should use validator passed to the spin button (with valid input)', () => { + const errorMessage: string = 'The value is invalid'; + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleNewValue: string = '21'; + + const renderedDOM: HTMLElement = renderIntoDocument( + { + let numberValue: number = +newValue; + return (!isNaN(numberValue) && numberValue >= exampleMinValue && numberValue <= exampleMaxValue) ? newValue : errorMessage; + } } + /> + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + ReactTestUtils.Simulate.input(inputDOM, mockEvent(String(exampleNewValue))); + ReactTestUtils.Simulate.blur(inputDOM); + + expect(inputDOM.value).to.equal(String(exampleNewValue)); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleNewValue)); + }); + + it('should use validator passed to the spin button', () => { + const errorMessage: string = 'The value is invalid'; + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleNewValue: string = '100'; + + const renderedDOM: HTMLElement = renderIntoDocument( + { + let numberValue: number = Number(newValue); + return (!isNaN(numberValue) && numberValue >= exampleMinValue && numberValue <= exampleMaxValue) ? newValue : errorMessage; + } } + /> + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + ReactTestUtils.Simulate.input(inputDOM, mockEvent(String(exampleNewValue))); + ReactTestUtils.Simulate.blur(inputDOM); + + expect(inputDOM.value).to.equal(String(errorMessage)); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(errorMessage)); + }); + + it('should have correct value after increment and using defaultValue', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleStepValue: number = 2; + const exampleNewValue: string = String(Number(exampleDefaultValue) + exampleStepValue); + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + const upButtonDOM: HTMLButtonElement = renderedDOM.getElementsByClassName('ms-UpButton')[0] as HTMLButtonElement; + ReactTestUtils.Simulate.mouseDown(upButtonDOM); + ReactTestUtils.Simulate.mouseUp(upButtonDOM); + + expect(inputDOM.value).to.equal(String(exampleNewValue)); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleNewValue)); + }); + + it('should have correct value after decrement and using defaultValue', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + const exampleStepValue: number = 2; + const exampleNewValue: string = String(Number(exampleDefaultValue) - exampleStepValue); + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + const downButtonDOM: HTMLButtonElement = renderedDOM.getElementsByClassName('ms-DownButton')[0] as HTMLButtonElement; + ReactTestUtils.Simulate.mouseDown(downButtonDOM); + ReactTestUtils.Simulate.mouseUp(downButtonDOM); + + expect(inputDOM.value).to.equal(String(exampleNewValue)); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(String(exampleNewValue)); + }); + + it('should stop spinning if text field is focused while actively spinning', () => { + const exampleLabelValue: string = 'SpinButton'; + const exampleMinValue: number = 2; + const exampleMaxValue: number = 22; + const exampleDefaultValue: string = '12'; + + function delay(millisecond: number): Promise { + return new Promise((resolve) => setTimeout(resolve, millisecond)); + } + + const renderedDOM: HTMLElement = renderIntoDocument( + + ); + + // Assert on the input element. + const inputDOM: HTMLInputElement = renderedDOM.getElementsByTagName('input')[0]; + const buttonDOM: Element = renderedDOM.getElementsByClassName('ms-UpButton')[0]; + + expect(buttonDOM.tagName).to.equal('BUTTON'); + + ReactTestUtils.Simulate.mouseDown(buttonDOM, + { + type: 'mousedown', + clientX: 0, + clientY: 0 + }); + + delay(500).then(() => ReactTestUtils.Simulate.focus(inputDOM)); + + let currentValue = inputDOM.value; + expect(currentValue).to.not.equal('2'); + expect(inputDOM.getAttribute('aria-valuemin')).to.equal(String(exampleMinValue)); + expect(inputDOM.getAttribute('aria-valuemax')).to.equal(String(exampleMaxValue)); + expect(inputDOM.getAttribute('aria-valuenow')).to.equal(currentValue); + + let newCurrentValue = inputDOM.value; + expect(currentValue).to.equal(newCurrentValue); + }); +}); diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx new file mode 100644 index 0000000000000..4e29fefbe90fb --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButton.tsx @@ -0,0 +1,428 @@ +import * as React from 'react'; +import { IconButton } from '../../Button'; +import { Label } from '../../Label'; +import { Icon } from '../../Icon'; +import { + BaseComponent, + css, + getId, + KeyCodes, + autobind +} from '../../Utilities'; +import { + ISpinButton, + ISpinButtonProps +} from './SpinButton.Props'; +import { Position } from '../../utilities/positioning'; +import * as stylesImport from './SpinButton.scss'; +const styles: any = stylesImport; + +export enum KeyboardSpinDirection { + down = -1, + notSpinning = 0, + up = 1 +} + +export interface ISpinButtonState { + /** + * the value of the spin button + */ + value?: string; + + /** + * keyboard spin direction, used to style the up or down button + * as active when up/down arrow is pressed + */ + keyboardSpinDirection?: KeyboardSpinDirection; +} + +export class SpinButton extends BaseComponent implements ISpinButton { + + public static defaultProps: ISpinButtonProps = { + step: 1, + min: 0, + max: 100, + disabled: false, + labelPosition: Position.start, + label: null, + incrementButtonIcon: { iconName: 'ChevronUpSmall' }, + decrementButtonIcon: { iconName: 'ChevronDownSmall' } + }; + + private _input: HTMLInputElement; + private _inputId: string; + private _labelId: string; + private _lastValidValue: string; + private _spinningByMouse: boolean; + + private _onValidate?: (value: string) => string | void; + private _onIncrement?: (value: string) => string | void; + private _onDecrement?: (value: string) => string | void; + + private _currentStepFunctionHandle: number; + private _stepDelay = 100; + private _formattedValidUnitOptions: string[] = []; + private _arrowButtonStyle: React.CSSProperties = { + icon: { + fontSize: '6px', + } + }; + + constructor(props: ISpinButtonProps) { + super(props); + + this._warnMutuallyExclusive({ + 'value': 'defaultValue' + }); + + let value = props.value || props.defaultValue || String(props.min); + this._lastValidValue = value; + + this.state = { + value: value, + keyboardSpinDirection: KeyboardSpinDirection.notSpinning + }; + + this._currentStepFunctionHandle = -1; + this._labelId = getId('Label'); + this._inputId = getId('input'); + this._spinningByMouse = false; + + if (props.defaultValue) { + this._onValidate = this._defaultOnValidate; + this._onIncrement = this._defaultOnIncrement; + this._onDecrement = this._defaultOnDecrement; + } else { + this._onValidate = props.onValidate; + this._onIncrement = props.onIncrement; + this._onDecrement = props.onDecrement; + } + + this.focus = this.focus.bind(this); + } + + /** + * Invoked when a component is receiving new props. This method is not called for the initial render. + */ + public componentWillReceiveProps(newProps: ISpinButtonProps): void { + this._lastValidValue = this.state.value; + let value: string = newProps.value ? newProps.value : String(newProps.min); + if (newProps.defaultValue) { + value = String(Math.max(newProps.min, Math.min(newProps.max, Number(newProps.defaultValue)))); + } + + this.setState({ + value: value + }); + } + + public render() { + const { + disabled, + label, + min, + max, + labelPosition, + iconProps, + incrementButtonIcon, + decrementButtonIcon, + title, + ariaLabel + } = this.props; + + const { + value, + keyboardSpinDirection + } = this.state; + + return ( +
+ { labelPosition !== Position.bottom &&
+ { iconProps && } + { label && + + } +
} +
+ + + +
+ { labelPosition === Position.bottom &&
+ { iconProps && } + { label && + + } +
+ } +
+ ); + } + + /** + * OnFocus select the contents of the input + */ + public focus() { + if (this._spinningByMouse || this.state.keyboardSpinDirection !== KeyboardSpinDirection.notSpinning) { + this._stop(); + } + + this._input.focus(); + this._input.select(); + } + + /** + * Validate function to use if one is not passed in + */ + private _defaultOnValidate = (value: string) => { + if (isNaN(Number(value))) { + return this._lastValidValue; + } + const newValue = Math.min(this.props.max, Math.max(this.props.min, Number(value))); + return String(newValue); + } + + /** + * Increment function to use if one is not passed in + */ + private _defaultOnIncrement = (value: string) => { + let newValue = Math.min(Number(value) + this.props.step, this.props.max); + return String(newValue); + } + + /** + * Increment function to use if one is not passed in + */ + private _defaultOnDecrement = (value: string) => { + let newValue = Math.max(Number(value) - this.props.step, this.props.min); + return String(newValue); + } + + /** + * Returns the class name corresponding to the label position + */ + private _getClassNameForLabelPosition(labelPosition: Position): string { + let className: string = ''; + + switch (labelPosition) { + case Position.start: + className = styles.start; + break; + case Position.end: + className = styles.end; + break; + case Position.top: + className = styles.top; + break; + case Position.bottom: + className = styles.bottom; + } + + return className; + } + + private _onChange() { + /** + * A noop input change handler. + * https://github.com/facebook/react/issues/7027. + * Using the native onInput handler fixes the issue but onChange + * still need to be wired to avoid React console errors + * TODO: Check if issue is resolved when React 16 is available. + */ + } + + /** + * This is used when validating text entry + * in the input (not when changed via the buttons) + * @param event - the event that fired + */ + @autobind + private _validate(event: React.FocusEvent) { + const element: HTMLInputElement = event.target as HTMLInputElement; + const value: string = element.value; + if (this.state.value) { + const newValue = this._onValidate(value); + if (newValue) { + this._lastValidValue = newValue; + this.setState({ value: newValue }); + } + } + } + + /** + * The method is needed to ensure we are updating the actual input value. + * without this our value will never change (and validation will not have the correct number) + * @param event - the event that was fired + */ + @autobind + private _onInputChange(event: React.FormEvent): void { + const element: HTMLInputElement = event.target as HTMLInputElement; + const value: string = element.value; + + this.setState({ + value: value, + }); + } + + /** + * Update the value with the given stepFunction + * @param shouldSpin - should we fire off another updateValue when we are done here? This should be true + * when spinning in response to a mouseDown + * @param stepFunction - function to use to step by + */ + @autobind + private _updateValue(shouldSpin: boolean, stepFunction: (string) => string | void) { + const newValue = stepFunction(this.state.value); + if (newValue) { + this._lastValidValue = newValue; + this.setState({ value: newValue }); + } + + if (this._spinningByMouse !== shouldSpin) { + this._spinningByMouse = shouldSpin; + } + + if (shouldSpin) { + this._currentStepFunctionHandle = this._async.setTimeout(() => { this._updateValue(shouldSpin, stepFunction); }, this._stepDelay); + } + } + + /** + * Stop spinning (clear any currently pending update and set spinning to false) + */ + @autobind + private _stop() { + if (this._currentStepFunctionHandle >= 0) { + this._async.clearTimeout(this._currentStepFunctionHandle); + this._currentStepFunctionHandle = -1; + } + + if (this._spinningByMouse || this.state.keyboardSpinDirection !== KeyboardSpinDirection.notSpinning) { + this._spinningByMouse = false; + this.setState({ keyboardSpinDirection: KeyboardSpinDirection.notSpinning }); + } + } + + /** + * Handle keydown on the text field. We need to update + * the value when up or down arrow are depressed + * @param event - the keyboardEvent that was fired + */ + @autobind + private _handleKeyDown(event: React.KeyboardEvent) { + if (this.props.disabled) { + this._stop(); + + // eat the up and down arrow keys to keep the page from scrolling + if (event.which === KeyCodes.up || event.which === KeyCodes.down) { + event.preventDefault(); + event.stopPropagation(); + } + + return; + } + + let spinDirection = KeyboardSpinDirection.notSpinning; + + if (event.which === KeyCodes.up) { + + spinDirection = KeyboardSpinDirection.up; + this._updateValue(false /* shouldSpin */, this._onIncrement); + } else if (event.which === KeyCodes.down) { + + spinDirection = KeyboardSpinDirection.down; + this._updateValue(false /* shouldSpin */, this._onDecrement); + } else if (event.which === KeyCodes.enter) { + event.currentTarget.blur(); + this.focus(); + } else if (event.which === KeyCodes.escape) { + if (this.state.value !== this._lastValidValue) { + this.setState({ value: this._lastValidValue }); + } + } + + // style the increment/decrement button to look active + // when the corresponding up/down arrow keys trigger a step + if (this.state.keyboardSpinDirection !== spinDirection) { + this.setState({ keyboardSpinDirection: spinDirection }); + } + } + + /** + * Make sure that we have stopped spinning on keyUp + * if the up or down arrow fired this event + * @param event stop spinning if we + */ + @autobind + private _handleKeyUp(event: React.KeyboardEvent) { + + if (this.props.disabled || event.which === KeyCodes.up || event.which === KeyCodes.down) { + this._stop(); + return; + } + } + + @autobind + private _onIncrementMouseDown() { + this._updateValue(true /* shouldSpin */, this._onIncrement); + } + + @autobind + private _onDecrementMouseDown() { + this._updateValue(true /* shouldSpin */, this._onDecrement); + } + +} \ No newline at end of file diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/SpinButtonPage.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButtonPage.tsx new file mode 100644 index 0000000000000..37fd9c958c51a --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/SpinButtonPage.tsx @@ -0,0 +1,97 @@ +import * as React from 'react'; +import { + ExampleCard, + IComponentDemoPageProps, + ComponentPage, + PropertiesTableSet +} from '@uifabric/example-app-base'; +import { SpinButtonBasicExample } from './examples/SpinButton.Basic.Example'; +import { SpinButtonBasicDisabledExample } from './examples/SpinButton.BasicDisabled.Example'; +import { SpinButtonStatefulExample } from './examples/SpinButton.Stateful.Example'; +import { SpinButtonBasicWithIconExample } from './examples/SpinButton.BasicWithIcon.Example'; +import { SpinButtonBasicWithEndPositionExample } from './examples/SpinButton.BasicWithEndPosition.Example'; + +const SpinButtonBasicExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Basic.Example.tsx') as string; +const SpinButtonBasicDisabledExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicDisabled.Example.tsx') as string; +const SpinButtonStatefulExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Stateful.Example.tsx') as string; +const SpinButtonBasicWithIconExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithIcon.Example.tsx') as string; +const SpinButtonBasicWithEndPositionExampleCode = require('!raw-loader!office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithEndPosition.Example.tsx') as string; + +export class SpinButtonPage extends React.Component { + public render() { + return ( + + + + + + + + + + + + + + + + +
+ } + propertiesTables={ + ('!raw-loader!office-ui-fabric-react/src/components/SpinButton/SpinButton.Props.ts') + ] } + /> + } + overview={ +
+

+ A SpinButton allows the user to incrementaly adjust a value in small steps. It is mainly used for numeric values, but other values are supported too. +

+
+ } + bestPractices={ +
+ } + dos={ +
+
    +
  • Use a SpinButton when changing a value with precise control.
  • +
  • Use a SpinButton when values are tied to a unit.
  • +
  • Include a label indicating what value the SpinButton changes.
  • +
+
+ } + donts={ +
+
    +
  • Don’t use a SpinButton if the range of values is large.
  • +
  • Don’t use a SpinButton for binary settings.
  • +
  • Don't use a SpinButton for a range of three values or less.
  • +
+
+ } + related={ + Fabric JS + } + isHeaderVisible={ this.props.isHeaderVisible }> + + ); + } +} diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Basic.Example.tsx new file mode 100644 index 0000000000000..4999ef440bc24 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Basic.Example.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { SpinButton } from 'office-ui-fabric-react/lib/SpinButton'; + +export class SpinButtonBasicExample extends React.Component { + public render() { + return ( +
+ +
+ ); + } +} diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicDisabled.Example.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicDisabled.Example.tsx new file mode 100644 index 0000000000000..b57b1d432db08 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicDisabled.Example.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { SpinButton } from 'office-ui-fabric-react/lib/SpinButton'; + +export class SpinButtonBasicDisabledExample extends React.Component { + public render() { + return ( +
+ +
+ ); + } +} diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithEndPosition.Example.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithEndPosition.Example.tsx new file mode 100644 index 0000000000000..31d1d77b14ab5 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithEndPosition.Example.tsx @@ -0,0 +1,21 @@ +import * as React from 'react'; +import { SpinButton } from 'office-ui-fabric-react/lib/SpinButton'; +import { Position } from 'office-ui-fabric-react/lib/utilities/positioning'; + +export class SpinButtonBasicWithEndPositionExample extends React.Component { + public render() { + return ( +
+ +
+ ); + } +} diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithIcon.Example.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithIcon.Example.tsx new file mode 100644 index 0000000000000..056ba07096ba3 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.BasicWithIcon.Example.tsx @@ -0,0 +1,19 @@ +import * as React from 'react'; +import { SpinButton } from 'office-ui-fabric-react/lib/SpinButton'; + +export class SpinButtonBasicWithIconExample extends React.Component { + public render() { + return ( +
+ +
+ ); + } +} diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Stateful.Example.tsx b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Stateful.Example.tsx new file mode 100644 index 0000000000000..205ddc1370100 --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/examples/SpinButton.Stateful.Example.tsx @@ -0,0 +1,46 @@ +import * as React from 'react'; +import { SpinButton, ISpinButtonState, ISpinButtonProps } from 'office-ui-fabric-react/lib/SpinButton'; + +export class SpinButtonStatefulExample extends React.Component { + public render() { + let suffix = ' cm'; + + return ( +
+ { + value = this.removeSuffix(value, suffix); + if (isNaN(+value)) { + return '0' + suffix; + } + + return String(value) + suffix; + } } + onIncrement={ (value: string) => { + value = this.removeSuffix(value, suffix); + return String(+value + 2) + suffix; + } } + onDecrement={ (value: string) => { + value = this.removeSuffix(value, suffix); + return String(+value - 2) + suffix; + } } + /> +
+ ); + } + + private hasSuffix(string: string, suffix: string): Boolean { + let subString = string.substr(string.length - suffix.length); + return subString === suffix; + } + + private removeSuffix(string: string, suffix: string): string { + if (!this.hasSuffix(string, suffix)) { + return string; + } + + return string.substr(0, string.length - suffix.length); + } +} diff --git a/packages/office-ui-fabric-react/src/components/SpinButton/index.ts b/packages/office-ui-fabric-react/src/components/SpinButton/index.ts new file mode 100644 index 0000000000000..08acda35da3bd --- /dev/null +++ b/packages/office-ui-fabric-react/src/components/SpinButton/index.ts @@ -0,0 +1,2 @@ +export * from './SpinButton'; +export * from './SpinButton.Props'; diff --git a/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx b/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx index b28edcfe89b2d..f7deda39eb3fe 100644 --- a/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx +++ b/packages/office-ui-fabric-react/src/demo/AppDefinition.tsx @@ -208,6 +208,12 @@ export const AppDefinition: IAppDefinition = { name: 'SearchBox', url: '#/examples/searchbox' }, + { + component: require('../components/SpinButton/SpinButtonPage').SpinButtonPage, + key: 'SpinButton', + name: 'SpinButton', + url: '#/examples/spinbutton' + }, { component: require('../components/Spinner/SpinnerPage').SpinnerPage, key: 'Spinner', diff --git a/packages/office-ui-fabric-react/src/index.ts b/packages/office-ui-fabric-react/src/index.ts index ec653160e48d1..7bc84c48df80f 100644 --- a/packages/office-ui-fabric-react/src/index.ts +++ b/packages/office-ui-fabric-react/src/index.ts @@ -41,6 +41,7 @@ export * from './ProgressIndicator'; export * from './Rating'; export * from './SearchBox'; export * from './Slider'; +export * from './SpinButton'; export * from './Spinner'; export * from './Styling'; export * from './TeachingBubble'; diff --git a/packages/office-ui-fabric-react/src/utilities/positioning.ts b/packages/office-ui-fabric-react/src/utilities/positioning.ts index 75baa2310b660..b3b857cfd435e 100644 --- a/packages/office-ui-fabric-react/src/utilities/positioning.ts +++ b/packages/office-ui-fabric-react/src/utilities/positioning.ts @@ -13,6 +13,13 @@ export enum RectangleEdge { right = 3 } +export enum Position { + top = 0, + bottom = 1, + start = 2, + end = 3 +} + let SLIDE_ANIMATIONS: { [key: number]: string; } = { [RectangleEdge.top]: 'slideUpIn20', [RectangleEdge.bottom]: 'slideDownIn20', From 8db368fbebe1f711181322b7aa9153f89c67892d Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 11:31:43 -0700 Subject: [PATCH 12/31] merge some changes --- packages/styling/CHANGELOG.md | 7 +++++++ packages/styling/package.json | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/styling/CHANGELOG.md b/packages/styling/CHANGELOG.md index 7184d546076d4..c73030e7e1bdc 100644 --- a/packages/styling/CHANGELOG.md +++ b/packages/styling/CHANGELOG.md @@ -9,6 +9,13 @@ Mon, 12 Jun 2017 01:47:18 GMT - Enable no implicit any in utilities package +## 0.7.2 + Fri, 09 Jun 2017 10:10:47 GMT + + ### Patches + + - Update the version of rtl-css-js used to get latest bug fixes + ## 0.7.1 Thu, 08 Jun 2017 10:20:07 GMT diff --git a/packages/styling/package.json b/packages/styling/package.json index cdd29e6dd2826..6f0280a933519 100644 --- a/packages/styling/package.json +++ b/packages/styling/package.json @@ -47,7 +47,7 @@ "@microsoft/load-themed-styles": "^1.2.2", "@uifabric/utilities": ">=4.1.2 <5.0.0", "tslib": "^1.6.0", - "rtl-css-js": "^1.1.0", + "rtl-css-js": "^1.1.1", "glamor": "^2.20.24" } } \ No newline at end of file From b1a1f6e9211e20d36216cced0e6f15a492485068 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 11:35:24 -0700 Subject: [PATCH 13/31] merge changes --- common/config/rush/npm-shrinkwrap.json | 34 +++++++++++++++++++------- packages/styling/CHANGELOG.json | 14 +++++++++++ packages/styling/CHANGELOG.md | 6 ++--- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index 50bf39ecf082f..c8afd9393a958 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -805,15 +805,19 @@ "bundled": true }, "readable-stream": { - "version": "2.2.10", + "version": "2.2.11", "bundled": true, "dependencies": { "string_decoder": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true } } }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, "stream-browserify": { "version": "2.0.1", "bundled": true @@ -959,15 +963,19 @@ "bundled": true }, "readable-stream": { - "version": "2.2.10", + "version": "2.2.11", "bundled": true, "dependencies": { "string_decoder": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true } } }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, "source-map": { "version": "0.1.43", "bundled": true @@ -1099,15 +1107,19 @@ "bundled": true }, "readable-stream": { - "version": "2.2.10", + "version": "2.2.11", "bundled": true, "dependencies": { "string_decoder": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true } } }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, "stream-browserify": { "version": "2.0.1", "bundled": true @@ -1220,15 +1232,19 @@ "bundled": true }, "readable-stream": { - "version": "2.2.10", + "version": "2.2.11", "bundled": true, "dependencies": { "string_decoder": { - "version": "1.0.1", + "version": "1.0.2", "bundled": true } } }, + "safe-buffer": { + "version": "5.0.1", + "bundled": true + }, "source-map": { "version": "0.1.43", "bundled": true @@ -10517,4 +10533,4 @@ "integrity": "sha1-RCs4+denr3rke/z8NWhz6GFq0bc=" } } -} +} \ No newline at end of file diff --git a/packages/styling/CHANGELOG.json b/packages/styling/CHANGELOG.json index 19e1528a1e160..f8863ed432caf 100644 --- a/packages/styling/CHANGELOG.json +++ b/packages/styling/CHANGELOG.json @@ -20,6 +20,20 @@ ] } }, + { + "version": "0.7.2", + "tag": "@uifabric/styling_v0.7.2", + "date": "Fri, 09 Jun 2017 10:10:47 GMT", + "comments": { + "patch": [ + { + "author": "Christian Gonzalez ", + "commit": "bc7c1ebc6b552d523728c2cb9a4f4127df38a11c", + "comment": "Update the version of rtl-css-js used to get latest bug fixes" + } + ] + } + }, { "version": "0.7.1", "tag": "@uifabric/styling_v0.7.1", diff --git a/packages/styling/CHANGELOG.md b/packages/styling/CHANGELOG.md index c73030e7e1bdc..91130f26322fd 100644 --- a/packages/styling/CHANGELOG.md +++ b/packages/styling/CHANGELOG.md @@ -10,11 +10,11 @@ Mon, 12 Jun 2017 01:47:18 GMT - Enable no implicit any in utilities package ## 0.7.2 - Fri, 09 Jun 2017 10:10:47 GMT +Fri, 09 Jun 2017 10:10:47 GMT - ### Patches +### Patches - - Update the version of rtl-css-js used to get latest bug fixes +- Update the version of rtl-css-js used to get latest bug fixes ## 0.7.1 Thu, 08 Jun 2017 10:20:07 GMT From 2be204345c57436d1eed059d0fe4eee27c87804c Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 11:37:45 -0700 Subject: [PATCH 14/31] merge --- common/config/rush/npm-shrinkwrap.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index c8afd9393a958..bdddbfe99f489 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -10533,4 +10533,4 @@ "integrity": "sha1-RCs4+denr3rke/z8NWhz6GFq0bc=" } } -} \ No newline at end of file +} From f61ce254dc606447866db58ed4bff272daac0b81 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 13:23:51 -0700 Subject: [PATCH 15/31] Fix a new tslint warning after npm installing --- .../office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx | 2 +- .../src/components/ComboBox/examples/ComboBox.Basic.Example.tsx | 2 +- .../src/components/pickers/AutoFill/BaseAutoFill.tsx | 1 - 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 7eeb320fe0a09..b97a4f30b1196 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -94,7 +94,7 @@ export class ComboBox extends BaseComponent { // Promise used when resolving the comboBox options private _currentPromise: PromiseLike; - //The current visible value sent to the auto fill on render + // The current visible value sent to the auto fill on render private _currentVisibleValue; constructor(props?: IComboBoxProps) { diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index 697b46348b9cd..1cf4d943ced0c 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -29,7 +29,7 @@ export class ComboBoxBasicExample extends React.Component { ['Time New Roman']: '"Times New Roman", "Times New Roman_MSFontService", serif', ['Comic Sans MS']: '"Comic Sans MS", "Comic Sans MS_MSFontService", fantasy', ['Calibri']: 'Calibri, Calibri_MSFontService, sans-serif' - } + }; constructor() { super(); diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx index d56a0ea9fc2bd..37149c5314e9e 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx @@ -1,7 +1,6 @@ import * as React from 'react'; import { IBaseAutoFillProps, IBaseAutoFill } from './BaseAutoFill.Props'; import { - assign, BaseComponent, KeyCodes, autobind, From 51e4e219c2a1957649bbb7807141d6a23ecbf25d Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 13:52:55 -0700 Subject: [PATCH 16/31] Fixing some casing warnings npm start was angry about --- packages/office-ui-fabric-react/src/SelectableOption.ts | 2 +- .../src/components/ComboBox/ComboBox.Props.ts | 4 ++-- .../src/components/ComboBox/ComboBox.test.tsx | 2 +- .../src/components/ComboBox/ComboBox.tsx | 2 +- .../components/ComboBox/examples/ComboBox.Basic.Example.tsx | 2 +- .../src/components/Dropdown/Dropdown.Props.ts | 4 ++-- .../src/components/Dropdown/Dropdown.tsx | 2 +- .../components/Dropdown/examples/Dropdown.Basic.Example.tsx | 2 +- packages/office-ui-fabric-react/src/index.ts | 1 + .../selectableOption/SelectableDroppableText.Props.ts | 2 +- 10 files changed, 12 insertions(+), 11 deletions(-) diff --git a/packages/office-ui-fabric-react/src/SelectableOption.ts b/packages/office-ui-fabric-react/src/SelectableOption.ts index ec2ae3d50cbd7..a313435011aec 100644 --- a/packages/office-ui-fabric-react/src/SelectableOption.ts +++ b/packages/office-ui-fabric-react/src/SelectableOption.ts @@ -1 +1 @@ -export * from './utilities/selectableOption/index'; +export * from './Utilities/SelectableOption/index'; diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts index f5f7abeb25aee..a1179dbdd4eb0 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -1,6 +1,6 @@ import { IIconProps } from '../../Icon'; -import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; -import { ISelectableDroppableTextProps } from '../../Utilities/selectableOption/SelectableDroppableText.Props'; +import { ISelectableOption } from '../../Utilities/SelectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../Utilities/SelectableOption/SelectableDroppableText.Props'; export interface IComboBoxProps extends ISelectableDroppableTextProps { diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index 9f4f45aab86b8..d0644dc711f7a 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -8,7 +8,7 @@ import { KeyCodes } from 'office-ui-fabric-react/lib/Utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; -import { ISelectableOption } from 'office-ui-fabric-react/lib/Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption } from 'office-ui-fabric-react/lib/Utilities/SelectableOption/SelectableOption.Props'; const DEFAULT_OPTIONS: ISelectableOption[] = [ { key: '1', text: '1' }, diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index b97a4f30b1196..1d9e31d0a4b38 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -17,7 +17,7 @@ import { getId, KeyCodes } from '../../Utilities'; -import { ISelectableOption, SelectableOptionMenuItemType } from '../../Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption, SelectableOptionMenuItemType } from '../../Utilities/SelectableOption/SelectableOption.Props'; import * as stylesImport from './ComboBox.scss'; const styles: any = stylesImport; diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index 1cf4d943ced0c..ee2bff03422a0 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -5,7 +5,7 @@ import { assign, autobind } from 'office-ui-fabric-react/lib/Utilities'; -import { ISelectableOption, SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption, SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/SelectableOption/SelectableOption.Props'; export class ComboBoxBasicExample extends React.Component { private _testOptions = diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts index b523682c67d53..dcb0ee7d668a8 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts @@ -1,7 +1,7 @@ import { IRenderFunction } from '../../Utilities'; import { Dropdown } from './Dropdown'; -import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; -import { ISelectableDroppableTextProps } from '../../Utilities/selectableOption/SelectableDroppableText.Props'; +import { ISelectableOption } from '../../Utilities/SelectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../Utilities/SelectableOption/SelectableDroppableText.Props'; export interface IDropdown { diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx index edc2fc3a4becb..4bfa89e92bf9f 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx @@ -17,7 +17,7 @@ import { findIndex, getId } from '../../Utilities'; -import { SelectableOptionMenuItemType } from '../../Utilities/selectableOption/SelectableOption.Props'; +import { SelectableOptionMenuItemType } from '../../Utilities/SelectableOption/SelectableOption.Props'; import * as stylesImport from './Dropdown.scss'; const styles: any = stylesImport; diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx index a347356dd0aa1..1bf7d5060eaa1 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; import './Dropdown.Basic.Example.scss'; -import { SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/selectableOption/SelectableOption.Props'; +import { SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/SelectableOption/SelectableOption.Props'; export class DropdownBasicExample extends React.Component { constructor() { diff --git a/packages/office-ui-fabric-react/src/index.ts b/packages/office-ui-fabric-react/src/index.ts index 7bc84c48df80f..22dee1d6ce0e7 100644 --- a/packages/office-ui-fabric-react/src/index.ts +++ b/packages/office-ui-fabric-react/src/index.ts @@ -40,6 +40,7 @@ export * from './Pivot'; export * from './ProgressIndicator'; export * from './Rating'; export * from './SearchBox'; +export * from './SelectableOption'; export * from './Slider'; export * from './SpinButton'; export * from './Spinner'; diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts index fb4ab0c0f4360..8954db78c7a72 100644 --- a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { IRenderFunction } from '../../Utilities'; import { ICalloutProps } from '../../Callout'; -import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption } from '../../Utilities/SelectableOption/SelectableOption.Props'; export interface ISelectableDroppableTextProps extends React.Props { /** From f8e0956659d6cfda41dd58bb584aa04b65fe65f7 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 14:20:38 -0700 Subject: [PATCH 17/31] Removing an extra line that got added with the last push --- packages/office-ui-fabric-react/src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/index.ts b/packages/office-ui-fabric-react/src/index.ts index 22dee1d6ce0e7..7bc84c48df80f 100644 --- a/packages/office-ui-fabric-react/src/index.ts +++ b/packages/office-ui-fabric-react/src/index.ts @@ -40,7 +40,6 @@ export * from './Pivot'; export * from './ProgressIndicator'; export * from './Rating'; export * from './SearchBox'; -export * from './SelectableOption'; export * from './Slider'; export * from './SpinButton'; export * from './Spinner'; From b312e368e6da16a72d493aedb8f71091b9aacc4d Mon Sep 17 00:00:00 2001 From: David Zearing Date: Mon, 12 Jun 2017 16:16:06 -0700 Subject: [PATCH 18/31] Create SelectableOption.ts --- packages/office-ui-fabric-react/src/SelectableOption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/SelectableOption.ts b/packages/office-ui-fabric-react/src/SelectableOption.ts index a313435011aec..85251d399de17 100644 --- a/packages/office-ui-fabric-react/src/SelectableOption.ts +++ b/packages/office-ui-fabric-react/src/SelectableOption.ts @@ -1 +1 @@ -export * from './Utilities/SelectableOption/index'; +export * from './utilities/SelectableOption/index'; From 97c208f56c1bc9decc3b4d4be904ef8ca912ee20 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Mon, 12 Jun 2017 16:16:33 -0700 Subject: [PATCH 19/31] Create SelectableOption.ts --- packages/office-ui-fabric-react/src/SelectableOption.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/SelectableOption.ts b/packages/office-ui-fabric-react/src/SelectableOption.ts index 85251d399de17..ec2ae3d50cbd7 100644 --- a/packages/office-ui-fabric-react/src/SelectableOption.ts +++ b/packages/office-ui-fabric-react/src/SelectableOption.ts @@ -1 +1 @@ -export * from './utilities/SelectableOption/index'; +export * from './utilities/selectableOption/index'; From 80f901e23e072e7e21787937bd98b74eda859ad0 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Mon, 12 Jun 2017 16:17:09 -0700 Subject: [PATCH 20/31] Create ComboBox.Props.ts --- .../src/components/ComboBox/ComboBox.Props.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts index a1179dbdd4eb0..271b6115db727 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -1,6 +1,6 @@ import { IIconProps } from '../../Icon'; -import { ISelectableOption } from '../../Utilities/SelectableOption/SelectableOption.Props'; -import { ISelectableDroppableTextProps } from '../../Utilities/SelectableOption/SelectableDroppableText.Props'; +import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../utilities/selectableOption/SelectableDroppableText.Props'; export interface IComboBoxProps extends ISelectableDroppableTextProps { From 2670a5d7a072de7160b17d69ff19957377a1b898 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Mon, 12 Jun 2017 16:17:49 -0700 Subject: [PATCH 21/31] Create ComboBox.test.tsx --- .../src/components/ComboBox/ComboBox.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index d0644dc711f7a..29bb6cdbbce13 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -8,7 +8,7 @@ import { KeyCodes } from 'office-ui-fabric-react/lib/Utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; -import { ISelectableOption } from 'office-ui-fabric-react/lib/Utilities/SelectableOption/SelectableOption.Props'; +import { ISelectableOption } from 'office-ui-fabric-react/lib/utilities/selectableOption/SelectableOption.Props'; const DEFAULT_OPTIONS: ISelectableOption[] = [ { key: '1', text: '1' }, @@ -322,4 +322,4 @@ describe('ComboBox', () => { expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); } }); -}); \ No newline at end of file +}); From d02daaae29e96bca6cbdb84444ca71e1b954ab91 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Mon, 12 Jun 2017 16:18:36 -0700 Subject: [PATCH 22/31] Create ComboBox.Basic.Example.tsx --- .../src/components/ComboBox/examples/ComboBox.Basic.Example.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index ee2bff03422a0..59f0ddd4fdd3f 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -5,7 +5,7 @@ import { assign, autobind } from 'office-ui-fabric-react/lib/Utilities'; -import { ISelectableOption, SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/SelectableOption/SelectableOption.Props'; +import { ISelectableOption, SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/utilities/selectableOption/SelectableOption.Props'; export class ComboBoxBasicExample extends React.Component { private _testOptions = From 398bdd1445cbf4e7f22677efeeaba42b069f17b4 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Mon, 12 Jun 2017 16:19:08 -0700 Subject: [PATCH 23/31] Create Dropdown.Props.ts --- .../src/components/Dropdown/Dropdown.Props.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts index dcb0ee7d668a8..1d319e450d777 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts @@ -1,7 +1,7 @@ import { IRenderFunction } from '../../Utilities'; import { Dropdown } from './Dropdown'; -import { ISelectableOption } from '../../Utilities/SelectableOption/SelectableOption.Props'; -import { ISelectableDroppableTextProps } from '../../Utilities/SelectableOption/SelectableDroppableText.Props'; +import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../utilities/selectableOption/SelectableDroppableText.Props'; export interface IDropdown { From 12b7b7bac811ab9e673d08dd60d0d481d66ede88 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 16:23:36 -0700 Subject: [PATCH 24/31] Fix the case sensitivity issue --- .../office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx | 2 +- .../office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx | 2 +- .../src/components/Dropdown/examples/Dropdown.Basic.Example.tsx | 2 +- .../utilities/selectableOption/SelectableDroppableText.Props.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 1d9e31d0a4b38..a67ae0344fc64 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -17,7 +17,7 @@ import { getId, KeyCodes } from '../../Utilities'; -import { ISelectableOption, SelectableOptionMenuItemType } from '../../Utilities/SelectableOption/SelectableOption.Props'; +import { ISelectableOption, SelectableOptionMenuItemType } from '../../utilities/selectableOption/SelectableOption.Props'; import * as stylesImport from './ComboBox.scss'; const styles: any = stylesImport; diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx index 4bfa89e92bf9f..fc474a1c923d2 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx @@ -17,7 +17,7 @@ import { findIndex, getId } from '../../Utilities'; -import { SelectableOptionMenuItemType } from '../../Utilities/SelectableOption/SelectableOption.Props'; +import { SelectableOptionMenuItemType } from '../../utilities/selectableOption/SelectableOption.Props'; import * as stylesImport from './Dropdown.scss'; const styles: any = stylesImport; diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx index 1bf7d5060eaa1..f2c4f0569de02 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/examples/Dropdown.Basic.Example.tsx @@ -1,7 +1,7 @@ import * as React from 'react'; import { Dropdown } from 'office-ui-fabric-react/lib/Dropdown'; import './Dropdown.Basic.Example.scss'; -import { SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/Utilities/SelectableOption/SelectableOption.Props'; +import { SelectableOptionMenuItemType } from 'office-ui-fabric-react/lib/utilities/selectableOption/SelectableOption.Props'; export class DropdownBasicExample extends React.Component { constructor() { diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts index 8954db78c7a72..4ac749da16005 100644 --- a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts @@ -1,7 +1,7 @@ import * as React from 'react'; import { IRenderFunction } from '../../Utilities'; import { ICalloutProps } from '../../Callout'; -import { ISelectableOption } from '../../Utilities/SelectableOption/SelectableOption.Props'; +import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; export interface ISelectableDroppableTextProps extends React.Props { /** From 3536017433559112459103108059916d8a52d310 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 16:37:27 -0700 Subject: [PATCH 25/31] one more casing issue --- .../src/components/ComboBox/ComboBox.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index 29bb6cdbbce13..91b399d7fe845 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -4,7 +4,7 @@ import * as ReactDOM from 'react-dom'; /* tslint:enable:no-unused-variable */ import * as ReactTestUtils from 'react-addons-test-utils'; import { mount, shallow } from 'enzyme'; -import { KeyCodes } from 'office-ui-fabric-react/lib/Utilities'; +import { KeyCodes } from 'office-ui-fabric-react/lib/utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; From 55e2ad7ce34e174407b3803f787b0ceb5c45f19c Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 17:01:13 -0700 Subject: [PATCH 26/31] changing the reference of utilities in the test file --- .../src/components/ComboBox/ComboBox.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index 91b399d7fe845..532b2473e9b8e 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -4,11 +4,11 @@ import * as ReactDOM from 'react-dom'; /* tslint:enable:no-unused-variable */ import * as ReactTestUtils from 'react-addons-test-utils'; import { mount, shallow } from 'enzyme'; -import { KeyCodes } from 'office-ui-fabric-react/lib/utilities'; +import { KeyCodes } from '../../utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; -import { ISelectableOption } from 'office-ui-fabric-react/lib/utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; const DEFAULT_OPTIONS: ISelectableOption[] = [ { key: '1', text: '1' }, From c6b98ed149dc9945beafce6942b5a39fd54a6a21 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 17:18:16 -0700 Subject: [PATCH 27/31] Actually it look like it has to be pascalCase here --- .../src/components/ComboBox/ComboBox.test.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index 532b2473e9b8e..cf4015123c6cd 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -4,11 +4,11 @@ import * as ReactDOM from 'react-dom'; /* tslint:enable:no-unused-variable */ import * as ReactTestUtils from 'react-addons-test-utils'; import { mount, shallow } from 'enzyme'; -import { KeyCodes } from '../../utilities'; +import { KeyCodes } from '../../Utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; -import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; const DEFAULT_OPTIONS: ISelectableOption[] = [ { key: '1', text: '1' }, From 460f3de5e175a98f16184c9cf31003b29cae4447 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 12 Jun 2017 17:32:33 -0700 Subject: [PATCH 28/31] ... really... what's going on with the casing here --- .../src/components/ComboBox/ComboBox.test.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index cf4015123c6cd..3cf7523796eae 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -8,7 +8,7 @@ import { KeyCodes } from '../../Utilities'; let { expect } = chai; import { ComboBox } from './ComboBox'; -import { ISelectableOption } from '../../Utilities/selectableOption/SelectableOption.Props'; +import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; const DEFAULT_OPTIONS: ISelectableOption[] = [ { key: '1', text: '1' }, From 3022036fb14c219adc62f964a7a5117197a61c17 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Thu, 15 Jun 2017 13:58:35 -0700 Subject: [PATCH 29/31] Address feedback from in person review with David --- common/config/rush/npm-shrinkwrap.json | 80 +++++++++++-------- .../src/components/ComboBox/ComboBox.Props.ts | 13 +++ .../src/components/ComboBox/ComboBox.scss | 13 +-- .../src/components/ComboBox/ComboBox.tsx | 61 +++++++++++--- .../src/components/Dropdown/Dropdown.Props.ts | 2 + .../src/components/Dropdown/Dropdown.tsx | 2 +- .../SelectableDroppableText.Props.ts | 2 +- 7 files changed, 112 insertions(+), 61 deletions(-) diff --git a/common/config/rush/npm-shrinkwrap.json b/common/config/rush/npm-shrinkwrap.json index 631cf0740c655..0067e2978da41 100644 --- a/common/config/rush/npm-shrinkwrap.json +++ b/common/config/rush/npm-shrinkwrap.json @@ -570,9 +570,9 @@ "integrity": "sha1-DldYaol/ddjdDAKdKVtBapg4f5w=", "dependencies": { "@microsoft/api-extractor": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-2.0.7.tgz", - "integrity": "sha1-zmAg7skaKVy1JlmXu58gIqmBPWk=" + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@microsoft/api-extractor/-/api-extractor-2.0.8.tgz", + "integrity": "sha1-MbnfuwRtx2MYLgVQ+1shCD40nXA=" }, "@microsoft/gulp-core-build-sass": { "version": "3.1.2", @@ -605,9 +605,9 @@ "integrity": "sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=" }, "clean-css": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.3.tgz", - "integrity": "sha1-B8/omA7bINRV3cI6rc8eBMblCc4=" + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.4.tgz", + "integrity": "sha1-7siBHbJ0V+AHjYypIfqBty+oK/Q=" }, "configstore": { "version": "3.1.0", @@ -1648,14 +1648,14 @@ } }, "@uifabric/styling": { - "version": "0.7.4", - "resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-0.7.4.tgz", - "integrity": "sha512-MzIyq7vk6crRTWATfj8ryESGwZApt9RlFIScPkAFy8MBIvLhWP3Rc0H3/TrgjEYgIjDph+DybjmW/9aXlq3jLg==" + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/@uifabric/styling/-/styling-0.8.1.tgz", + "integrity": "sha512-7uldSHBNLP8dNfYg7W54WKTvLLqL9phUNkKUCxAIsXDpqx405ffk3QDVb2hos77Rc8bbNdujfk8IaXuUV/8Oyw==" }, "@uifabric/utilities": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-4.2.0.tgz", - "integrity": "sha512-eB9YLGf1hDFXV0Uq8+FoaHi13aLOvDAVHFSnqoIMnlAa1Q+3bODmpifwGcxRiP8WzqhRKlZKt4boK6iF6uQnOg==" + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@uifabric/utilities/-/utilities-4.3.0.tgz", + "integrity": "sha512-/4/xTLAJBtdYb+ohwWYPeusSiNlUCigPWlD5nkq+uAjGW/vkT/TDJXuCQasrb9QaV6wmgF6GDLzzZMykz5w0Tg==" }, "abbrev": { "version": "1.0.9", @@ -2302,9 +2302,9 @@ "integrity": "sha1-tTTnxzTE+B7F++isoq0kNUuWLGw=" }, "caniuse-db": { - "version": "1.0.30000684", - "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000684.tgz", - "integrity": "sha1-maywEYuP0f3WAaFeDA8t/BWoFoA=" + "version": "1.0.30000686", + "resolved": "https://registry.npmjs.org/caniuse-db/-/caniuse-db-1.0.30000686.tgz", + "integrity": "sha1-1VtHntbmQCwf0/H9j0bmlNhupGQ=" }, "capture-stack-trace": { "version": "1.0.0", @@ -2443,9 +2443,16 @@ "integrity": "sha1-4+JbIHrE5wGvch4staFnksrD3Fg=" }, "clone-deep": { - "version": "0.2.4", - "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.2.4.tgz", - "integrity": "sha1-TnPdCen7lxzDhnDF3O2cGJZIHMY=" + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-0.3.0.tgz", + "integrity": "sha1-NIxhrpzb4O3+BT2R/0zFIdeQ7eg=", + "dependencies": { + "for-own": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/for-own/-/for-own-1.0.0.tgz", + "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=" + } + } }, "clone-stats": { "version": "0.0.1", @@ -5551,14 +5558,14 @@ "integrity": "sha1-1zvD/0SJQkCIGM5gm/P7DqfvTrc=", "dependencies": { "clean-css": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.3.tgz", - "integrity": "sha1-B8/omA7bINRV3cI6rc8eBMblCc4=" + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.1.4.tgz", + "integrity": "sha1-7siBHbJ0V+AHjYypIfqBty+oK/Q=" }, "uglify-js": { - "version": "3.0.15", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.15.tgz", - "integrity": "sha1-qssyOoRrI0YCJw3q2KMkQaiAb0I=" + "version": "3.0.17", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.0.17.tgz", + "integrity": "sha1-0ijNVcLfmz0vU/FHVoy0zEpyzAY=" } } }, @@ -7353,9 +7360,9 @@ "integrity": "sha1-6mJvqUURirroSrsZc45KCLCHPx8=" }, "office-ui-fabric-react": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-4.5.1.tgz", - "integrity": "sha512-lPeE/wgI9C/ryKpaQGTQRvo/1txgoFs5/g6i5kBEMc41DDVj7ZMXW2kgPER6dtV9Ku1IYqcJSNwi/KP1a07KvA==" + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/office-ui-fabric-react/-/office-ui-fabric-react-4.7.0.tgz", + "integrity": "sha512-X/FHLjmUEgipp/BucZVFmw3x59DV3clKS5SDenFK9J4uQErHiXpS4OBRLs/gUgyxnrS5Xzu/CJk8hyp+I+LuEQ==" }, "on-finished": { "version": "2.3.0", @@ -8601,9 +8608,9 @@ } }, "sass-loader": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.5.tgz", - "integrity": "sha1-qEeRDzZEKqVsWYWHnVTrUZ4koyg=", + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/sass-loader/-/sass-loader-6.0.6.tgz", + "integrity": "sha512-c3/Zc+iW+qqDip6kXPYLEgsAu2lf4xz0EZDplB7EmSUMda12U1sGJPetH55B/j9eu0bTtKzKlNPWWyYC7wFNyQ==", "dependencies": { "async": { "version": "2.4.1", @@ -8614,6 +8621,11 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.1.0.tgz", "integrity": "sha1-yYrvSIvM7aL/teLeZG1qdUQp9c0=" + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=" } } }, @@ -9596,9 +9608,9 @@ "integrity": "sha1-BMgamb3V3FImPqKdJMa/jUgYpLs=" }, "uglify-js": { - "version": "2.8.28", - "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.28.tgz", - "integrity": "sha512-WqKNbmNJKzIdIEQu/U2ytgGBbhCy2PVks94GoetczOAJ/zCgVu2CuO7gguI5KPFGPtUtI1dmPQl6h0D4cPzypA==", + "version": "2.8.29", + "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz", + "integrity": "sha1-KcVzMUgFe7Th913zW3qcty5qWd0=", "dependencies": { "camelcase": { "version": "1.2.1", @@ -10515,4 +10527,4 @@ "integrity": "sha1-RCs4+denr3rke/z8NWhz6GFq0bc=" } } -} +} diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts index 271b6115db727..2f14bdfdb7ba0 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -2,7 +2,20 @@ import { IIconProps } from '../../Icon'; import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; import { ISelectableDroppableTextProps } from '../../utilities/selectableOption/SelectableDroppableText.Props'; +export interface IComboBox { + /** + * Sets focus to the input in the comboBox + * @returns True if focus could be set, false if no operation was taken. + */ + focus(): boolean; +} + export interface IComboBoxProps extends ISelectableDroppableTextProps { + /** + * Optional callback to access the IBreadcrumb interface. Use this instead of ref for accessing + * the public methods and properties of the component. + */ + componentRef?: (component: IComboBox) => void; /** * Collection of options for this ComboBox diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss index 57245f48caf87..f0d015f3c55ad 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.scss @@ -72,9 +72,8 @@ $ComboBox-item-height: 36px; } &.readOnly { - .input, .caretDown { - background-color: $ms-color-neutralLighter; + background-color: inherit; } } } @@ -121,15 +120,6 @@ $ComboBox-item-height: 36px; text-overflow: ellipsis; padding-left: 12px; - &:hover { - background-color: $ms-color-neutralLight; - color: $ms-color-neutralPrimary; - } - - &::selection { - background-color: $ms-color-neutralSecondaryAlt; - color: $ms-color-white; - } } .caretDown { @@ -154,7 +144,6 @@ $ComboBox-item-height: 36px; .callout { box-shadow: 0 0px 5px 0px rgba(0, 0, 0, 0.4); border: 1px solid $ms-color-neutralLight; - max-height: 576px; } .errorMessage{ diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index a67ae0344fc64..80970bc84a1a8 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -13,11 +13,14 @@ import { autobind, BaseComponent, css, + divProperties, findIndex, getId, + getNativeProps, KeyCodes } from '../../Utilities'; import { ISelectableOption, SelectableOptionMenuItemType } from '../../utilities/selectableOption/SelectableOption.Props'; +import { ISelectableDroppableTextProps } from '../../utilities/selectableOption/SelectableDroppableText.Props'; import * as stylesImport from './ComboBox.scss'; const styles: any = stylesImport; @@ -59,7 +62,7 @@ export class ComboBox extends BaseComponent { options: [], allowFreeform: false, autoComplete: true, - buttonIconProps: { iconName: 'ChevronDownSmall' } + buttonIconProps: { iconName: 'chevronDown' } }; public refs: { @@ -200,8 +203,10 @@ export class ComboBox extends BaseComponent { let { isOpen, selectedIndex, focused, suggestedDisplayValue } = this.state; this._currentVisibleValue = this._getVisibleValue(); + let divProps = getNativeProps(this.props, divProperties); + return ( -
+
{ label && ( ) } @@ -715,7 +720,7 @@ export class ComboBox extends BaseComponent { // Render Callout container and pass in list @autobind - private _onRenderContainer(props: IComboBoxProps): JSX.Element { + private _onRenderContainer(props: ISelectableDroppableTextProps | IComboBoxProps): JSX.Element { let { onRenderList = this._onRenderList, calloutProps @@ -734,7 +739,7 @@ export class ComboBox extends BaseComponent { onDismiss={ this._onDismiss } setInitialFocus={ false } > -
+
{ onRenderList({ ...this.props }, this._onRenderList) }
@@ -743,7 +748,7 @@ export class ComboBox extends BaseComponent { // Render List of items @autobind - private _onRenderList(props: IComboBoxProps): JSX.Element { + private _onRenderList(props: ISelectableDroppableTextProps | IComboBoxProps): JSX.Element { let { onRenderItem = this._onRenderItem } = this.props; @@ -1013,17 +1018,44 @@ export class ComboBox extends BaseComponent { // On enter submit the pending value this._submitPendingValue(); + // if we are open or + // if we are not allowing freeform or + // our we have no pending value + // and no valid pending index + // flip the open state + if ((isOpen || + ((!allowFreeform || + this.state.currentPendingValue === undefined || + this.state.currentPendingValue === null || + this.state.currentPendingValue.length <= 0) && + this.state.currentPendingValueValidIndex < 0))) { + this.setState({ + isOpen: !isOpen + }); + } + + // Allow TAB to propigate + if (ev.which === KeyCodes.tab) { + return; + } + break; + + case KeyCodes.tab: + // On enter submit the pending value + this._submitPendingValue(); + // If we are not allowing freeform // or the comboBox is open, flip the open state - if (!allowFreeform || isOpen) { + if (isOpen) { this.setState({ isOpen: !isOpen }); } - break; + + // Allow TAB to propigate + return; case KeyCodes.escape: - case KeyCodes.tab: // reset the selected index this._resetSelectedIndex(); @@ -1033,11 +1065,6 @@ export class ComboBox extends BaseComponent { isOpen: false }); } - - // Allow TAB to propigate - if (ev.which === KeyCodes.tab) { - return; - } break; case KeyCodes.up: @@ -1089,6 +1116,12 @@ export class ComboBox extends BaseComponent { return; } + // If we get here and we got either and ALT key + // or meta key and we are current open, let's close the menu + if ((ev.altKey || ev.metaKey) && isOpen) { + this.setState({ isOpen: !isOpen }); + } + // If we are not allowing freeform and // allowing autoComplete, handle the input here // since we have marked the input as readonly @@ -1096,6 +1129,8 @@ export class ComboBox extends BaseComponent { this._onInputChange(String.fromCharCode(ev.which)); break; } + + // allow the key to propigate by default return; } diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts index 00036a48ee896..2f62833c2eec0 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.Props.ts @@ -3,6 +3,8 @@ import { Dropdown } from './Dropdown'; import { ISelectableOption } from '../../utilities/selectableOption/SelectableOption.Props'; import { ISelectableDroppableTextProps } from '../../utilities/selectableOption/SelectableDroppableText.Props'; +export { SelectableOptionMenuItemType as DropdownMenuItemType } from '../../utilities/selectableOption/SelectableOption.Props'; + export interface IDropdown { } diff --git a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx index 8e80b18a51452..96f673c876046 100644 --- a/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx +++ b/packages/office-ui-fabric-react/src/components/Dropdown/Dropdown.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { IDropdownProps, IDropdownOption } from './Dropdown.Props'; +import { IDropdownProps, IDropdownOption, DropdownMenuItemType } from './Dropdown.Props'; import { DirectionalHint } from '../../common/DirectionalHint'; import { Callout } from '../../Callout'; import { Label } from '../../Label'; diff --git a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts index 4ac749da16005..633a04cd68d90 100644 --- a/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts +++ b/packages/office-ui-fabric-react/src/utilities/selectableOption/SelectableDroppableText.Props.ts @@ -44,7 +44,7 @@ export interface ISelectableDroppableTextProps extends React.Props { /** * Collection of options for this ISelectableDroppableText */ - options?: ISelectableOption[]; + options?: any; /** * Callback issues when the selected option changes From 358d02558a35fa3d6d25567459e6aaf096e1ee00 Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 19 Jun 2017 11:20:13 -0700 Subject: [PATCH 30/31] A few minor updated to remove uneeded comment and unneeded try/finally in tests --- .../src/components/ComboBox/ComboBox.test.tsx | 285 ++++++++---------- .../src/components/ComboBox/ComboBox.tsx | 11 +- 2 files changed, 125 insertions(+), 171 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx index 3cf7523796eae..e9be6876e1ad2 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.test.tsx @@ -113,213 +113,172 @@ describe('ComboBox', () => { it('Can change items in uncontrolled case', () => { let comboBoxRoot; - - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - let buttonElement = comboBoxRoot.find('button'); - buttonElement.simulate('click'); - let secondItemElement = wrapper.getDOMNode().ownerDocument.querySelector('.ms-ComboBox-item[data-index="1"]'); - ReactTestUtils.Simulate.click(secondItemElement); - } - finally { - let inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('2'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + let buttonElement = comboBoxRoot.find('button'); + buttonElement.simulate('click'); + let secondItemElement = wrapper.getDOMNode().ownerDocument.querySelector('.ms-ComboBox-item[data-index="1"]'); + ReactTestUtils.Simulate.click(secondItemElement); + let inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('2'); }); it('Can insert text in uncontrolled case with autoComplete and allowFreeform on', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: { value: 'f' } }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('Foo'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: { value: 'f' } }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('Foo'); }); it('Can insert text in uncontrolled case with autoComplete on and allowFreeform off', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: { value: 'f' } }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('Foo'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: { value: 'f' } }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('Foo'); }); it('Can insert text in uncontrolled case with autoComplete off and allowFreeform on', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('change', { target: { value: 'f' } }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('f'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('change', { target: { value: 'f' } }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('f'); }); it('Can insert text in uncontrolled case with autoComplete and allowFreeform off', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('keydown', { which: 'f' }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('One'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: 'f' }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('One'); }); it('Can change selected option with keyboard', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('keydown', { which: KeyCodes.down }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('Foo'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: KeyCodes.down }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('Foo'); }); it('Cannot insert text while disabled', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('keydown', { which: KeyCodes.a }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('One'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: KeyCodes.a }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('One'); }); it('Cannot change selected option with keyboard while disabled', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('keydown', { which: KeyCodes.down }); - } - finally { - inputElement = comboBoxRoot.find('input'); - expect(inputElement.props().value).equals('One'); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('keydown', { which: KeyCodes.down }); + inputElement = comboBoxRoot.find('input'); + expect(inputElement.props().value).equals('One'); }); it('Cannot expand the menu when clicking on the input while disabled', () => { let comboBoxRoot; let inputElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - inputElement = comboBoxRoot.find('input'); - inputElement.simulate('click'); - } - finally { - expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + inputElement = comboBoxRoot.find('input'); + inputElement.simulate('click'); + expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); }); it('Cannot expand the menu when clicking on the button while disabled', () => { let comboBoxRoot; let buttonElement; - try { - let wrapper = mount( - ); - comboBoxRoot = wrapper.find('.ms-ComboBox'); - buttonElement = comboBoxRoot.find('button'); - buttonElement.simulate('click'); - } - finally { - expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); - } + let wrapper = mount( + ); + comboBoxRoot = wrapper.find('.ms-ComboBox'); + buttonElement = comboBoxRoot.find('button'); + buttonElement.simulate('click'); + expect(comboBoxRoot.find('.is-opened').length).equals(0, `shouldn't be opened`); }); }); diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 80970bc84a1a8..4d083d6f408c8 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -619,12 +619,7 @@ export class ComboBox extends BaseComponent { if (this.props.onResolveOptions) { // get the options - let newOptions: ISelectableOption[] | PromiseLike = - this.props.onResolveOptions({ ...this.state.currentOptions }); - - // the options are either goingto be an array or a promise - // let newOptionsArray: IComboBoxOption[] = newOptions as IComboBoxOption[]; - // let newOptionsPromiseLike: PromiseLike = newOptions as PromiseLike; + let newOptions = this.props.onResolveOptions({ ...this.state.currentOptions }); // Check to see if the returned value is an array, if it is update the state // If the returned value is not an array then check to see if it's a promise or PromiseLike. If it is then resolve it asynchronously. @@ -724,7 +719,7 @@ export class ComboBox extends BaseComponent { let { onRenderList = this._onRenderList, calloutProps - } = this.props; + } = props; return ( { setInitialFocus={ false } >
- { onRenderList({ ...this.props }, this._onRenderList) } + { onRenderList({ ...props }, this._onRenderList) }
); From 9039ab4e7c4c0bb0ec97b398cbd9e0162c544c8e Mon Sep 17 00:00:00 2001 From: "REDMOND\\jspurlin" Date: Mon, 19 Jun 2017 12:27:09 -0700 Subject: [PATCH 31/31] Fix up a typo and fix up to use consistent syntax on a few lines --- .../src/components/ComboBox/ComboBox.Props.ts | 4 ++-- .../src/components/ComboBox/ComboBox.tsx | 9 ++++----- .../ComboBox/examples/ComboBox.Basic.Example.tsx | 4 ++-- .../components/pickers/AutoFill/BaseAutoFill.Props.ts | 2 +- .../src/components/pickers/AutoFill/BaseAutoFill.tsx | 4 ++-- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts index 2f14bdfdb7ba0..2a56e8860f64b 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.Props.ts @@ -10,9 +10,9 @@ export interface IComboBox { focus(): boolean; } -export interface IComboBoxProps extends ISelectableDroppableTextProps { +export interface IComboBoxProps extends ISelectableDroppableTextProps { /** - * Optional callback to access the IBreadcrumb interface. Use this instead of ref for accessing + * Optional callback to access the IComboBox interface. Use this instead of ref for accessing * the public methods and properties of the component. */ componentRef?: (component: IComboBox) => void; diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx index 4d083d6f408c8..bc7e5e8748c47 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/ComboBox.tsx @@ -20,7 +20,6 @@ import { KeyCodes } from '../../Utilities'; import { ISelectableOption, SelectableOptionMenuItemType } from '../../utilities/selectableOption/SelectableOption.Props'; -import { ISelectableDroppableTextProps } from '../../utilities/selectableOption/SelectableDroppableText.Props'; import * as stylesImport from './ComboBox.scss'; const styles: any = stylesImport; @@ -252,7 +251,7 @@ export class ComboBox extends BaseComponent { spellCheck={ false } defaultVisibleValue={ this._currentVisibleValue } suggestedDisplayValue={ suggestedDisplayValue } - updateValueInWillReceiceProps={ this._onUpdateValueInAutoFillWillReceiceProps } + updateValueInWillReceiveProps={ this._onUpdateValueInAutoFillWillReceiveProps } shouldSelectFullInputValueInComponentDidUpdate={ this._onShouldSelectFullInputValueInAutoFillComponentDidUpdate } /> { * @returns {string} - the updated value to set, if needed */ @autobind - private _onUpdateValueInAutoFillWillReceiceProps(): string { + private _onUpdateValueInAutoFillWillReceiveProps(): string { if (this._comboBox === null || this._comboBox === undefined) { return null; } @@ -715,7 +714,7 @@ export class ComboBox extends BaseComponent { // Render Callout container and pass in list @autobind - private _onRenderContainer(props: ISelectableDroppableTextProps | IComboBoxProps): JSX.Element { + private _onRenderContainer(props: IComboBoxProps): JSX.Element { let { onRenderList = this._onRenderList, calloutProps @@ -743,7 +742,7 @@ export class ComboBox extends BaseComponent { // Render List of items @autobind - private _onRenderList(props: ISelectableDroppableTextProps | IComboBoxProps): JSX.Element { + private _onRenderList(props: IComboBoxProps): JSX.Element { let { onRenderItem = this._onRenderItem } = this.props; diff --git a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx index 59f0ddd4fdd3f..dc221e04c6a93 100644 --- a/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx +++ b/packages/office-ui-fabric-react/src/components/ComboBox/examples/ComboBox.Basic.Example.tsx @@ -211,12 +211,12 @@ export class ComboBoxBasicExample extends React.Component { @autobind private _onChanged(option: ISelectableOption, index: number, value: string) { - if (option != null) { + if (option !== null) { this.setState({ selectedOptionKey: option.key, value: null }); - } else if (index != null && index >= 0 && index < this.state.options.length) { + } else if (index !== null && index >= 0 && index < this.state.options.length) { this.setState({ selectedOptionKey: this.state.options[index].key, value: null diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts index 8ba7d364c87bb..c214933ae77a5 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.Props.ts @@ -72,7 +72,7 @@ export interface IBaseAutoFillProps extends React.HTMLAttributes string; + updateValueInWillReceiveProps?: () => string; /** * Handler for checking if the full value of the input should diff --git a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx index 37149c5314e9e..a05af2ce803e2 100644 --- a/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx +++ b/packages/office-ui-fabric-react/src/components/pickers/AutoFill/BaseAutoFill.tsx @@ -67,8 +67,8 @@ export class BaseAutoFill extends BaseComponent