Skip to content

Commit

Permalink
feat: added default react-grid-layout handlers (#188)
Browse files Browse the repository at this point in the history
* feat: added default react-grid-layout handlers

* fix: storybook grops showcase typing

* chore: updated readme

* fix: removed console.log
  • Loading branch information
flops authored Sep 13, 2024
1 parent 91a1bfa commit 442a9ef
Show file tree
Hide file tree
Showing 9 changed files with 232 additions and 46 deletions.
23 changes: 23 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,15 @@ Plugins are required to create custom widgets.
### Props

```ts
type ItemManipulationCallback = (eventData: {
layout: Layouts;
oldItem: Layout;
newItem: Layout;
placeholder: Layout;
e: MouseEvent;
element: HTMLElement;
}) => void;

interface DashKitProps {
config: Config;
editMode: boolean;
Expand All @@ -32,6 +41,14 @@ interface DashKitProps {
onDrop: (dropProps: ItemDropProps) => void;
onItemMountChange: (item: ConfigItem, state: {isAsync: boolead; isMounted: boolean}) => void;
onItemRender: (item: ConfigItem) => void;

onDragStart?: ItemManipulationCallback;
onDrag?: ItemManipulationCallback;
onDragStop?: ItemManipulationCallback;
onResizeStart?: ItemManipulationCallback;
onResize?: ItemManipulationCallback;
onResizeStop?: ItemManipulationCallback;

defaultGlobalParams: GlobalParams;
globalParams: GlobalParams;
itemsStateAndParams: ItemsStateAndParams;
Expand Down Expand Up @@ -61,6 +78,12 @@ interface DashKitProps {
- **noOverlay**: If `true`, overlay and controls are not displayed while editing.
- **focusable**: If `true`, grid items will be focusable.
- **draggableHandleClassName** : СSS class name of the element that makes the widget draggable.
- **onDragStart**: ReactGridLayout called when item drag started
- **onDrag**: ReactGridLayout called while item drag
- **onDragStop**: ReactGridLayout called when item drag stopped
- **onResizeStart**: ReactGridLayout called when item resize started
- **onResize**: ReactGridLayout called while item resizing
- **onResizeStop**: ReactGridLayout called when item resize stoped

## Usage

Expand Down
12 changes: 11 additions & 1 deletion src/components/DashKit/DashKit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import {
ContextProps,
DashKitGroup,
GridReflowOptions,
ItemManipulationCallback,
MenuItem,
Plugin,
ReactGridLayoutProps,
Expand Down Expand Up @@ -45,9 +46,18 @@ interface DashKitDefaultProps {
itemsStateAndParams: ItemsStateAndParams;
groups?: DashKitGroup[];
}) => void;
onDrop: (dropProps: ItemDropProps) => void;
onDrop?: (dropProps: ItemDropProps) => void;

onItemMountChange?: (item: ConfigItem, state: {isAsync: boolean; isMounted: boolean}) => void;
onItemRender?: (item: ConfigItem) => void;

onDragStart?: ItemManipulationCallback;
onDrag?: ItemManipulationCallback;
onDragStop?: ItemManipulationCallback;
onResizeStart?: ItemManipulationCallback;
onResize?: ItemManipulationCallback;
onResizeStop?: ItemManipulationCallback;

defaultGlobalParams: GlobalParams;
globalParams: GlobalParams;
itemsStateAndParams: ItemsStateAndParams;
Expand Down
22 changes: 22 additions & 0 deletions src/components/DashKit/__stories__/DashKitGroupsShowcase.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
DashKitGroup,
DashKitProps,
DashkitGroupRenderProps,
ItemManipulationCallback,
ReactGridLayoutProps,
} from '../../..';
import {DEFAULT_GROUP, MenuItems} from '../../../helpers';
Expand Down Expand Up @@ -247,6 +248,25 @@ export const DashKitGroupsShowcase: React.FC = () => {
[config, groups],
);

const updateConfigOrder = React.useCallback<ItemManipulationCallback>(
(eventProps) => {
const index = config.items.findIndex((item) => item.id === eventProps.newItem.i);

const copyItems = [...config.items];
copyItems.push(copyItems.splice(index, 1)[0]);

const copyLyaout = [...config.layout];
copyLyaout.push(copyLyaout.splice(index, 1)[0]);

setConfig({
...config,
items: copyItems,
layout: copyLyaout,
});
},
[config],
);

return (
<DashKitDnDWrapper
onDragStart={() => {
Expand All @@ -270,6 +290,8 @@ export const DashKitGroupsShowcase: React.FC = () => {
onChange={onChange}
onDrop={onDrop}
overlayMenuItems={overlayMenuItems}
onDragStart={updateConfigOrder}
onResizeStart={updateConfigOrder}
/>
<ActionPanel items={items} />
</DemoRow>
Expand Down
171 changes: 130 additions & 41 deletions src/components/GridLayout/GridLayout.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
TEMPORARY_ITEM_ID,
} from '../../constants';
import {DashKitContext} from '../../context/DashKitContext';
import {resolveLayoutGroup} from '../../utils';
import GridItem from '../GridItem/GridItem';

import {Layout} from './ReactGridLayout';
Expand Down Expand Up @@ -93,7 +94,7 @@ export default class GridLayout extends React.PureComponent {
return temporaryLayout?.data || layout;
}

getMemoGroupLayout = (group, layout) => {
getMemoGroupLayout(group, layout) {
// fastest possible way to match json
const key = JSON.stringify(layout);

Expand All @@ -112,28 +113,37 @@ export default class GridLayout extends React.PureComponent {
}

return this._memoGroupsLayouts[group];
};
}

getMemoGroupCallbacks = (group) => {
getMemoGroupCallbacks(group) {
if (!this._memoCallbacksForGroups[group]) {
const onDragStart = this._onDragStart.bind(this, group);
const onStop = this._onStop.bind(this, group);
const onDrag = this._onDrag.bind(this, group);
const onDragStop = this._onDragStop.bind(this, group);

const onResizeStart = this._onResizeStart.bind(this, group);
const onResize = this._onResize.bind(this, group);
const onResizeStop = this._onResizeStop.bind(this, group);

const onDrop = this._onDrop.bind(this, group);
const onDropDragOver = this._onDropDragOver.bind(this, group);
const onDragTargetRestore = this._onTargetRestore.bind(this, group);

this._memoCallbacksForGroups[group] = {
onDragStart,
onDragStop: onStop,
onResizeStop: onStop,
onDrag,
onDragStop,
onResizeStart,
onResize,
onResizeStop,
onDrop,
onDropDragOver,
onDragTargetRestore,
};
}

return this._memoCallbacksForGroups[group];
};
}

getMemoGroupProps = (group, renderLayout, properties) => {
// Needed for _onDropDragOver
Expand Down Expand Up @@ -183,7 +193,7 @@ export default class GridLayout extends React.PureComponent {
}, {});

return renderLayout.map((currentItem) => {
const itemParent = itemsByGroup[currentItem.i].parent || DEFAULT_GROUP;
const itemParent = resolveLayoutGroup(itemsByGroup[currentItem.i]);

if (itemParent === group) {
return newItemsLayoutById[currentItem.i];
Expand Down Expand Up @@ -217,50 +227,117 @@ export default class GridLayout extends React.PureComponent {
}
}

_onDragStart = (group, _newLayout, layoutItem, _newItem, _placeholder, e) => {
if (this.temporaryLayout) return;
prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element) {
return {
group,
layout,
oldItem,
newItem,
placeholder,
e,
element,
};
}

updateeDraggingElementState(group, layoutItem, e) {
let currentDraggingElement = this.state.currentDraggingElement;

if (!currentDraggingElement) {
const {temporaryLayout} = this.context;
const layoutId = layoutItem.i;

const item = temporaryLayout
? temporaryLayout.dragProps
: this.context.config.items.find(({id}) => id === layoutId);
const {offsetX, offsetY} = e.nativeEvent;

currentDraggingElement = {
group,
layoutItem,
item,
cursorPosition: {offsetX, offsetY},
};
}

this.setState({currentDraggingElement, draggedOverGroup: group});
}

_onDragStart(group, _newLayout, layoutItem, _newItem, _placeholder, e, element) {
this.context.onDragStart?.call(
this,
this.prepareDefaultArguments(
group,
_newLayout,
layoutItem,
_newItem,
_placeholder,
e,
element,
),
);

if (this.context.dragOverPlugin) {
this.setState({isDragging: true});
} else {
let currentDraggingElement = this.state.currentDraggingElement;

if (!currentDraggingElement) {
const layoutId = layoutItem.i;
const item = this.context.config.items.find(({id}) => id === layoutId);
const {offsetX, offsetY} = e.nativeEvent;

currentDraggingElement = {
group,
layoutItem,
item,
cursorPosition: {offsetX, offsetY},
};
}

this.setState({
isDragging: true,
currentDraggingElement,
draggedOverGroup: group,
});
this.updateeDraggingElementState(group, layoutItem, e);
this.setState({isDragging: true});
}
};
}

_onDrag(group, layout, oldItem, newItem, placeholder, e, element) {
this.context.onDrag?.call(
this,
this.prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element),
);
}

_onDragStop(group, layout, oldItem, newItem, placeholder, e, element) {
this._onStop(group, layout);

_onResizeStart = () => {
this.context.onDragStop?.call(
this,
this.prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element),
);
}

_onResizeStart(group, layout, oldItem, newItem, placeholder, e, element) {
this.setState({
isDragging: true,
});
};

_onTargetRestore = () => {
this.context.onResizeStart?.call(
this,
this.prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element),
);
}

_onResize(group, layout, oldItem, newItem, placeholder, e, element) {
this.context.onResize?.call(
this,
this.prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element),
);
}

_onResizeStop(group, layout, oldItem, newItem, placeholder, e, element) {
this.context.onResizeStop?.call(
this,
this.prepareDefaultArguments(group, layout, oldItem, newItem, placeholder, e, element),
);
}

_onTargetRestore() {
if (this.context.temporaryLayout) {
return;
}

const {currentDraggingElement} = this.state;

if (currentDraggingElement) {
this.setState({
draggedOverGroup: currentDraggingElement.group,
});
}
};
}

_onStop = (group, newLayout) => {
const {layoutChange, onDrop, temporaryLayout} = this.context;
Expand Down Expand Up @@ -328,6 +405,7 @@ export default class GridLayout extends React.PureComponent {
draggedOverGroup: null,
});

// TODO temporaryLayout
layoutChange(groupedLayout);
};

Expand Down Expand Up @@ -358,9 +436,14 @@ export default class GridLayout extends React.PureComponent {
};

_onDropDragOver = (group, e) => {
const {editMode, dragOverPlugin, onDropDragOver} = this.context;
const {editMode, dragOverPlugin, onDropDragOver, temporaryLayout} = this.context;
const {currentDraggingElement} = this.state;

// TODO If temporary item is trying to change group
if (temporaryLayout && currentDraggingElement) {
return false;
}

if (!editMode || (!dragOverPlugin && !currentDraggingElement)) {
return false;
}
Expand Down Expand Up @@ -443,31 +526,37 @@ export default class GridLayout extends React.PureComponent {

return (
<Layout
// Group properties
{...properties}
// Layout props
compactType={compactType}
layout={layout}
key={`group_${group}`}
isDraggable={editMode}
isResizable={editMode}
onResizeStart={this._onResizeStart}
draggableCancel={`.${OVERLAY_CONTROLS_CLASS_NAME}`}
{...(draggableHandleClassName
? {draggableHandle: `.${draggableHandleClassName}`}
: null)}
// Default callbacks
onDragStart={callbacks.onDragStart}
onDrag={callbacks.onDrag}
onDragStop={callbacks.onDragStop}
onResizeStart={callbacks.onResizeStart}
onResize={callbacks.onResize}
onResizeStop={callbacks.onResizeStop}
// External Drag N Drop options
onDragTargetRestore={callbacks.onDragTargetRestore}
onDropDragOver={callbacks.onDropDragOver}
onDrop={callbacks.onDrop}
hasSharedDragItem={hasSharedDragItem}
sharedDragPosition={currentDraggingElement?.cursorPosition}
isDragCaptured={isDragCaptured}
{...(draggableHandleClassName
? {draggableHandle: `.${draggableHandleClassName}`}
: null)}
{...(outerDnDEnable
? {
isDroppable: true,
}
: null)}
draggableCancel={`.${OVERLAY_CONTROLS_CLASS_NAME}`}
>
{renderItems.map((item, i) => {
return (
Expand Down
Loading

0 comments on commit 442a9ef

Please sign in to comment.