Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
7dc1773
Column Reorder changes
laxmikankanala May 8, 2018
a9e892d
Drop hint related changes
laxmikankanala May 13, 2018
fdb8bc9
Change description
laxmikankanala May 13, 2018
39862b2
Updated the function signatures based on the latest commit
laxmikankanala May 14, 2018
7f614bc
Resolved Marquee selection issue
laxmikankanala May 15, 2018
7bae381
dragStart changes
laxmikankanala May 15, 2018
c42b104
Removing drag drop events object related changes in DetailsHeader
laxmikankanala May 17, 2018
97db355
Reverting dragdrop helper changes related to dragenter event
laxmikankanala May 21, 2018
71469c8
Changing minimum pixel value
laxmikankanala May 21, 2018
ec63431
Added column reorder options in drag drop example
laxmikankanala May 25, 2018
a32a4b2
Updated DetailsHeader tests and snapshots with columnReorderOptions
laxmikankanala May 28, 2018
42e1032
UX changes for dragdrop
May 31, 2018
cea2518
Added frozen columns from end
laxmikankanala Jun 1, 2018
7c03c94
Performance improvements, drophint css changes
laxmikankanala Jun 12, 2018
3da3d6c
Fixed row items drag issue
laxmikankanala Jun 14, 2018
a752d99
Fixed row drag issue
laxmikankanala Jun 15, 2018
5d35a99
Updating Dragdrop example snapshot
laxmikankanala Jun 18, 2018
fdb5c2a
Perf fixes and PR comments
laxmikankanala Jun 20, 2018
41f53ce
Resolving PR comments
laxmikankanala Jun 21, 2018
fc08ea8
Resolving merge issues
laxmikankanala Jul 10, 2018
d1d1f74
Minor CSS change for column gripper Icon, when hover
laxmikankanala Jun 22, 2018
0e78c6b
Target index bug fix for column reorder
laxmikankanala Jul 3, 2018
c6ccde6
Changes to columnIndex
laxmikankanala Jul 10, 2018
cf6d7ec
Adding changefile
laxmikankanala Jul 10, 2018
c34100e
Merge branch '5.0' into laxmi/backport
ThomasMichon Jul 10, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"changes": [
{
"packageName": "office-ui-fabric-react",
"comment": "Added column reorder with DragAndDrop support in Details List. As part of this feature, a new component DetailsColumn has been added inside the header, for each column. An optional new prop(ColumnReorderOptions) has been added to DetailsList to handle the column reorder. Have added column level drag subscriptions to be able to drag the columns, and added one header level subscription, to handle the drops. All the drag drop events are being handled at header level",
"type": "minor"
}
],
"packageName": "office-ui-fabric-react",
"email": "laxmika@microsoft.com"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
import * as React from 'react';
import * as stylesImport from './DetailsHeader.scss';
const styles: any = stylesImport;
import { Icon } from '../../Icon';
import { BaseComponent, css, IRenderFunction, createRef, IDisposable } from '../../Utilities';
import { IColumn, ColumnActionsMode } from './DetailsList.types';

import { ITooltipHostProps } from '../../Tooltip';
import { IDragDropHelper, IDragDropOptions } from './../../utilities/dragdrop/interfaces';

const INNER_PADDING = 16; // Account for padding around the cell.
const ISPADDED_WIDTH = 24;
const MOUSEDOWN_PRIMARY_BUTTON = 0; // for mouse down event we are using ev.button property, 0 means left button

export interface IDetailsColumnProps extends React.Props<DetailsColumn> {
componentRef?: () => void;
column: IColumn;
columnIndex: number;
parentId?: string;
onRenderColumnHeaderTooltip?: IRenderFunction<ITooltipHostProps>;
onColumnClick?: (ev: React.MouseEvent<HTMLElement>, column: IColumn) => void;
onColumnContextMenu?: (column: IColumn, ev: React.MouseEvent<HTMLElement>) => void;
dragDropHelper?: IDragDropHelper | null;
isDraggable?: boolean;
setDraggedItemIndex?: (itemIndex: number) => void;
isDropped?: boolean;
}

export class DetailsColumn extends BaseComponent<IDetailsColumnProps> {
private _root: any;
private _dragDropSubscription: IDisposable;

constructor(props: IDetailsColumnProps) {
super(props);

this._root = createRef();
this._onDragStart = this._onDragStart.bind(this);
this._onDragEnd = this._onDragEnd.bind(this);
this._onRootMouseDown = this._onRootMouseDown.bind(this);
}

public render() {
const { column, columnIndex, parentId, isDraggable } = this.props;
const { onRenderColumnHeaderTooltip = this._onRenderColumnHeaderTooltip } = this.props;

return [
(
<div
key={ column.key }
ref={ this._root }
role={ 'columnheader' }
aria-sort={ column.isSorted ? (column.isSortedDescending ? 'descending' : 'ascending') : 'none' }
aria-disabled={ column.columnActionsMode === ColumnActionsMode.disabled }
aria-colindex={ columnIndex }
className={ css(
'ms-DetailsHeader-cell',
styles.cell,
column.headerClassName,
column.columnActionsMode !== ColumnActionsMode.disabled && 'is-actionable ' + styles.cellIsActionable,
!column.name && 'is-empty ' + styles.cellIsEmpty,
(column.isSorted || column.isGrouped || column.isFiltered) && 'is-icon-visible',
column.isPadded && styles.cellWrapperPadded
) }
data-is-draggable={ isDraggable }
draggable={ isDraggable }
style={ { width: column.calculatedWidth! + INNER_PADDING + (column.isPadded ? ISPADDED_WIDTH : 0) } }
data-automationid={ 'ColumnsHeaderColumn' }
data-item-key={ column.key }
>
{ isDraggable && <Icon iconName={ 'GripperBarVertical' } className={ css(styles.gripperBarVerticalStyle) } /> }
{ onRenderColumnHeaderTooltip(
{
hostClassName: css(styles.cellTooltip),
id: `${parentId}-${column.key}-tooltip`,
setAriaDescribedBy: false,
content: column.columnActionsMode !== ColumnActionsMode.disabled ? column.ariaLabel : '',
children: (
<span
id={ `${parentId}-${column.key}` }
aria-label={ column.isIconOnly ? column.name : undefined }
aria-labelledby={ column.isIconOnly ? undefined : `${parentId}-${column.key}-name ` }
className={ css('ms-DetailsHeader-cellTitle', styles.cellTitle) }
data-is-focusable={ column.columnActionsMode !== ColumnActionsMode.disabled }
role={ column.columnActionsMode !== ColumnActionsMode.disabled ? 'button' : undefined }
aria-describedby={
this.props.onRenderColumnHeaderTooltip ? `${parentId}-${column.key}-tooltip` : undefined
}
onContextMenu={ this._onColumnContextMenu.bind(this, column) }
onClick={ this._onColumnClick.bind(this, column) }
aria-haspopup={ column.columnActionsMode === ColumnActionsMode.hasDropdown }
>
<span
id={ `${parentId}-${column.key}-name` }
className={ css('ms-DetailsHeader-cellName', styles.cellName, {
[styles.iconOnlyHeader]: column.isIconOnly
}) }
>
{ (column.iconName || column.iconClassName) && (
<Icon className={ css(styles.nearIcon, column.iconClassName) } iconName={ column.iconName } />
) }

{ !column.isIconOnly ? column.name : undefined }
</span>

{ column.isFiltered && <Icon className={ styles.nearIcon } iconName={ 'Filter' } /> }

{ column.isSorted && (
<Icon
className={ css(styles.nearIcon, styles.sortIcon) }
iconName={ column.isSortedDescending ? 'SortDown' : 'SortUp' }
/>
) }

{ column.isGrouped && <Icon className={ styles.nearIcon } iconName={ 'GroupedDescending' } /> }

{ column.columnActionsMode === ColumnActionsMode.hasDropdown &&
!column.isIconOnly && (
<Icon
className={ css('ms-DetailsHeader-filterChevron', styles.filterChevron) }
iconName={ 'ChevronDown' }
/>
) }
</span>
)
},
this._onRenderColumnHeaderTooltip
) }
</div>
),
(
column.ariaLabel && !this.props.onRenderColumnHeaderTooltip ? (
<label key={ `${column.key}_label` } id={ `${parentId}-${column.key}-tooltip` } className={ styles.accessibleLabel }>
{ column.ariaLabel }
</label>
) : null
)
];
}

public componentDidMount(): void {
if (this._dragDropSubscription) {
this._dragDropSubscription.dispose();
delete this._dragDropSubscription;
}

if (this.props.dragDropHelper && this.props.isDraggable!) {
this._dragDropSubscription = this.props.dragDropHelper.subscribe(
this._root.current as HTMLElement,
this._events,
this._getColumnDragDropOptions()
);

// We need to use native on this to avoid MarqueeSelection from handling the event before us.
this._events.on(this._root.current, 'mousedown', this._onRootMouseDown);
}
if (this.props.isDropped) {
if (this._root!.current!) {
this._root!.current!.classList!.add(styles.borderAfterDropping);
}
setTimeout(() => {
if (this._root!.current!) {
this._root!.current!.classList!.remove(styles.borderAfterDropping);
}
}, 1500);
}
}

public componentWillUnmount(): void {
if (this._dragDropSubscription) {
this._dragDropSubscription.dispose();
delete this._dragDropSubscription;
}
}

public componentDidUpdate(): void {
if (!this._dragDropSubscription && this.props.dragDropHelper && this.props.isDraggable!) {
this._dragDropSubscription = this.props.dragDropHelper.subscribe(
this._root.value as HTMLElement,
this._events,
this._getColumnDragDropOptions()
);

// We need to use native on this to avoid MarqueeSelection from handling the event before us.
this._events.on(this._root.current, 'mousedown', this._onRootMouseDown);
}
if (this._dragDropSubscription && !this.props.isDraggable!) {
this._dragDropSubscription.dispose();
this._events.off(this._root.current, 'mousedown');
delete this._dragDropSubscription;
}
}

private _onRenderColumnHeaderTooltip = (
tooltipHostProps: ITooltipHostProps,
defaultRender?: IRenderFunction<ITooltipHostProps>
): JSX.Element => {
return <span className={ tooltipHostProps.hostClassName }>{ tooltipHostProps.children }</span>;
}

private _onColumnClick(column: IColumn, ev: React.MouseEvent<HTMLElement>): void {
const { onColumnClick } = this.props;
if (column.onColumnClick) {
column.onColumnClick(ev, column);
}
if (onColumnClick) {
onColumnClick(ev, column);
}
}

private _getColumnDragDropOptions(): IDragDropOptions {
const { columnIndex } = this.props;
const options = {
selectionIndex: columnIndex,
context: { data: columnIndex, index: columnIndex },
canDrag: () => this.props.isDraggable!,
canDrop: () => false,
onDragStart: this._onDragStart,
updateDropState: () => undefined,
onDrop: () => undefined,
onDragEnd: this._onDragEnd
};
return options;
}

private _onDragStart(item?: any, itemIndex?: number, selectedItems?: any[], event?: MouseEvent): void {
if (itemIndex && this.props.setDraggedItemIndex) {
this.props.setDraggedItemIndex(itemIndex);
this._root.current.classList.add(styles.borderWhileDragging);
}
}

private _onDragEnd(item?: any, event?: MouseEvent): void {
if (this.props.setDraggedItemIndex) {
this.props.setDraggedItemIndex(-1);
this._root.current.classList.remove(styles.borderWhileDragging);
}
}

private _onColumnContextMenu(column: IColumn, ev: React.MouseEvent<HTMLElement>): void {
const { onColumnContextMenu } = this.props;
if (column.onColumnContextMenu) {
column.onColumnContextMenu(column, ev);
ev.preventDefault();
}
if (onColumnContextMenu) {
onColumnContextMenu(column, ev);
ev.preventDefault();
}
}

private _onRootMouseDown(ev: MouseEvent): void {
const { isDraggable } = this.props;
// Ignore anything except the primary button.
if (isDraggable && ev.button === MOUSEDOWN_PRIMARY_BUTTON) {
ev.stopPropagation();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,18 @@ $isPaddedMargin: 24px;
&.cellIsEmpty {
text-overflow: clip;
}

&:hover .gripperBarVerticalStyle {
display: block;
}
}

.gripperBarVerticalStyle {
display: none;
position: absolute;
@include ms-text-align(left);
color: $ms-color-neutralTertiary;
@include ms-left(1px);
}

.cellSizer {
Expand Down Expand Up @@ -123,6 +135,7 @@ $isPaddedMargin: 24px;
@include margin-left(-16px);
}


.collapseButton {
text-align: center;
transform: rotate(-180deg);
Expand Down Expand Up @@ -169,7 +182,7 @@ $isPaddedMargin: 24px;
overflow: hidden;
max-width: 100%;
@include focus-border($position: auto);
padding: 0 8px;
padding: 0 8px 0 12px;
}

.cellName {
Expand Down Expand Up @@ -213,3 +226,66 @@ $isPaddedMargin: 24px;
.accessibleLabel {
@include ms-screenReaderOnly;
}

.borderWhileDragging {
border-style : solid;
border-width: 1px;
border-color:$ms-color-themePrimary;
-webkit-animation: fadeOut 0.2s forwards;
animation: fadeOut 0.2s forwards;
}

.dropHintCircleStyle {
display:inline-block;
visibility:hidden;
position: absolute;
bottom: 0;
top: -27px;
height: 9px;
width: 9px;
border-radius: 50%;
@include ms-margin-left(-5px);
top:34px;
overflow: visible;
z-index: 10;
border:1px solid $ms-color-themePrimary;
background: $ms-color-white;
}

.dropHintLineStyle{
display: inline-block;
visibility:hidden;
position: absolute;
bottom: 0;
top: -3px;
overflow: hidden;
height: 37px;
width: 1px;
background: $ms-color-themePrimary;
z-index:10;
}

.dropHintStyle{
display: inline-block;
position: absolute;
}

.borderAfterDropping {
border-style : solid;
border-width: 1px;
border-color:$ms-color-themePrimary;
-webkit-animation: fadeOut 1.5s forwards;
animation: fadeOut 1.5s forwards;
@include ms-left(-1px);
line-height: 31px;
}

@-webkit-keyframes fadeOut {
from {border-color:$ms-color-themePrimary; }
to {border-color:white }
}

@keyframes fadeOut {
from {border-color:$ms-color-themePrimary; }
to {border-color:white }
}
Loading