Skip to content
Merged
Show file tree
Hide file tree
Changes from 18 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
77ad148
feat: first POC of the usage of pragmatic dnd instead of dnd kit
marcosmoura Apr 8, 2024
3f56d45
fix: remove unused imports and make the component a little more stable
marcosmoura Apr 23, 2024
82d112e
Merge branch 'main' into experimental/react-draggable-dialog/use-prag…
marcosmoura Sep 8, 2025
3b47184
fix(react-draggable-dialog): improve calculation performance after mo…
marcosmoura Sep 9, 2025
a000a7b
Merge branch 'main' into fix/react-draggable-dialog/improve-performan…
marcosmoura Sep 9, 2025
2cb38b0
Change files
marcosmoura Sep 9, 2025
2d4207f
fix: update changelog message
marcosmoura Sep 9, 2025
c2347ff
fix: dedupe
marcosmoura Sep 9, 2025
f7acb6a
fix: dedupe
marcosmoura Sep 9, 2025
183e134
fix: dedupe
marcosmoura Sep 9, 2025
c81b0b2
fix: dedupe
marcosmoura Sep 9, 2025
0e08bcb
fix: clear animation frame on cleanup
marcosmoura Sep 11, 2025
e9f195c
fix: remove unnecessary useMemo
marcosmoura Sep 11, 2025
96059fa
fix: remove unnecessary useMemo
marcosmoura Sep 11, 2025
de1845c
Merge branch 'main' into fix/react-draggable-dialog/improve-performan…
marcosmoura Sep 19, 2025
8019fad
fix: revert changes to yarn.lock
marcosmoura Oct 14, 2025
cf9eb3c
fix: upgrade dnd-kit
marcosmoura Oct 14, 2025
716f2b6
Merge branch 'main' into fix/react-draggable-dialog/improve-performan…
marcosmoura Oct 14, 2025
be6f79f
fix: formatting
marcosmoura Oct 14, 2025
9f1158e
fix: remove eslint comment
marcosmoura Oct 14, 2025
309edb6
fix: update swc config for tests
marcosmoura Oct 15, 2025
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,7 @@
{
"type": "patch",
"comment": "fix: improve calculation performance after motion upgrade on Dialog",
"packageName": "@fluentui-contrib/react-draggable-dialog",
"email": "[email protected]",
"dependentChangeType": "patch"
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@
"@babel/preset-env": "7.27.1",
"@babel/preset-react": "7.27.1",
"@babel/preset-typescript": "7.27.1",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/modifiers": "^7.0.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/modifiers": "^9.0.0",
"@dnd-kit/utilities": "^3.2.2",
"@fluentui/react": "^8.120.2",
"@fluentui/react-components": "^9.70.0",
Expand Down
11 changes: 8 additions & 3 deletions packages/react-draggable-dialog/jest.config.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
/* eslint-disable */
import { readFileSync } from 'fs';
import { dirname, join } from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);

Check failure on line 6 in packages/react-draggable-dialog/jest.config.ts

View workflow job for this annotation

GitHub Actions / main

The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node16', 'node18', or 'nodenext'.

Check failure on line 6 in packages/react-draggable-dialog/jest.config.ts

View workflow job for this annotation

GitHub Actions / main

The 'import.meta' meta-property is only allowed when the '--module' option is 'es2020', 'es2022', 'esnext', 'system', 'node16', 'node18', or 'nodenext'.
const __dirname = dirname(__filename);

// Reading the SWC compilation config and remove the "exclude"
// for the test files to be compiled by SWC
const { exclude: _, ...swcJestConfig } = JSON.parse(
readFileSync(`${__dirname}/.swcrc`, 'utf-8')
readFileSync(join(__dirname, '.swcrc'), 'utf-8')
);

// disable .swcrc look-up by SWC core because we're passing in swcJestConfig ourselves.
Expand All @@ -19,11 +24,11 @@
// swcJestConfig.module.noInterop = false;

export default {
displayName: 'button',
displayName: 'react-draggable-dialog',
preset: '../../jest.preset.js',
transform: {
'^.+\\.[tj]sx?$': ['@swc/jest', swcJestConfig],
},
moduleFileExtensions: ['js', 'ts', 'tsx', 'html'],
coverageDirectory: '../../coverage/packages/button',
coverageDirectory: '../../coverage/packages/react-draggable-dialog',
};
4 changes: 2 additions & 2 deletions packages/react-draggable-dialog/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
"version": "0.2.1",
"dependencies": {
"@swc/helpers": "~0.5.11",
"@dnd-kit/core": "^6.1.0",
"@dnd-kit/core": "^6.3.1",
"@dnd-kit/utilities": "^3.2.2",
"@dnd-kit/modifiers": "^7.0.0"
"@dnd-kit/modifiers": "^9.0.0"
},
"peerDependencies": {
"@fluentui/react-components": ">=9.70.0 <10.0.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,16 @@ import * as React from 'react';
import { render } from '@testing-library/react';

import { DraggableDialog } from './DraggableDialog';
import { DraggableDialogSurface } from '../DraggableDialogSurface';

describe('DraggableDialog', () => {
it('should render', () => {
const text = 'Context';
const { getByText } = render(
<DraggableDialog open>
<div>{text}</div>
<DraggableDialogSurface>
<div>{text}</div>
</DraggableDialogSurface>
</DraggableDialog>
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { useDraggableDialog } from './useDraggableDialog';
/**
* DraggableDialog is a wrapper around the Dialog component that makes it draggable.
*/
export const DraggableDialog: React.FC<DraggableDialogProps> = (props) => {
export const DraggableDialog: React.FC<DraggableDialogProps> = React.memo((props) => {
const { contextValue, dialogProps, ...dndProps } = useDraggableDialog(props);

return (
Expand All @@ -19,4 +19,6 @@ export const DraggableDialog: React.FC<DraggableDialogProps> = (props) => {
</DraggableDialogContextProvider>
</DndContext>
);
};
});

DraggableDialog.displayName = 'DraggableDialog';
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ describe('DraggableDialog', () => {
'sensors',
'modifiers',
'accessibility',
'contextValue',
'dialogProps',
'contextValue',
]);
expect(result.current.modifiers).toHaveLength(1);
expect(result.current.accessibility).toEqual(undefined);
Expand All @@ -47,10 +47,10 @@ describe('DraggableDialog', () => {
const sensorNames = result.current.sensors.map(({ sensor }) => sensor.name);

expect(sensorNames).toStrictEqual([
'PointerSensor',
'KeyboardSensor',
'MouseSensor',
'PointerSensor',
'TouchSensor',
'KeyboardSensor',
]);
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ import {
useSensor,
useSensors,
} from '@dnd-kit/core';
import { useId } from '@fluentui/react-components';
import { useAnimationFrame, useId } from '@fluentui/react-components';

import {
DraggableDialogProps,
DraggableDialogState,
} from './DraggableDialog.types';
import { getParsedDraggableMargin } from './utils/getParsedDraggableMargin';
import { restrictToMarginModifier } from './utils/restrictToMarginModifier';
import { restrictToBoundaryModifier } from './utils/restrictToBoundaryModifier';

/**
* This function is used to partition the props into two separate objects. The first object contains
Expand Down Expand Up @@ -68,36 +68,52 @@ export const useDraggableDialog = (
},
dialogProps,
} = usePartitionedDraggableProps(props);

const [dropPosition, setDropPosition] = React.useState({ x: 0, y: 0 });
const [hasBeenDragged, setHasBeenDragged] = React.useState(false);

const [setOnDragAnimationFrame, cancelOnDragAnimationFrame] =
useAnimationFrame();

const keyboardSensor = useSensor(KeyboardSensor);
const mouseSensor = useSensor(MouseSensor);
const pointerSensor = useSensor(PointerSensor);
const touchSensor = useSensor(TouchSensor);
const keyboardSensor = useSensor(KeyboardSensor);
const sensors = useSensors(
pointerSensor,
keyboardSensor,
mouseSensor,
touchSensor,
keyboardSensor
pointerSensor,
touchSensor
);

const onDragMove = React.useCallback(
({ active }: DragMoveEvent | DragEndEvent) => {
const { translated: rect } = active.rect.current;

if (!onPositionChange || !rect) {
return;
}

const position = {
x: rect.left,
y: rect.top,
};
cancelOnDragAnimationFrame();
setOnDragAnimationFrame(() => {
const { translated: rect } = active.rect.current;

if (!onPositionChange || !rect) {
return;
}

onPositionChange({
x: rect.left,
y: rect.top,
});
});
},
[cancelOnDragAnimationFrame, setOnDragAnimationFrame, onPositionChange]
);

onPositionChange(position);
const setInitialDropPosition = React.useCallback(
({ x, y }: typeof dropPosition) => {
setHasBeenDragged(true);
setDropPosition({
x,
y,
});
},
[onPositionChange]
[]
);

const onDragEnd = React.useCallback(
Expand All @@ -114,22 +130,11 @@ export const useDraggableDialog = (
});
onDragMove(event);
},
[onDragMove]
);

const setInitialDropPosition = React.useCallback(
({ x, y }: typeof dropPosition) => {
setHasBeenDragged(true);
setDropPosition({
x,
y,
});
},
[]
[onDragMove, setInitialDropPosition]
);

const modifiers = React.useMemo(() => {
return [restrictToMarginModifier({ margin, boundary })];
return [restrictToBoundaryModifier({ margin, boundary })];
}, [margin, boundary]);

const accessibility = React.useMemo(() => {
Expand All @@ -154,17 +159,10 @@ export const useDraggableDialog = (
};
}, [announcements]);

const contextValue = {
hasDraggableParent: true,
setDropPosition: setInitialDropPosition,
dropPosition,
position,
onPositionChange,
id,
hasBeenDragged,
margin,
boundary,
};
React.useEffect(
() => () => cancelOnDragAnimationFrame(),
[cancelOnDragAnimationFrame]
);

return React.useMemo(
() => ({
Expand All @@ -173,17 +171,33 @@ export const useDraggableDialog = (
sensors,
modifiers,
accessibility,
contextValue,
dialogProps,
contextValue: {
hasDraggableParent: true,
dropPosition,
position,
onPositionChange,
id,
hasBeenDragged,
margin,
boundary,
setDropPosition: () => undefined, // deprecated but needed for compatibility
},
}),
[
onDragMove,
onDragEnd,
sensors,
modifiers,
accessibility,
contextValue,
dialogProps,
dropPosition,
position,
onPositionChange,
id,
hasBeenDragged,
margin,
boundary,
]
);
};
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@ import {
DraggableDialogMarginViewport,
} from '../DraggableDialog.types';

type RestrictToMarginModifierOptions = {
type RestrictToBoundaryModifierOptions = {
margin: Required<DraggableDialogMarginViewport>;
} & Pick<DraggableDialogProps, 'boundary'>;

type RestrictToMarginModifier = (
options: RestrictToMarginModifierOptions
type RestrictToBoundaryModifier = (
options: RestrictToBoundaryModifierOptions
) => Modifier;

const getRectWithMargin = (
Expand All @@ -26,7 +26,7 @@ const getRectWithMargin = (
left: rect.left + margin.start,
});

export const restrictToMarginModifier: RestrictToMarginModifier =
export const restrictToBoundaryModifier: RestrictToBoundaryModifier =
({ margin, boundary }) =>
({ windowRect, containerNodeRect, transform, ...modifier }) => {
if (!boundary) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,25 @@ import { useDraggableDialogHandle } from './useDraggableDialogHandle';
/**
* DraggableDialogHandle is the handle element that can be used to drag the DraggableDialog.
*/
export const DraggableDialogHandle: React.FC<DraggableDialogHandleProps> = ({
children,
className,
}) => {
const styles = useStyles();
const { setActivatorNodeRef, attributes, listeners } =
useDraggableDialogHandle();
export const DraggableDialogHandle: React.FC<DraggableDialogHandleProps> =
React.memo(({ children, className }) => {
const styles = useStyles();
const { setActivatorNodeRef, attributes, listeners } =
useDraggableDialogHandle();

const child = React.Children.only(children);
const classnames = mergeClasses(
'fui-DraggableDialogHandle',
styles.root,
child.props.className,
className
);
const child = React.Children.only(children);

return React.cloneElement(child, {
ref: setActivatorNodeRef,
className: classnames,
...attributes,
...listeners,
return React.cloneElement(child, {
ref: setActivatorNodeRef,
className: mergeClasses(
'fui-DraggableDialogHandle',
styles.root,
child.props.className,
className
),
...attributes,
...listeners,
});
});
};

DraggableDialogHandle.displayName = 'DraggableDialogHandle';
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,16 @@ import * as React from 'react';
import { render } from '@testing-library/react';
import { DraggableDialogSurface } from './DraggableDialogSurface';

jest.mock('../../contexts/DraggableDialogContext', () => ({
__esModule: true,
...jest.requireActual('../../contexts/DraggableDialogContext'),
}));

describe('DraggableDialogSurface', () => {
let consoleErrorMock: jest.SpyInstance;
let consoleErrorSpy: jest.SpyInstance;

beforeEach(() => {
consoleErrorMock = jest
beforeAll(() => {
consoleErrorSpy = jest
.spyOn(console, 'error')
.mockImplementation((error: string) => error);
});

afterEach(() => {
jest.clearAllMocks();
});
afterAll(() => jest.resetAllMocks());

it('should render', () => {
const text = 'Surface';
Expand All @@ -38,7 +31,7 @@ describe('DraggableDialogSurface', () => {
</DraggableDialogSurface>
);

expect(consoleErrorMock).toHaveBeenCalledWith(expect.any(String));
expect(consoleErrorSpy).toHaveBeenCalledWith(expect.any(String));
});

it('should render DialogSurface with draggable class', () => {
Expand Down
Loading
Loading