office-ui-fabric-react
Version:
Reusable React components for building experiences for Office 365.
463 lines (461 loc) • 26.1 kB
JavaScript
define(["require", "exports", "tslib", "react", "../../Utilities", "./SwatchColorPicker.Props", "../../ContextualMenu", "../../utilities/color/colors", "../../utilities/grid/Grid", "../../Button", "../../Callout", "../../FocusZone", "./SwatchColorPicker.scss"], function (require, exports, tslib_1, React, Utilities_1, SwatchColorPicker_Props_1, ContextualMenu_1, colors_1, Grid_1, Button_1, Callout_1, FocusZone_1, stylesImport) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var styles = stylesImport;
var SwatchColorPickerOption = function (props) {
var item = props.item, id = props.id, posInSet = props.posInSet, setSize = props.setSize, className = props.className, role = props.role, selectedIndex = props.selectedIndex, disabled = props.disabled, cellShape = props.cellShape, onClick = props.onClick, onHover = props.onHover, onFocus = props.onFocus;
return (React.createElement(Button_1.CommandButton, tslib_1.__assign({}, item.menuItemButtonProps, { id: id + '-item' + item.index, "data-index": item.index, "data-is-focusable": true, "aria-posinset": posInSet && posInSet, "aria-setsize": setSize && setSize, disabled: disabled, className: Utilities_1.css(className, (_a = {},
_a['is-selected ' + styles.cellIsSelected] = (selectedIndex && selectedIndex === item.index),
_a['is-disabled ' + styles.disabled] = disabled,
_a)), onClick: _onClick, onMouseEnter: _onMouseEnter, onMouseLeave: _onMouseLeave, onFocus: _onFocus, role: role, "aria-selected": selectedIndex && (selectedIndex === item.index).toString(), ariaLabel: item.label && item.label, title: item.label && item.label }), _onRenderOption()));
function _onClick() {
if (onClick && !disabled) {
onClick(item);
}
}
function _onMouseEnter() {
if (onHover && !disabled) {
onHover(item.id, item.color);
}
}
function _onMouseLeave() {
if (onHover && !disabled) {
onHover();
}
}
function _onFocus() {
if (onFocus && !disabled) {
onFocus(item.id, item.color);
}
}
/**
* Render the core of an cell or menu item
* @returns {JSX.Element} - Element representing the core of the item
*/
function _onRenderOption() {
// Menu items just need their label text
if (item.type !== SwatchColorPicker_Props_1.SwatchColorPickerItemType.Cell) {
return React.createElement("span", { className: styles.menuItem }, item.label);
}
// Build an SVG for the cell with the given shape and color properties
return (React.createElement("svg", { className: Utilities_1.css(styles.svg, cellShape, cellShape === 'circle' ? styles.circle : ''), viewBox: '0 0 20 20', fill: colors_1.getColorFromString(item.color).str }, cellShape === 'circle' ?
React.createElement("circle", { cx: '50%', cy: '50%', r: '50%' }) :
React.createElement("rect", { width: '100%', height: '100%' })));
}
var _a;
};
var SwatchColorPicker = (function (_super) {
tslib_1.__extends(SwatchColorPicker, _super);
function SwatchColorPicker(props) {
var _this = _super.call(this, props) || this;
_this._id = props.id || Utilities_1.getId('swatchColorPicker');
var selectedIndex;
if (props.selectedId) {
selectedIndex = _this._getSelectedIndex(props.swatchColorPickerItems, props.selectedId);
}
_this.state = {
selectedIndex: selectedIndex,
expanded: false
};
return _this;
}
SwatchColorPicker.prototype.componentWillReceiveProps = function (newProps) {
var newSelectedIndex;
if (newProps.selectedId) {
newSelectedIndex = this._getSelectedIndex(newProps.swatchColorPickerItems, newProps.selectedId);
}
if (newSelectedIndex !== undefined &&
newSelectedIndex !== this.state.selectedIndex) {
this.setState({
selectedIndex: newSelectedIndex
});
}
};
SwatchColorPicker.prototype.render = function () {
var expanded = this.state.expanded;
var _a = this.props, menuButtonProps = _a.menuButtonProps, disabled = _a.disabled, swatchColorPickerItems = _a.swatchColorPickerItems, columnCount = _a.columnCount;
// If we got button props, put the swatch color picker behind a button, otherwise
// render all of the items
var colorToSet = this._getSelectedColorToSet();
var renderElement = menuButtonProps !== undefined ?
React.createElement(SwatchColorPickerMenuButton, tslib_1.__assign({}, this.props, { color: colorToSet, onClick: this._onButtonClick, expanded: !!this.state.expanded, onRenderContainer: this._onRenderContainer, menuIconProps: menuButtonProps.menuIconProps ?
menuButtonProps.menuIconProps :
{ iconName: 'chevronDown' } })) :
React.createElement(SwatchColorPickerBody, tslib_1.__assign({}, this.props, { onRenderItems: this._onRenderItems }));
return (React.createElement("div", { ref: this._resolveRef('_buttonWrapper'), className: Utilities_1.css('ms-swatchColorPickerWrapper', styles.wrapper) }, renderElement));
};
/**
* Get the selected item's index
* @param items - The items to search
* @param selectedId - The selected item's id to find
* @returns {number} - The index of the selected item's id, -1 if there was no match
*/
SwatchColorPicker.prototype._getSelectedIndex = function (items, selectedId) {
var selectedIndex = Utilities_1.findIndex(items, (function (item) { return (item.id === selectedId); }));
return selectedIndex >= 0 ? selectedIndex : undefined;
};
/**
* Gets the color for the selected index
* @returns {string} - The color for the selected index,
* or undefined if: we are not updating the button icon with color,
* there is not a valid selected index, or if we do not have a valid color
*/
SwatchColorPicker.prototype._getSelectedColorToSet = function () {
var selectedIndex = this.state.selectedIndex;
var _a = this.props, swatchColorPickerItems = _a.swatchColorPickerItems, setSelectedColorForIcon = _a.setSelectedColorForIcon;
// Do we need to update the button with the selected
// item's color? If so, attempt to grab the color
if (setSelectedColorForIcon && selectedIndex !== undefined) {
var swatchColorPickerItem = swatchColorPickerItems[selectedIndex];
if (swatchColorPickerItem.type === SwatchColorPicker_Props_1.SwatchColorPickerItemType.Cell) {
return swatchColorPickerItem.color;
}
}
return undefined;
};
/**
* Render all the items
* @param items - The swatch color picker items
* @returns {JSX.Element[]} - An array of all the items in the swatch color picker
*/
SwatchColorPicker.prototype._onRenderItems = function (items) {
// This holds all of the element for the items
var elements = [];
// The number of cells that were processed in the previous iteration of loop.
// This will be used to increase the index to the item after the last processed
// item
var numOfItemsInChunk = -1;
// If we have cell items and we are in a menu, get all the
// first executable items per chunk. Note, in this
// context each menuItem is in its own "chunk", only grouped
// cells are processed as a chunk. This helps with being able
// to determine the correct aria-posinset and aria-setsize values
var firstExecutableItemsPerChunk = (this.props.menuButtonProps ?
this._getFirstExecutableItemsPerChunk() : undefined);
// Did we find any executable items? (e.g. should be calculate the set information)
var shouldGetSetInfo = (firstExecutableItemsPerChunk && firstExecutableItemsPerChunk.length > 0);
var setSize = shouldGetSetInfo ? firstExecutableItemsPerChunk.length : undefined;
// If any menuItem has an icon, all menu items need to be positined correctly so they align
var shouldAccountForIcon = (firstExecutableItemsPerChunk && Utilities_1.findIndex(firstExecutableItemsPerChunk, function (executableItem) { return (executableItem.type === SwatchColorPicker_Props_1.SwatchColorPickerItemType.MenuItem && executableItem.menuItemButtonProps); }));
// Loop across the items processing them depending on their item type
var index = 0;
while (index < items.length) {
var item = items[index];
var posInSet = shouldGetSetInfo ? this._getPositionInSet(firstExecutableItemsPerChunk, item.id) : undefined;
switch (item.type) {
case SwatchColorPicker_Props_1.SwatchColorPickerItemType.Divider:
elements.push(this._renderSeparator(item));
break;
case SwatchColorPicker_Props_1.SwatchColorPickerItemType.Header:
elements.push(this._renderHeader(item));
break;
case SwatchColorPicker_Props_1.SwatchColorPickerItemType.Cell:
// build all the cells in the chunk this cell
// exists within (this will process all of the
// consecutive cells until the next non-cell type
// is incountered (or if we reach the end of the items))
var chunkItems = this._getNextChunkOfCellItems(items.slice(index));
// Update the number of items in chunk
numOfItemsInChunk = chunkItems.length > 0 ? chunkItems.length : -1;
// Add the result to the array
elements.push(this._renderNextChunkOfCellItems(chunkItems, posInSet, setSize));
break;
default:
// Does at least one of the menu items have an icon?
if (shouldAccountForIcon) {
// Make sure the width styling is the same by applying a consistent className
if (item.menuItemButtonProps) {
item.menuItemButtonProps = tslib_1.__assign({}, item.menuItemButtonProps, { iconProps: tslib_1.__assign({}, item.menuItemButtonProps.iconProps, { className: styles.icon }) });
}
else {
// This menu item didn't have an icon so add a "spacer" icon to make the text
// content align. This aligns with how ContextualMenu achieves this alignmet
item.menuItemButtonProps = tslib_1.__assign({}, item.menuItemButtonProps, { iconProps: { iconName: 'customIcon', className: styles.icon } });
}
}
elements.push(this._renderOption(item, posInSet, setSize));
}
// Increase the index by the number of items in the just processed chunk,
// otherwise just increment the index
index += numOfItemsInChunk > 0 ? numOfItemsInChunk : 1;
numOfItemsInChunk = -1;
}
return elements;
};
/**
* Renders the next consecutive chunk of cells
* @param items - The items to process
* @param posInSet - Optional, the position in the set for this chunk
* @param setSize - Optional, the size of the total set
* @returns {JSX.Element} - The grid that represents the chunk
*/
SwatchColorPicker.prototype._renderNextChunkOfCellItems = function (items, posInSet, setSize) {
return (React.createElement(Grid_1.Grid, { key: this._id + items[0].id + '-grid', items: items, columnCount: this.props.columnCount, onRenderItem: this._renderOption, positionInSet: posInSet, setSize: setSize }));
};
/**
* Get the position in set for the given id
* @param firstExecutableItemsPerChunk - The list of fist executable items per chunk
* @param itemId - The id of item to find the index of
* @returns {number} - The position in the set
*/
SwatchColorPicker.prototype._getPositionInSet = function (firstExecutableItemsPerChunk, itemId) {
// Find the index of the given id in the list of first executable items per chunk
var index = Utilities_1.findIndex(firstExecutableItemsPerChunk, function (executableItem) { return (executableItem.id === itemId); });
return index < 0 ? undefined : index + 1;
};
/**
* Gets the next chunk of consecutive cells starting a index zero.
* Note, index zero should be of type cell
* @param items - The list of items to process where index zero is the start of the chunk
* @returns {ISwatchColorPickerItemProps[]} - the array of consecutive cells starting at index zero
* (and continuing to the first non-cell type or the end of the items)
*/
SwatchColorPicker.prototype._getNextChunkOfCellItems = function (items) {
var nextIndextAfterChunk = Utilities_1.findIndex(items, (function (item) { return item.type !== SwatchColorPicker_Props_1.SwatchColorPickerItemType.Cell; }));
// If we didn't find a non-cell item, we need to handle everything that is left
if (nextIndextAfterChunk < 0) {
return items;
}
// If we get here we found our chunk boundry
return items.slice(0 /* start */, nextIndextAfterChunk);
};
/**
* Get only the executable items (cells and menuItems)
* @returns {ISwatchColorPickerItemProps[]} - an array of the executable items
*/
SwatchColorPicker.prototype._getFirstExecutableItemsPerChunk = function () {
// Make sure every item has an index, then filter
// the results so that you only get the executable items,
// finally filter those items down to just the items that are
// either the start of a chunk or an menu item
return (this.props.swatchColorPickerItems.map(function (item, index) { return tslib_1.__assign({}, item, { index: index }); })
.filter(function (item) { return (item.type === SwatchColorPicker_Props_1.SwatchColorPickerItemType.Cell || item.type === SwatchColorPicker_Props_1.SwatchColorPickerItemType.MenuItem); })
.filter(function (item, filteredIndex, items) {
return (filteredIndex === 0 ||
item.type === SwatchColorPicker_Props_1.SwatchColorPickerItemType.MenuItem ||
(item.index - items[filteredIndex - 1].index !== 1));
}));
};
/**
* Render the separator
* @param item - The divider item to get the data to render from
* @returns {JSX.Element} - Element that represents the separator
*/
SwatchColorPicker.prototype._renderSeparator = function (item) {
return this._renderHeaderOrDivider(item, 'separator', styles.divider);
};
/**
* Render the header
* @param item - The header item to get the data to render from
* @returns {JSX.Element} - Element that represents the header
*/
SwatchColorPicker.prototype._renderHeader = function (item) {
return this._renderHeaderOrDivider(item, 'heading', styles.header);
};
/**
* Handle the rendering of the header/divider
* @param item - The item to get the data to render from
* @param role - The role of the element
* @param className - the className to use
* @returns {JSX.Element} - Element that represents the header
*/
SwatchColorPicker.prototype._renderHeaderOrDivider = function (item, role, className) {
return React.createElement("div", { role: role, key: item.id, className: className }, item.label && item.label);
};
/**
* Render a cell or menu item
* @param item - The item to render
* @param posInSet - Optional, the position in the set of the item
* @param setSize - Optional, the total set size this item is in
* @returns {JSX.Element} - Element representing the item
*/
SwatchColorPicker.prototype._renderOption = function (item, posInSet, setSize) {
var id = this._id;
var isCell = item.type === SwatchColorPicker_Props_1.SwatchColorPickerItemType.Cell;
var optionProps;
if (isCell) {
optionProps = {
className: styles.cell,
onClick: this._onCellClick,
onHover: this.props.onCellHovered,
onFocus: this.props.onCellFocused,
role: 'gridcell',
selectedIndex: this.state.selectedIndex
};
}
else {
optionProps = {
posInSet: posInSet && posInSet,
setSize: setSize && setSize,
className: styles.item,
onClick: this._onMenuItemClick,
onFocus: this._clearFocusColorOnMenuItem,
role: this.props.menuButtonProps ? 'menuitem' : 'button'
};
}
return (React.createElement(SwatchColorPickerOption, tslib_1.__assign({ item: item, id: this._id, key: id + item.id, disabled: this.props.disabled || item.disabled, cellShape: this.props.cellShape }, optionProps)));
};
/**
* Handle the click on a cell
* @param item - The cell that the click was fired against
*/
SwatchColorPicker.prototype._onCellClick = function (item) {
if (this.props.disabled || item.disabled) {
return;
}
var index = item.index;
// If we have a valid index and it is not already
// selected, select it
if (index >= 0 && index !== this.state.selectedIndex) {
if (this.props.onColorChanged) {
this.props.onColorChanged(item.id, item.color);
}
this.setState({
selectedIndex: index,
expanded: false
});
}
else if (index === this.state.selectedIndex) {
// The index that got the click was already selected,
// clear the selection
this._clearColors([this.props.onColorChanged, this.props.onCellHovered, this.props.onCellFocused]);
this.setState({
selectedIndex: undefined,
expanded: false
});
}
};
/**
* Handle the click on a menu item
* @param item - The menu item that the click was fired against
*/
SwatchColorPicker.prototype._onMenuItemClick = function (item) {
if (this.props.disabled || item.disabled) {
return;
}
if (this.props.onMenuItemClick) {
this.props.onMenuItemClick(item);
}
// Make sure to clear any hover/focus colors
this._clearColors([this.props.onCellHovered, this.props.onCellFocused]);
this.setState({
expanded: false
});
};
/**
* Clear the colors by calling the given callbacks
* @param callbacks - The callbacks to handle the clear operation
*/
SwatchColorPicker.prototype._clearColors = function (callbacks) {
callbacks.forEach(function (callback) {
if (callback) {
callback();
}
});
};
/**
* Clear the focus color
* @param id - The id of the item
* @param color - The color for the item
*/
SwatchColorPicker.prototype._clearFocusColorOnMenuItem = function (id, color) {
this._clearColors([this.props.onCellFocused]);
};
/**
* onClick Handler for the button
*/
SwatchColorPicker.prototype._onButtonClick = function () {
if (this.props.disabled) {
return;
}
if (this.state.expanded) {
this._onDismiss();
}
this.setState({
expanded: this.props.disabled ? false : !this.state.expanded
});
};
/**
* Render the menu (callout) for when the swatch color picker
* is behind a button and is expanded
*/
SwatchColorPicker.prototype._onRenderContainer = function () {
return (React.createElement(Callout_1.Callout, { isBeakVisible: false, gapSpace: 0, doNotLayer: false, role: 'menu', directionalHint: ContextualMenu_1.DirectionalHint.bottomLeftEdge, className: Utilities_1.css('ms-swatchColorPickerMenu', styles.swatchColorPickerContainer), targetElement: this._buttonWrapper, onDismiss: this._onDismiss, setInitialFocus: true },
React.createElement(SwatchColorPickerBody, tslib_1.__assign({}, this.props, { onRenderItems: this._onRenderItems }))));
};
/**
* Handle dismissing the menu
*/
SwatchColorPicker.prototype._onDismiss = function () {
this._clearColors([this.props.onCellHovered, this.props.onCellFocused]);
this.setState({
expanded: false
});
};
return SwatchColorPicker;
}(Utilities_1.BaseComponent));
SwatchColorPicker.defaultProps = {
cellShape: 'circle',
updateButtonIconWithColor: false,
disabled: false
};
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_onRenderItems", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_renderOption", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_onCellClick", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_onMenuItemClick", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_clearFocusColorOnMenuItem", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_onButtonClick", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_onRenderContainer", null);
tslib_1.__decorate([
Utilities_1.autobind
], SwatchColorPicker.prototype, "_onDismiss", null);
exports.SwatchColorPicker = SwatchColorPicker;
var SwatchColorPickerMenuButton = (function (_super) {
tslib_1.__extends(SwatchColorPickerMenuButton, _super);
function SwatchColorPickerMenuButton() {
return _super !== null && _super.apply(this, arguments) || this;
}
SwatchColorPickerMenuButton.prototype.render = function () {
var _a = this.props, color = _a.color, expanded = _a.expanded, disabled = _a.disabled, onClick = _a.onClick, onRenderContainer = _a.onRenderContainer, menuIconProps = _a.menuIconProps, menuButtonProps = _a.menuButtonProps;
return (React.createElement("div", null,
React.createElement(Button_1.DefaultButton, tslib_1.__assign({}, menuButtonProps, { style: { color: color && color }, className: Utilities_1.css('ms-swatchColorPickerButton', {
'is-expanded': expanded
}), onClick: onClick, "aria-haspopup": true, "aria-expanded": !disabled && expanded, disabled: disabled, menuIconProps: menuIconProps ?
menuIconProps :
{ iconName: 'chevronDown' } })),
(!disabled && expanded) &&
onRenderContainer()));
};
return SwatchColorPickerMenuButton;
}(Utilities_1.BaseComponent));
SwatchColorPickerMenuButton.defaultProps = {
expanded: false,
disabled: false
};
var SwatchColorPickerBody = (function (_super) {
tslib_1.__extends(SwatchColorPickerBody, _super);
function SwatchColorPickerBody() {
return _super !== null && _super.apply(this, arguments) || this;
}
SwatchColorPickerBody.prototype.render = function () {
var _a = this.props, swatchColorPickerItems = _a.swatchColorPickerItems, columnCount = _a.columnCount, onRenderItems = _a.onRenderItems;
return (React.createElement(FocusZone_1.FocusZone, { isCircularNavigation: true, className: Utilities_1.css('ms-swatchColorPickerBodyContainer', styles.swatchColorPickerContainer) }, onRenderItems(swatchColorPickerItems.map(function (item, index) { return tslib_1.__assign({}, item, { index: index }); }))));
};
return SwatchColorPickerBody;
}(Utilities_1.BaseComponent));
});
//# sourceMappingURL=SwatchColorPicker.js.map