From 47545a22cb6a268b339ba17eb4bf2c215393f885 Mon Sep 17 00:00:00 2001 From: abenems Date: Thu, 1 Sep 2016 16:13:45 -0700 Subject: [PATCH 01/18] Persona: now renders children within the details, and overflow on details removed (#215) * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * adding an optional control property to the Persona control, to support edit spuser site permissions * resolve comments * resolve comments * Minor changes. --- src/components/Persona/Persona.scss | 1 - src/components/Persona/Persona.tsx | 19 +++++++++++++++---- 2 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/Persona/Persona.scss b/src/components/Persona/Persona.scss index 2c1c9530766efc..c8b18136f3744c 100644 --- a/src/components/Persona/Persona.scss +++ b/src/components/Persona/Persona.scss @@ -148,7 +148,6 @@ $ms-color-initials-darkRed: #b91d47; display: table-cell; padding: 0 12px; vertical-align: middle; - overflow: hidden; } .ms-Persona-primaryText, diff --git a/src/components/Persona/Persona.tsx b/src/components/Persona/Persona.tsx index b4fda7fbb44af0..d52c2106afff70 100644 --- a/src/components/Persona/Persona.tsx +++ b/src/components/Persona/Persona.tsx @@ -23,19 +23,29 @@ export class Persona extends React.Component { }; public render() { - let { className, size, imageUrl, imageInitials, initialsColor, presence, primaryText, secondaryText, tertiaryText, optionalText, hidePersonaDetails } = this.props; + let { + className, + size, + imageUrl, + imageInitials, + initialsColor, + presence, + primaryText, + secondaryText, + tertiaryText, + optionalText, + hidePersonaDetails + } = this.props; return ( -
+
{ size !== PersonaSize.tiny && (
{ imageInitials }
) } - { presence !== PersonaPresence.none &&
} - { !hidePersonaDetails && (
{ primaryText }
@@ -44,6 +54,7 @@ export class Persona extends React.Component { ) : (null) }
{ tertiaryText }
{ optionalText }
+ { this.props.children }
) }
); From bd0ad88c8a7dc08b32804d03b7647f2dee2513d4 Mon Sep 17 00:00:00 2001 From: David Zearing Date: Thu, 1 Sep 2016 17:31:10 -0700 Subject: [PATCH 02/18] Adding @autobind usage to a number of the components. (#233) * Updating GroupedList, Link, MarqueeSelection, Breadcrumb, Toggle, Callout * Updating autobinds for Checkbox, ColorPicker, CommandBar, ContextualMenu. * Updating SelectionZone autobinds. * Adding autobind to Utilities export. * Adding best practice notes. * Updating autobind for FocusZone. * Removing dead code. --- ghdocs/BESTPRACTICES.md | 335 +++++++++++++++++- src/Utilities.ts | 1 + src/components/Breadcrumb/Breadcrumb.tsx | 8 +- src/components/Callout/Callout.tsx | 4 +- src/components/Checkbox/Checkbox.tsx | 4 +- src/components/ColorPicker/ColorPicker.tsx | 15 +- src/components/ColorPicker/ColorRectangle.tsx | 7 +- src/components/ColorPicker/ColorSlider.tsx | 7 +- src/components/CommandBar/CommandBar.tsx | 7 +- .../ContextualMenu/ContextualMenu.tsx | 25 +- src/components/FocusZone/FocusZone.tsx | 15 +- src/components/GroupedList/GroupFooter.tsx | 7 +- src/components/GroupedList/GroupHeader.tsx | 8 +- src/components/GroupedList/GroupedList.tsx | 12 +- src/components/Link/Link.tsx | 11 +- .../MarqueeSelection/MarqueeSelection.tsx | 4 +- src/components/Toggle/Toggle.tsx | 4 +- src/utilities/selection/SelectionZone.tsx | 22 +- 18 files changed, 407 insertions(+), 89 deletions(-) diff --git a/ghdocs/BESTPRACTICES.md b/ghdocs/BESTPRACTICES.md index 130a90ef9c8365..c04dd9aeffdcdc 100644 --- a/ghdocs/BESTPRACTICES.md +++ b/ghdocs/BESTPRACTICES.md @@ -23,6 +23,138 @@ interface IButton { } ``` +## Use @autobind. Avoid ALL binds in templates + +The autobind decorator simplifies making event callbacks "bound" to the instance. It's easy to use: + +```typescript +class Foo { + + @autobind + public _onClick(ev: React.MouseEvent) { + } + +} +``` + +When we use bind in a template, it means that the function is recreated every time, which is an anti-pattern. + +## For gathering refs, avoid string refs, use resolve functions + +Facebook has deprecated the use of string refs. Plus, string refs don't play very well with some scenarios like in Layered content, where the content may be asynchronously rendered. The recommendation is to use functions to resolve refs. These functions must be pre-bound to + +Bad: +```typescript +public render() { + return
+} +``` + +Bad: +```typescript +public render() { + return
this._root = el } /> +} +``` + +Good: +```typescript +private _root: HTMLElement; +private _resolveRoot: (element: HTMLElement) => any; + +constructor() { + this._resolveRoot = (el: HTMLElement) => this._root = el; +} + +public render() { + return
+} +``` + +Note that it's very critical you do NOT inline the functions in render! This causes weird side effects, because the function +is recreated. If you do this, react will call your function with null to clear it, and then render, and then once complete, will +call it again with the element. This creates timing issues where your ref may be null when you didn't expect. + +See example here that illustates refs being re-evaluated: + +http://codepen.io/dzearing/pen/WGZOaR + +## Use unique keys for items in a mapped array, avoid indexes for keys + +```typescript +public render() { + return ( +
+ { this.props.items.map((item, index)=> ( +
+ +
+ )) } +
+ ); +} +``` + +When you need to map data, require that your data have unique ids available. When you map the data to content, use the id as +the item's key. This helps React's dom diffing to understand which elements represent which items. + +When your code uses index as the key, this can cause unexpected side effects. For example, say that each component you map items to +maintains some internal state, like the value in an uncontrolled input box. Removing an item in the array will not reset that state +and react will think the first text box still represents key "0", which is actually now a different item, and thus will not re-render +the component, leaving the old state intact. + +So lesson is, use actual unique ids for items that come from the data, rather than generated by the ux component, and thus require +that your data has ids so that you can use that as a unique identifier for rendering. + +## Avoid nesting content in an array, wrap mapped content in component instead. + +When we do inline mapped entries, we want to inline the content in the map: + +Bad: +```typescript +class Foo extends React.Component { + public render() { + return ( +
+ { items.map(item => ( +
{ item.name }
+ ))} +
+ ); + } +} +``` + +Better: +```typescript +class Foo extends React.Component { + public render() { + return ( +
+ { items.map(item => ( + + )) } +
+ ); + } +} + +class ItemComponent extends React.Component<...> { + let { item } = this.props; + + render() { + return ( +
{ item.name }
+ ); + } + + @autobind + _onClick(ev: MouseEvent) { + + } +} +``` + ## Extend from BaseComponent instead of React.Component in most cases. In the common folder, there exists a BaseComponent class. For simple components, it may be unnecessary to use. @@ -56,7 +188,208 @@ If specific component elements need special classnames injected, add more classN A component's SCSS file should ONLY include files applicable to the component, and should not define styles for any other component. -# Class name guidelines +# Data/state management best practices + +## Things to flat out avoid + +Singletons and globals. Do not share state of a component via a singleton instance object. This is a pure anti-pattern for the following reasons: + +* Singletons have no lifetime and therefore can't initialize/clean up when they aren't used. +* Singletons often paint you into a corner when you want more than one of the same thing on the page. +* They are often difficult to test without polluting states required for other tests. +* The make non-browser scenario reuse really difficult. (Build tooling, server side code.) + +Data and state should always follow the lifetime of the component that owns it. When that component is no longer needed, the state should +be garbage collected, event handlers should be removed, and xhr requires spawned in that context should be cancelled. + +There are cases where everything should share a common instance of a store; Persona presence is a great example. We have solutions that enable the code within a component scope to access shared stores. See store usage section below. + +## Use const values global to a file, rather than as static members + +Don't use public/private statics, use file scope. + +Bad: +```typescript +class Foo { + private static DEFAULT_DELAY = 300; +} +``` + +Good: +```typescript +const DEFAULT_DELAY = 300; + +class Foo { +} +``` + +Note: using file scopes minimizes the name down to 1 character, whereas this.REALLY_LONG_NAME doesn't minify the name. Caveat being +that if you export it, minify won't touch exports. + +## Use private members only for managing state local to a component that should not affect rendering when mutating + +Bad: +```typescript +class Foo extends React.Component { + + constructor(props) { + super(props); + this.state = { + isMounted: false + }; + } + + componentDidMount() { + this.setState({ + isMounted: true + }); + } + + render() { + return
Hello!
+ } +} +``` + +Good: +```typescript +class Foo extends React.Component { + private _isMounted: boolean; + + constructor(props) { + super(props); + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + } + + render() { + return
Hello!
+ } +} +``` + +Basically privates should only be use for tracking this that in no way affect the frequency of render. In some cases, you many +have an object which fires change events yet doesn't mutate, and you want to re-render on change events. In these cases, privates +may be used to store the object reference. + +## Use component state only for managing local state that is private to a component that should trigger re-render on mutation + +Bad: +```typescript +class Foo extends React.Component { + private _fooRocks: boolean; + + render() { + return
{ `Foo ${ this._fooRocks ? 'rocks' : 'rolls' }
; + } + + private _onClick() { + this._fooRocks = !this._fooRocks; + this.forceUpdate(); + } +``` + +Good: +```typescript +class Foo extends React.Component { + constructor() { + super(props); + + this.state = { + fooRocks: false + }; + } + + render() { + return
{ `Foo ${ this.state.fooRocks ? 'rocks' : 'rolls' }
; + } + + private _onClick() { + this.setState({ + fooRocks: !this.state.fooRocks + }); + } +``` + +## Experimental: Use a store for storing shared state within a component heirachy which must be shared across objects + +While React state management is very useful for simple, private state, it becomes unweildy when many things share that state +and you start ending up with many owners of the same state value. In these cases it may drive you towards a flux solution, but +before we jump there, lets call out a few things. + +* Global singletons are code smell, do not use globals or singletons except for very rare cases +* Do not store complex shared state directly in the components, this is also code smell + +Instead, make a store and wire dumb components together the store. + +What is a store? + +Think of a store as a self aware observable; it maintains state, and provides read/write access to that state. When its state +changes, it can emit change events causing components to re-evaluate. + +Here's an example store which tracks selection: + +```typescript +class SimpleSelectionStore extends BaseStore { + private _selectedKey: string; + + public isSelected(key: string) { + return key === this._selectedKey; + } + + public setSelected(key: string) { + if (this._selectedKey !== key) { + this._selectedKey = key; + this.emitChange(); + } + } +} +``` + +While we do not want globals, we do want simplified access to the stores available in a given component scope. + +You can use the StoreHost component to host stores within any given scope: + +```typescript +const stores = { + selection: new SimpleSelectionStore() +}; + +public render() { + return ( + + + + ); +} +``` + +You can write dumb components: + +```typescript +const Item = (props) => ( +
{ `I am item ${ props.name } and I'm ${ props.isSelected ? 'selected' : 'unselected' }
+); +``` + +And you can use the `connect` function to create a ConnectedItem which wraps the dumb component and translates the props +and stores in its context into props for the dumb component: + +```typescript +export ConnectedItem = connect(Item, (stores, props) => ({ + name: props.item.name, + isSelected: stores.selection.isSelected(props.item.key), + onClick: () => stores.selection.setSelected(props.item.key) +})); +``` + +Now in this setup, your state is in one place, it is composable and can have lifetime, and your dumb ui has no understanding +of the contextual stores provided. + +# CSS class name guidelines TODO: include our class name guidelines. diff --git a/src/Utilities.ts b/src/Utilities.ts index 9b00e246d1ff05..e95247a58fc45e 100644 --- a/src/Utilities.ts +++ b/src/Utilities.ts @@ -2,3 +2,4 @@ export * from './common/BaseComponent'; export * from './utilities/css'; export * from './utilities/rtl'; export * from './utilities/hoist'; +export * from './utilities/autobind'; diff --git a/src/components/Breadcrumb/Breadcrumb.tsx b/src/components/Breadcrumb/Breadcrumb.tsx index ffdebb4abd7180..1dbe8a95c841ba 100644 --- a/src/components/Breadcrumb/Breadcrumb.tsx +++ b/src/components/Breadcrumb/Breadcrumb.tsx @@ -7,6 +7,7 @@ import { DirectionalHint } from '../../common/DirectionalHint'; import { getRTL } from '../../utilities/rtl'; import { getId } from '../../utilities/object'; import { css } from '../../utilities/css'; +import { autobind } from '../../utilities/autobind'; import './Breadcrumb.scss'; export interface IBreadcrumbState { @@ -71,7 +72,7 @@ export class Breadcrumb extends BaseComponent
+ onDismiss={ this._onOverflowDismissed } /> ) : (null) }
); } + @autobind private _onOverflowClicked(ev: MouseEvent) { this.setState({ 'isOverflowOpen' : !this.state.isOverflowOpen, @@ -120,6 +122,7 @@ export class Breadcrumb extends BaseComponent { }; this._events = new EventGroup(this); - - this._onLayerDidMount = this._onLayerDidMount.bind(this); } public componentDidUpdate() { @@ -108,6 +107,7 @@ export class Callout extends React.Component { } } + @autobind private _onLayerDidMount() { // This is added so the callout will dismiss when the window is scrolled // but not when something inside the callout is scrolled. diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index 32cacbb05550e3..4e5b327d74cfa4 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -3,9 +3,9 @@ import { ICheckbox, ICheckboxProps } from './Checkbox.Props'; +import { autobind } from '../../utilities/autobind'; import { css } from '../../utilities/css'; import { getId } from '../../utilities/object'; - import './Checkbox.scss'; export class Checkbox extends React.Component implements ICheckbox { @@ -19,7 +19,6 @@ export class Checkbox extends React.Component implements ICh super(props); this._id = getId('checkbox-'); - this._onChange = this._onChange.bind(this); } public render() { @@ -65,6 +64,7 @@ export class Checkbox extends React.Component implements ICh } } + @autobind private _onChange(ev: React.FormEvent) { const { onChange } = this.props; const isChecked = (ev.target as HTMLInputElement).checked; diff --git a/src/components/ColorPicker/ColorPicker.tsx b/src/components/ColorPicker/ColorPicker.tsx index 30fb7886b71d88..a22028bec33282 100644 --- a/src/components/ColorPicker/ColorPicker.tsx +++ b/src/components/ColorPicker/ColorPicker.tsx @@ -3,6 +3,7 @@ import { IColorPickerProps } from './ColorPicker.Props'; import { TextField } from '../../TextField'; import { ColorRectangle } from './ColorRectangle'; import { ColorSlider } from './ColorSlider'; +import { autobind } from '../../utilities/autobind'; import { IColor, MAX_COLOR_HUE, @@ -34,11 +35,6 @@ export class ColorPicker extends React.Component { constructor(props: IColorPickerProps) { super(props); - this._onPickerClick = this._onPickerClick.bind(this); - this._onSVChanged = this._onSVChanged.bind(this); - this._onHChanged = this._onHChanged.bind(this); - this._onAChanged = this._onAChanged.bind(this); - this.state = { color: getColorFromString(props.color) }; @@ -91,20 +87,17 @@ export class ColorPicker extends React.Component { ); } - private _onPickerClick() { - this.setState({ - isOpen: !this.state.isOpen - }); - } - + @autobind private _onSVChanged(s: number, v: number) { this._updateColor(updateSV(this.state.color, s, v)); } + @autobind private _onHChanged(h: number) { this._updateColor(updateH(this.state.color, h)); } + @autobind private _onAChanged(a: number) { this._updateColor(updateA(this.state.color, a)); } diff --git a/src/components/ColorPicker/ColorRectangle.tsx b/src/components/ColorPicker/ColorRectangle.tsx index 64544b1de2940e..3d40c3fad2a6c5 100644 --- a/src/components/ColorPicker/ColorRectangle.tsx +++ b/src/components/ColorPicker/ColorRectangle.tsx @@ -6,6 +6,7 @@ import { getFullColorString } from './colors'; import { assign } from '../../utilities/object'; +import { autobind } from '../../utilities/autobind'; import { EventGroup } from '../../utilities/eventGroup/EventGroup'; let hsv2hex = require('color-functions/lib/hsv2hex'); @@ -42,9 +43,6 @@ export class ColorRectangle extends React.Component { location.href = item.href; } : null, onKeyDown: item.items && item.items.length ? this._onItemKeyDown.bind(this, item) : null, - onMouseEnter: this._onMouseEnter.bind(this, item), + onMouseEnter: this._onItemMouseEnter.bind(this, item), onMouseLeave: this._onMouseLeave, onMouseDown: (ev: any) => this._onItemMouseDown(item, ev), disabled: item.isDisabled, @@ -270,6 +266,7 @@ export class ContextualMenu extends React.Component this._onSubMenuExpand(item, targetElement), 500); + this._enterTimerId = this._async.setTimeout(() => this._onItemSubMenuExpand(item, targetElement), 500); } else { this._enterTimerId = this._async.setTimeout(() => this._onSubMenuDismiss(ev), 500); } } } + @autobind private _onMouseLeave(ev: React.MouseEvent) { this._async.clearTimeout(this._enterTimerId); } @@ -317,7 +315,7 @@ export class ContextualMenu extends React.Component implements I }; this._events = new EventGroup(this); - - this._onKeyDown = this._onKeyDown.bind(this); - this._onFocus = this._onFocus.bind(this); - this._onMouseDown = this._onMouseDown.bind(this); } public componentDidMount() { @@ -100,7 +97,8 @@ export class FocusZone extends React.Component implements I aria-labelledby={ ariaLabelledBy } onMouseDownCapture={ this._onMouseDown } onKeyDown={ this._onKeyDown } - onFocus={ this._onFocus } > + onFocus={ this._onFocus } + > { this.props.children }
); @@ -157,6 +155,7 @@ export class FocusZone extends React.Component implements I return false; } + @autobind private _onFocus(ev: React.FocusEvent) { let { onActiveElementChanged } = this.props; @@ -182,12 +181,13 @@ export class FocusZone extends React.Component implements I /** * Handle global tab presses so that we can patch tabindexes on the fly. */ - private _onKeyDownCapture(ev: React.KeyboardEvent) { + private _onKeyDownCapture(ev: KeyboardEvent) { if (ev.which === KeyCodes.tab) { this._updateTabIndexes(); } } + @autobind private _onMouseDown(ev: React.MouseEvent) { const { disabled } = this.props; @@ -218,6 +218,7 @@ export class FocusZone extends React.Component implements I /** * Handle the keystrokes. */ + @autobind private _onKeyDown(ev: React.KeyboardEvent) { const { direction, disabled, isInnerZoneKeystroke } = this.props; @@ -432,7 +433,7 @@ export class FocusZone extends React.Component implements I let distance = -1; if ((targetTop === -1 && targetRect.bottom <= activeRect.top) || - (targetRect.top === targetTop)) { + (targetRect.top === targetTop)) { targetTop = targetRect.top; if (leftAlignment >= targetRect.left && leftAlignment <= (targetRect.left + targetRect.width)) { distance = 0; diff --git a/src/components/GroupedList/GroupFooter.tsx b/src/components/GroupedList/GroupFooter.tsx index 14691145fee7d4..15db445c998140 100644 --- a/src/components/GroupedList/GroupFooter.tsx +++ b/src/components/GroupedList/GroupFooter.tsx @@ -5,6 +5,7 @@ import { IGroup, } from './GroupedList.Props'; import { GroupSpacer } from './GroupSpacer'; +import { autobind } from '../../utilities/autobind'; import './GroupFooter.scss'; export interface IGroupFooter { @@ -15,11 +16,6 @@ export interface IGroupFooter { } export class GroupFooter extends React.Component { - constructor(props: IGroupFooter) { - super(props); - - this._onToggleSummarize = this._onToggleSummarize.bind(this); - } public render() { let { group, groupLevel, footerProps } = this.props; @@ -36,6 +32,7 @@ export class GroupFooter extends React.Component { ); } + @autobind private _onToggleSummarize() { let { group, footerProps } = this.props; let onToggleSummarize = footerProps && footerProps.onToggleSummarize; diff --git a/src/components/GroupedList/GroupHeader.tsx b/src/components/GroupedList/GroupHeader.tsx index 775b0f62417302..1cadcd3f24a0f9 100644 --- a/src/components/GroupedList/GroupHeader.tsx +++ b/src/components/GroupedList/GroupHeader.tsx @@ -9,6 +9,7 @@ import { GroupSpacer } from './GroupSpacer'; import { Spinner } from '../../Spinner'; import { FocusZone, FocusZoneDirection } from '../../FocusZone'; import { css } from '../../utilities/css'; +import { autobind } from '../../utilities/autobind'; import { IViewport } from '../../utilities/decorators/withViewport'; import './GroupHeader.scss'; @@ -30,10 +31,6 @@ export class GroupHeader extends React.Component { - constructor(props: ILinkProps) { - super(props); - - this._onClick = this._onClick.bind(this); - this._popupWindow = this._popupWindow.bind(this); - } - public render() { let { children, className, href } = this.props; @@ -42,6 +36,7 @@ export class Link extends React.Component { )); } + @autobind private _onClick(ev: React.MouseEvent) { let { popupWindowProps, onClick } = this.props; diff --git a/src/components/MarqueeSelection/MarqueeSelection.tsx b/src/components/MarqueeSelection/MarqueeSelection.tsx index f38904fe560939..8bcf94e40faa12 100644 --- a/src/components/MarqueeSelection/MarqueeSelection.tsx +++ b/src/components/MarqueeSelection/MarqueeSelection.tsx @@ -7,6 +7,7 @@ import { IRectangle } from '../../common/IRectangle'; import { css } from '../../utilities/css'; import { findScrollableParent } from '../../utilities/scrollUtilities'; import { getDistanceBetweenPoints } from '../../utilities/math'; +import { autobind } from '../../utilities/autobind'; import './MarqueeSelection.scss'; @@ -52,8 +53,6 @@ export class MarqueeSelection extends BaseComponent { }; this._id = getId('Toggle'); - this._onClick = this._onClick.bind(this); } /** @@ -91,6 +92,7 @@ export class Toggle extends React.Component { } } + @autobind private _onClick() { let { checked, onChanged } = this.props; let { isChecked } = this.state; diff --git a/src/utilities/selection/SelectionZone.tsx b/src/utilities/selection/SelectionZone.tsx index 5d94d42c8b955a..c505e1e76931c5 100644 --- a/src/utilities/selection/SelectionZone.tsx +++ b/src/utilities/selection/SelectionZone.tsx @@ -1,5 +1,6 @@ import * as React from 'react'; import { BaseComponent } from '../../common/BaseComponent'; +import { autobind } from '../../utilities/autobind'; import { SelectionLayout } from './SelectionLayout'; import { KeyCodes } from '../KeyCodes'; import { @@ -55,21 +56,6 @@ export class SelectionZone extends BaseComponent { private _isMetaPressed: boolean; private _shouldIgnoreFocus: boolean; - constructor(props: ISelectionZoneProps) { - super(props); - - // Specifically for the click methods, we will want to use React eventing to allow - // React and non React events to stop propagation and avoid the default SelectionZone - // behaviors (like executing onInvoked.) - this._onFocus = this._onFocus.bind(this); - this._onKeyDown = this._onKeyDown.bind(this); - this._onClick = this._onClick.bind(this); - this._onMouseDown = this._onMouseDown.bind(this); - this._onDoubleClick = this._onDoubleClick.bind(this); - this._updateModifiers = this._updateModifiers.bind(this); - this.ignoreNextFocus = this.ignoreNextFocus.bind(this); - } - public componentDidMount() { // Track the latest modifier keys globally. this._events.on(window, 'keydown keyup', this._updateModifiers); @@ -98,6 +84,7 @@ export class SelectionZone extends BaseComponent { * been called on an element, so we need a flag to store the idea that we will bypass the "next" * focus event that occurs. This method does that. */ + @autobind public ignoreNextFocus() { this._shouldIgnoreFocus = true; } @@ -107,6 +94,7 @@ export class SelectionZone extends BaseComponent { * as long as the focus did not originate from a mouse down/touch event. For those cases, we handle them * specially. */ + @autobind private _onFocus(ev: React.FocusEvent) { let target = ev.target as HTMLElement; let { selection, selectionMode } = this.props; @@ -132,6 +120,7 @@ export class SelectionZone extends BaseComponent { } } + @autobind private _onMouseDown(ev: React.MouseEvent) { this._updateModifiers(ev); @@ -156,6 +145,7 @@ export class SelectionZone extends BaseComponent { } } + @autobind private _onClick(ev: React.MouseEvent) { this._updateModifiers(ev); @@ -193,6 +183,7 @@ export class SelectionZone extends BaseComponent { * In multi selection, if you double click within an item's root (but not within the invoke element or input elements), * we should execute the invoke handler. */ + @autobind private _onDoubleClick(ev: React.MouseEvent) { let target = ev.target as HTMLElement; let { selectionMode, onItemInvoked } = this.props; @@ -218,6 +209,7 @@ export class SelectionZone extends BaseComponent { } } + @autobind private _onKeyDown(ev: React.KeyboardEvent) { this._updateModifiers(ev); From 70e72d45cbe1f78436803e13ee3034b0f2eb6985 Mon Sep 17 00:00:00 2001 From: Alex Bettadapur Date: Thu, 1 Sep 2016 17:32:06 -0700 Subject: [PATCH 03/18] DatePicker: Change text input validation to only call onSelectDate when text input is allowed (#230) * Fixed validation when allowTextInput is false * Fixed formatting --- src/components/DatePicker/DatePicker.tsx | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/DatePicker/DatePicker.tsx b/src/components/DatePicker/DatePicker.tsx index 273f950c0a2487..000b7b52773b64 100644 --- a/src/components/DatePicker/DatePicker.tsx +++ b/src/components/DatePicker/DatePicker.tsx @@ -356,8 +356,8 @@ export class DatePicker extends React.Component Date: Thu, 1 Sep 2016 17:35:48 -0700 Subject: [PATCH 04/18] Update package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6d1fe798642123..35028c922b31f4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "office-ui-fabric-react", - "version": "0.47.0", + "version": "0.47.1", "description": "Reusable React components for building experiences for Office 365.", "main": "lib/index.js", "typings": "lib/index.d.ts", From b64ca0c43f2592c4aeb1ca4464452201648cda78 Mon Sep 17 00:00:00 2001 From: Srikanth Deshpande Date: Thu, 1 Sep 2016 18:09:20 -0700 Subject: [PATCH 05/18] Dropdown: isDisabled is respected when the value changes. (#237) --- src/components/Dropdown/Dropdown.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/components/Dropdown/Dropdown.tsx b/src/components/Dropdown/Dropdown.tsx index 5c6922de2bfd6d..a8cdcfea8f83fc 100644 --- a/src/components/Dropdown/Dropdown.tsx +++ b/src/components/Dropdown/Dropdown.tsx @@ -50,7 +50,8 @@ export class Dropdown extends React.Component { public componentWillReceiveProps(newProps: IDropdownProps) { this.setState({ - selectedIndex: this._getSelectedIndex(newProps.options, newProps.selectedKey) + selectedIndex: this._getSelectedIndex(newProps.options, newProps.selectedKey), + isDisabled: newProps.isDisabled }); } From 603d504c450655b8a5b0a22106be6fce089fc65a Mon Sep 17 00:00:00 2001 From: David Zearing Date: Thu, 1 Sep 2016 18:09:53 -0700 Subject: [PATCH 06/18] v0.47.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 35028c922b31f4..7468b49b618e31 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "office-ui-fabric-react", - "version": "0.47.1", + "version": "0.47.2", "description": "Reusable React components for building experiences for Office 365.", "main": "lib/index.js", "typings": "lib/index.d.ts", From 697e947b4df1cf3883e3b81f18e3b7b3512bd86e Mon Sep 17 00:00:00 2001 From: Aniket Handa Date: Fri, 2 Sep 2016 13:40:18 -0700 Subject: [PATCH 07/18] Using react events --- src/components/Checkbox/Checkbox.tsx | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/components/Checkbox/Checkbox.tsx b/src/components/Checkbox/Checkbox.tsx index 5db76129637643..13177e0da16fac 100644 --- a/src/components/Checkbox/Checkbox.tsx +++ b/src/components/Checkbox/Checkbox.tsx @@ -22,16 +22,8 @@ export class Checkbox extends React.Component implements ICh this._id = getId('checkbox-'); this._onChange = this._onChange.bind(this); - } - - public componentDidMount() { - this._checkBoxInput.addEventListener('focus', this._onFocus.bind(this), false); - this._checkBoxInput.addEventListener('blur', this._onBlur.bind(this), false); - } - - public componentWillUnmount() { - this._checkBoxInput.removeEventListener('focus', this._onFocus.bind(this)); - this._checkBoxInput.removeEventListener('blur', this._onBlur.bind(this)); + this._onFocus = this._onFocus.bind(this); + this._onBlur = this._onBlur.bind(this); } public render() { @@ -60,6 +52,8 @@ export class Checkbox extends React.Component implements ICh className='ms-Checkbox-input' type='checkbox' onChange={ this._onChange } + onFocus= { this._onFocus } + onBlur= { this._onBlur } aria-checked={ checked } />