Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
20 changes: 19 additions & 1 deletion gulpfile.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
const path = require('path');

const utils = require('@gravity-ui/gulp-utils');
const {task, src, dest, series, parallel} = require('gulp');
const {task, src, dest, series, parallel, watch} = require('gulp');
const sass = require('gulp-dart-sass');
const sourcemaps = require('gulp-sourcemaps');
const rimraf = require('rimraf');
Expand Down Expand Up @@ -101,3 +101,21 @@ task(
);

task('default', series(['build']));

task(
'rebuild',
parallel([
'compile-to-esm',
'compile-to-cjs',
'copy-js-declarations',
'copy-i18n',
'styles-components',
]),
);

task(
'watch',
series(['build'], () => {
return watch(['src/**/*.{js,jsx,ts,tsx,scss}'], series(['rebuild']));
}),
);
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@
"test": "jest",
"clean": "gulp clean",
"build": "gulp",
"build:watch": "gulp watch",
"start": "storybook dev -p 7120",
"typecheck": "tsc --noEmit",
"build-storybook": "sb build -c .storybook -o storybook-static",
Expand Down
23 changes: 18 additions & 5 deletions src/components/GridLayout/GridLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export default class GridLayout extends React.PureComponent<GridLayoutProps, Gri
offsetX: number;
offsetY: number;
} | null> = {current: null};
private _groupResetRegistryRef: React.MutableRefObject<Map<string, () => void>> = {
current: new Map(),
};

private _timeout?: NodeJS.Timeout;
private _lastReloadAt?: number;
Expand Down Expand Up @@ -478,7 +481,9 @@ export default class GridLayout extends React.PureComponent<GridLayoutProps, Gri
if (this.context.dragOverPlugin) {
this.setState({isDragging: true});
} else {
this._sharedDragRef.current = {isDragging: true, sourceGroup: group};
if (!this._sharedDragRef.current.isDragging) {
this._sharedDragRef.current = {isDragging: true, sourceGroup: group};
}
this._initDragCoordinatesWatcher(element);
this.updateDraggingElementState(group, layoutItem, e);
this.setState({isDragging: true});
Expand Down Expand Up @@ -604,8 +609,7 @@ export default class GridLayout extends React.PureComponent<GridLayoutProps, Gri

const groupedLayout = this.mergeGroupsLayout(group, newLayout);

this._sharedDragRef.current = {isDragging: false, sourceGroup: null};
this._sharedDragPositionRef.current = null;
this._clearSharedDragState();
this.setState({
isDragging: false,
currentDraggingElement: null,
Expand Down Expand Up @@ -650,8 +654,7 @@ export default class GridLayout extends React.PureComponent<GridLayoutProps, Gri
},
);

this._sharedDragRef.current = {isDragging: false, sourceGroup: null};
this._sharedDragPositionRef.current = null;
this._clearSharedDragState();
this.setState({
isDragging: false,
currentDraggingElement: null,
Expand Down Expand Up @@ -738,6 +741,15 @@ export default class GridLayout extends React.PureComponent<GridLayoutProps, Gri
return false;
};

_clearSharedDragState() {
this._sharedDragRef.current = {isDragging: false, sourceGroup: null};
this._sharedDragPositionRef.current = null;

// Reset all group placeholders — covers the case where mouseup happens
// in a gap between groups and no individual group's mouseUpHandler fires.
this._groupResetRegistryRef.current.forEach((reset) => reset());
}

renderTemporaryPlaceholder(gridLayout: Partial<ReactGridLayoutProps>) {
const {temporaryLayout, noOverlay, draggableHandleClassName} = this.context;

Expand Down Expand Up @@ -820,6 +832,7 @@ export default class GridLayout extends React.PureComponent<GridLayoutProps, Gri
temporaryPlaceholder={this.renderTemporaryPlaceholder(stableProperties)}
dragStateRef={this._sharedDragRef}
sharedDragPositionRef={this._sharedDragPositionRef}
groupResetRegistryRef={this._groupResetRegistryRef}
isDragCaptured={isDragCaptured}
isAnyDragging={groupIsAnyDragging}
currentDraggingItemId={groupCurrentDraggingItemId}
Expand Down
3 changes: 3 additions & 0 deletions src/components/GridLayout/GroupLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export interface GroupLayoutProps {
isDragCaptured: boolean;
dragStateRef: React.MutableRefObject<{isDragging: boolean; sourceGroup: string | null}>;
sharedDragPositionRef: React.MutableRefObject<{offsetX: number; offsetY: number} | null>;
groupResetRegistryRef: React.MutableRefObject<Map<string, () => void>>;

// Drag state scoped to this group only (always false/null for non-source groups)
isAnyDragging: boolean;
Expand Down Expand Up @@ -68,6 +69,7 @@ export const GroupLayout = React.memo(function GroupLayout({
temporaryPlaceholder,
dragStateRef,
sharedDragPositionRef,
groupResetRegistryRef,
isDragCaptured,
isAnyDragging,
currentDraggingItemId,
Expand Down Expand Up @@ -161,6 +163,7 @@ export const GroupLayout = React.memo(function GroupLayout({
onDrop={callbacks.onDrop}
dragStateRef={dragStateRef}
sharedDragPositionRef={sharedDragPositionRef}
groupResetRegistryRef={groupResetRegistryRef}
group={group}
isDragCaptured={isDragCaptured}
isDroppable={Boolean(outerDnDEnable) && editMode}
Expand Down
25 changes: 19 additions & 6 deletions src/components/GridLayout/ReactGridLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ type DragOverLayoutProps = ReactGridLayout.ReactGridLayoutProps & {
group?: string;
onDragTargetRestore?: () => void;
transformScaleRef?: React.MutableRefObject<number>;
groupResetRegistryRef?: React.MutableRefObject<Map<string, () => void>>;
};

type DragOverLayoutState = {
Expand Down Expand Up @@ -73,6 +74,13 @@ class DragOverLayout extends ReactGridLayout {
componentDidMount(): void {
super.componentDidMount?.();

if (this.props.group !== undefined) {
this.props.groupResetRegistryRef?.current.set(
this.props.group,
this.resetExternalPlaceholder,
);
}

// If cursor is moved out of the window there is a bug
// which leaves placeholder element in grid, this action needed to reset this state
window.addEventListener('dragend', this.resetExternalPlaceholder);
Expand All @@ -87,6 +95,10 @@ class DragOverLayout extends ReactGridLayout {
}

componentWillUnmount(): void {
if (this.props.group !== undefined) {
this.props.groupResetRegistryRef?.current.delete(this.props.group);
}

window.removeEventListener('dragend', this.resetExternalPlaceholder);
const innerElement = this.getInnerElement();

Expand Down Expand Up @@ -221,11 +233,9 @@ class DragOverLayout extends ReactGridLayout {
}
};

// Returns true when another group's item is being dragged over this grid.
// Reads from the ref directly — no re-render required to stay current.
isSharedDragTarget = (): boolean => {
const drag = this.props.dragStateRef?.current;
return Boolean(drag?.isDragging && drag?.sourceGroup !== this.props.group);
return Boolean(drag?.isDragging);
};

// Proxy mouse events -> drag methods for dnd between groups
Expand All @@ -247,7 +257,10 @@ class DragOverLayout extends ReactGridLayout {
};

mouseMoveHandler = (e: MouseEvent): void => {
if (this.isSharedDragTarget()) {
if (
this.isSharedDragTarget() &&
this.props.group !== this.props.dragStateRef?.current.sourceGroup
) {
if (!(e as MouseEvent & {nativeEvent?: MouseEvent}).nativeEvent) {
// Emulate nativeEvent for firefox
const target = this.getInnerElement() || (e.target as HTMLElement);
Expand All @@ -271,8 +284,8 @@ class DragOverLayout extends ReactGridLayout {
const {layout} = this.state;
const item = layout.find((l) => l.i === droppingItem?.i);

// reset dragEnter counter on drop
this.resetExternalPlaceholder();
// reset dragEnter counter on drop for all registered groups
this.props.groupResetRegistryRef?.current.forEach((reset) => reset());

if (item) {
this.props.onDrop?.(layout, item, e);
Expand Down
Loading