react-resizable-panels 2.0.3 → 2.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +8 -0
- package/dist/declarations/src/PanelResizeHandle.d.ts +1 -0
- package/dist/declarations/src/PanelResizeHandleRegistry.d.ts +1 -2
- package/dist/declarations/src/index.d.ts +3 -1
- package/dist/declarations/src/utils/rects/getIntersectingRectangle.d.ts +2 -0
- package/dist/declarations/src/utils/rects/intersects.d.ts +2 -0
- package/dist/declarations/src/utils/rects/types.d.ts +6 -0
- package/dist/react-resizable-panels.browser.cjs.js +138 -56
- package/dist/react-resizable-panels.browser.cjs.mjs +3 -1
- package/dist/react-resizable-panels.browser.development.cjs.js +138 -56
- package/dist/react-resizable-panels.browser.development.cjs.mjs +3 -1
- package/dist/react-resizable-panels.browser.development.esm.js +137 -57
- package/dist/react-resizable-panels.browser.esm.js +137 -57
- package/dist/react-resizable-panels.cjs.js +138 -56
- package/dist/react-resizable-panels.cjs.mjs +3 -1
- package/dist/react-resizable-panels.development.cjs.js +138 -56
- package/dist/react-resizable-panels.development.cjs.mjs +3 -1
- package/dist/react-resizable-panels.development.esm.js +137 -57
- package/dist/react-resizable-panels.development.node.cjs.js +138 -56
- package/dist/react-resizable-panels.development.node.cjs.mjs +3 -1
- package/dist/react-resizable-panels.development.node.esm.js +137 -57
- package/dist/react-resizable-panels.esm.js +137 -57
- package/dist/react-resizable-panels.node.cjs.js +138 -56
- package/dist/react-resizable-panels.node.cjs.mjs +3 -1
- package/dist/react-resizable-panels.node.esm.js +137 -57
- package/package.json +4 -1
- package/src/Panel.test.tsx +63 -0
- package/src/PanelGroup.test.tsx +21 -1
- package/src/PanelResizeHandle.test.tsx +181 -22
- package/src/PanelResizeHandle.ts +44 -24
- package/src/PanelResizeHandleRegistry.ts +87 -30
- package/src/index.ts +4 -0
- package/src/utils/rects/getIntersectingRectangle.test.ts +198 -0
- package/src/utils/rects/getIntersectingRectangle.ts +28 -0
- package/src/utils/rects/intersects.test.ts +197 -0
- package/src/utils/rects/intersects.ts +23 -0
- package/src/utils/rects/types.ts +6 -0
- package/src/utils/test-utils.ts +39 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import * as React from 'react';
|
|
2
|
+
import { compare } from 'stacking-order';
|
|
2
3
|
|
|
3
4
|
// This module exists to work around Webpack issue https://github.com/webpack/webpack/issues/14814
|
|
4
5
|
|
|
@@ -235,6 +236,14 @@ function getInputType() {
|
|
|
235
236
|
}
|
|
236
237
|
}
|
|
237
238
|
|
|
239
|
+
function intersects(rectOne, rectTwo, strict) {
|
|
240
|
+
if (strict) {
|
|
241
|
+
return rectOne.x < rectTwo.x + rectTwo.width && rectOne.x + rectOne.width > rectTwo.x && rectOne.y < rectTwo.y + rectTwo.height && rectOne.y + rectOne.height > rectTwo.y;
|
|
242
|
+
} else {
|
|
243
|
+
return rectOne.x <= rectTwo.x + rectTwo.width && rectOne.x + rectOne.width >= rectTwo.x && rectOne.y <= rectTwo.y + rectTwo.height && rectOne.y + rectOne.height >= rectTwo.y;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
238
247
|
const EXCEEDED_HORIZONTAL_MIN = 0b0001;
|
|
239
248
|
const EXCEEDED_HORIZONTAL_MAX = 0b0010;
|
|
240
249
|
const EXCEEDED_VERTICAL_MIN = 0b0100;
|
|
@@ -273,12 +282,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
|
|
|
273
282
|
};
|
|
274
283
|
}
|
|
275
284
|
function handlePointerDown(event) {
|
|
285
|
+
const {
|
|
286
|
+
target
|
|
287
|
+
} = event;
|
|
276
288
|
const {
|
|
277
289
|
x,
|
|
278
290
|
y
|
|
279
291
|
} = getResizeEventCoordinates(event);
|
|
280
292
|
isPointerDown = true;
|
|
281
293
|
recalculateIntersectingHandles({
|
|
294
|
+
target,
|
|
282
295
|
x,
|
|
283
296
|
y
|
|
284
297
|
});
|
|
@@ -293,29 +306,32 @@ function handlePointerMove(event) {
|
|
|
293
306
|
x,
|
|
294
307
|
y
|
|
295
308
|
} = getResizeEventCoordinates(event);
|
|
296
|
-
if (isPointerDown) {
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
} = data;
|
|
301
|
-
setResizeHandlerState("move", "drag", event);
|
|
302
|
-
});
|
|
309
|
+
if (!isPointerDown) {
|
|
310
|
+
const {
|
|
311
|
+
target
|
|
312
|
+
} = event;
|
|
303
313
|
|
|
304
|
-
//
|
|
305
|
-
|
|
306
|
-
|
|
314
|
+
// Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
|
|
315
|
+
// at that point, the handles may not move with the pointer (depending on constraints)
|
|
316
|
+
// but the same set of active handles should be locked until the pointer is released
|
|
307
317
|
recalculateIntersectingHandles({
|
|
318
|
+
target,
|
|
308
319
|
x,
|
|
309
320
|
y
|
|
310
321
|
});
|
|
311
|
-
updateResizeHandlerStates("move", event);
|
|
312
|
-
updateCursor();
|
|
313
322
|
}
|
|
323
|
+
updateResizeHandlerStates("move", event);
|
|
324
|
+
|
|
325
|
+
// Update cursor based on return value(s) from active handles
|
|
326
|
+
updateCursor();
|
|
314
327
|
if (intersectingHandles.length > 0) {
|
|
315
328
|
event.preventDefault();
|
|
316
329
|
}
|
|
317
330
|
}
|
|
318
331
|
function handlePointerUp(event) {
|
|
332
|
+
const {
|
|
333
|
+
target
|
|
334
|
+
} = event;
|
|
319
335
|
const {
|
|
320
336
|
x,
|
|
321
337
|
y
|
|
@@ -325,33 +341,72 @@ function handlePointerUp(event) {
|
|
|
325
341
|
if (intersectingHandles.length > 0) {
|
|
326
342
|
event.preventDefault();
|
|
327
343
|
}
|
|
344
|
+
updateResizeHandlerStates("up", event);
|
|
328
345
|
recalculateIntersectingHandles({
|
|
346
|
+
target,
|
|
329
347
|
x,
|
|
330
348
|
y
|
|
331
349
|
});
|
|
332
|
-
updateResizeHandlerStates("up", event);
|
|
333
350
|
updateCursor();
|
|
334
351
|
updateListeners();
|
|
335
352
|
}
|
|
336
353
|
function recalculateIntersectingHandles({
|
|
354
|
+
target,
|
|
337
355
|
x,
|
|
338
356
|
y
|
|
339
357
|
}) {
|
|
340
358
|
intersectingHandles.splice(0);
|
|
359
|
+
let targetElement = null;
|
|
360
|
+
if (target instanceof HTMLElement) {
|
|
361
|
+
targetElement = target;
|
|
362
|
+
}
|
|
341
363
|
registeredResizeHandlers.forEach(data => {
|
|
342
364
|
const {
|
|
343
|
-
element,
|
|
365
|
+
element: dragHandleElement,
|
|
344
366
|
hitAreaMargins
|
|
345
367
|
} = data;
|
|
368
|
+
const dragHandleRect = dragHandleElement.getBoundingClientRect();
|
|
346
369
|
const {
|
|
347
370
|
bottom,
|
|
348
371
|
left,
|
|
349
372
|
right,
|
|
350
373
|
top
|
|
351
|
-
} =
|
|
374
|
+
} = dragHandleRect;
|
|
352
375
|
const margin = isCoarsePointer ? hitAreaMargins.coarse : hitAreaMargins.fine;
|
|
353
|
-
const
|
|
354
|
-
if (
|
|
376
|
+
const eventIntersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
|
|
377
|
+
if (eventIntersects) {
|
|
378
|
+
// TRICKY
|
|
379
|
+
// We listen for pointers events at the root in order to support hit area margins
|
|
380
|
+
// (determining when the pointer is close enough to an element to be considered a "hit")
|
|
381
|
+
// Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
|
|
382
|
+
// so at this point we need to compare stacking order of a potentially intersecting drag handle,
|
|
383
|
+
// and the element that was actually clicked/touched
|
|
384
|
+
if (targetElement !== null && dragHandleElement !== targetElement && !dragHandleElement.contains(targetElement) && !targetElement.contains(dragHandleElement) &&
|
|
385
|
+
// Calculating stacking order has a cost, so we should avoid it if possible
|
|
386
|
+
// That is why we only check potentially intersecting handles,
|
|
387
|
+
// and why we skip if the event target is within the handle's DOM
|
|
388
|
+
compare(targetElement, dragHandleElement) > 0) {
|
|
389
|
+
// If the target is above the drag handle, then we also need to confirm they overlap
|
|
390
|
+
// If they are beside each other (e.g. a panel and its drag handle) then the handle is still interactive
|
|
391
|
+
//
|
|
392
|
+
// It's not enough to compare only the target
|
|
393
|
+
// The target might be a small element inside of a larger container
|
|
394
|
+
// (For example, a SPAN or a DIV inside of a larger modal dialog)
|
|
395
|
+
let currentElement = targetElement;
|
|
396
|
+
let didIntersect = false;
|
|
397
|
+
while (currentElement) {
|
|
398
|
+
if (currentElement.contains(dragHandleElement)) {
|
|
399
|
+
break;
|
|
400
|
+
} else if (intersects(currentElement.getBoundingClientRect(), dragHandleRect, true)) {
|
|
401
|
+
didIntersect = true;
|
|
402
|
+
break;
|
|
403
|
+
}
|
|
404
|
+
currentElement = currentElement.parentElement;
|
|
405
|
+
}
|
|
406
|
+
if (didIntersect) {
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
355
410
|
intersectingHandles.push(data);
|
|
356
411
|
}
|
|
357
412
|
});
|
|
@@ -443,15 +498,8 @@ function updateResizeHandlerStates(action, event) {
|
|
|
443
498
|
const {
|
|
444
499
|
setResizeHandlerState
|
|
445
500
|
} = data;
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
setResizeHandlerState(action, "drag", event);
|
|
449
|
-
} else {
|
|
450
|
-
setResizeHandlerState(action, "hover", event);
|
|
451
|
-
}
|
|
452
|
-
} else {
|
|
453
|
-
setResizeHandlerState(action, "inactive", event);
|
|
454
|
-
}
|
|
501
|
+
const isActive = intersectingHandles.includes(data);
|
|
502
|
+
setResizeHandlerState(action, isActive, event);
|
|
455
503
|
});
|
|
456
504
|
}
|
|
457
505
|
|
|
@@ -1804,6 +1852,12 @@ function PanelResizeHandle({
|
|
|
1804
1852
|
const [state, setState] = useState("inactive");
|
|
1805
1853
|
const [isFocused, setIsFocused] = useState(false);
|
|
1806
1854
|
const [resizeHandler, setResizeHandler] = useState(null);
|
|
1855
|
+
const committedValuesRef = useRef({
|
|
1856
|
+
state
|
|
1857
|
+
});
|
|
1858
|
+
useLayoutEffect(() => {
|
|
1859
|
+
committedValuesRef.current.state = state;
|
|
1860
|
+
});
|
|
1807
1861
|
useEffect(() => {
|
|
1808
1862
|
if (disabled) {
|
|
1809
1863
|
setResizeHandler(null);
|
|
@@ -1819,38 +1873,47 @@ function PanelResizeHandle({
|
|
|
1819
1873
|
}
|
|
1820
1874
|
const element = elementRef.current;
|
|
1821
1875
|
assert(element);
|
|
1822
|
-
const setResizeHandlerState = (action,
|
|
1823
|
-
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
onDragging
|
|
1876
|
+
const setResizeHandlerState = (action, isActive, event) => {
|
|
1877
|
+
if (isActive) {
|
|
1878
|
+
switch (action) {
|
|
1879
|
+
case "down":
|
|
1880
|
+
{
|
|
1881
|
+
setState("drag");
|
|
1882
|
+
startDragging(resizeHandleId, event);
|
|
1883
|
+
const {
|
|
1884
|
+
onDragging
|
|
1885
|
+
} = callbacksRef.current;
|
|
1886
|
+
if (onDragging) {
|
|
1887
|
+
onDragging(true);
|
|
1888
|
+
}
|
|
1889
|
+
break;
|
|
1833
1890
|
}
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1891
|
+
case "move":
|
|
1892
|
+
{
|
|
1893
|
+
const {
|
|
1894
|
+
state
|
|
1895
|
+
} = committedValuesRef.current;
|
|
1896
|
+
if (state !== "drag") {
|
|
1897
|
+
setState("hover");
|
|
1898
|
+
}
|
|
1899
|
+
resizeHandler(event);
|
|
1900
|
+
break;
|
|
1844
1901
|
}
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1902
|
+
case "up":
|
|
1903
|
+
{
|
|
1904
|
+
setState("hover");
|
|
1905
|
+
stopDragging();
|
|
1906
|
+
const {
|
|
1907
|
+
onDragging
|
|
1908
|
+
} = callbacksRef.current;
|
|
1909
|
+
if (onDragging) {
|
|
1910
|
+
onDragging(false);
|
|
1911
|
+
}
|
|
1912
|
+
break;
|
|
1913
|
+
}
|
|
1914
|
+
}
|
|
1915
|
+
} else {
|
|
1916
|
+
setState("inactive");
|
|
1854
1917
|
}
|
|
1855
1918
|
};
|
|
1856
1919
|
return registerResizeHandle(resizeHandleId, element, direction, {
|
|
@@ -1907,4 +1970,21 @@ function getPanelElementsForGroup(groupId, scope = document) {
|
|
|
1907
1970
|
return Array.from(scope.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
|
|
1908
1971
|
}
|
|
1909
1972
|
|
|
1910
|
-
|
|
1973
|
+
function getIntersectingRectangle(rectOne, rectTwo, strict) {
|
|
1974
|
+
if (!intersects(rectOne, rectTwo, strict)) {
|
|
1975
|
+
return {
|
|
1976
|
+
x: 0,
|
|
1977
|
+
y: 0,
|
|
1978
|
+
width: 0,
|
|
1979
|
+
height: 0
|
|
1980
|
+
};
|
|
1981
|
+
}
|
|
1982
|
+
return {
|
|
1983
|
+
x: Math.max(rectOne.x, rectTwo.x),
|
|
1984
|
+
y: Math.max(rectOne.y, rectTwo.y),
|
|
1985
|
+
width: Math.min(rectOne.x + rectOne.width, rectTwo.x + rectTwo.width) - Math.max(rectOne.x, rectTwo.x),
|
|
1986
|
+
height: Math.min(rectOne.y + rectOne.height, rectTwo.y + rectTwo.height) - Math.max(rectOne.y, rectTwo.y)
|
|
1987
|
+
};
|
|
1988
|
+
}
|
|
1989
|
+
|
|
1990
|
+
export { Panel, PanelGroup, PanelResizeHandle, assert, getIntersectingRectangle, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, intersects };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-resizable-panels",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.5",
|
|
4
4
|
"description": "React components for resizable panel groups/layouts",
|
|
5
5
|
"author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
|
|
6
6
|
"license": "MIT",
|
|
@@ -66,6 +66,9 @@
|
|
|
66
66
|
"test:watch": "jest --config=jest.config.js --watch",
|
|
67
67
|
"watch": "parcel watch --port=2345"
|
|
68
68
|
},
|
|
69
|
+
"dependencies": {
|
|
70
|
+
"stacking-order": "^1"
|
|
71
|
+
},
|
|
69
72
|
"devDependencies": {
|
|
70
73
|
"@babel/plugin-proposal-nullish-coalescing-operator": "7.18.6",
|
|
71
74
|
"@babel/plugin-proposal-optional-chaining": "7.21.0",
|
package/src/Panel.test.tsx
CHANGED
|
@@ -5,6 +5,7 @@ import { assert } from "./utils/assert";
|
|
|
5
5
|
import { getPanelElement } from "./utils/dom/getPanelElement";
|
|
6
6
|
import {
|
|
7
7
|
mockPanelGroupOffsetWidthAndHeight,
|
|
8
|
+
verifyAttribute,
|
|
8
9
|
verifyExpandedPanelGroupLayout,
|
|
9
10
|
} from "./utils/test-utils";
|
|
10
11
|
import { createRef } from "./vendor/react";
|
|
@@ -675,6 +676,68 @@ describe("PanelGroup", () => {
|
|
|
675
676
|
});
|
|
676
677
|
});
|
|
677
678
|
|
|
679
|
+
describe("data attributes", () => {
|
|
680
|
+
it("should initialize with the correct props based attributes", () => {
|
|
681
|
+
act(() => {
|
|
682
|
+
root.render(
|
|
683
|
+
<PanelGroup direction="horizontal" id="test-group">
|
|
684
|
+
<Panel defaultSize={75} id="left-panel" />
|
|
685
|
+
<PanelResizeHandle />
|
|
686
|
+
<Panel collapsible id="right-panel" />
|
|
687
|
+
</PanelGroup>
|
|
688
|
+
);
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
const leftElement = getPanelElement("left-panel", container);
|
|
692
|
+
const rightElement = getPanelElement("right-panel", container);
|
|
693
|
+
|
|
694
|
+
assert(leftElement);
|
|
695
|
+
assert(rightElement);
|
|
696
|
+
|
|
697
|
+
verifyAttribute(leftElement, "data-panel", "");
|
|
698
|
+
verifyAttribute(leftElement, "data-panel-id", "left-panel");
|
|
699
|
+
verifyAttribute(leftElement, "data-panel-group-id", "test-group");
|
|
700
|
+
verifyAttribute(leftElement, "data-panel-size", "75.0");
|
|
701
|
+
verifyAttribute(leftElement, "data-panel-collapsible", null);
|
|
702
|
+
|
|
703
|
+
verifyAttribute(rightElement, "data-panel", "");
|
|
704
|
+
verifyAttribute(rightElement, "data-panel-id", "right-panel");
|
|
705
|
+
verifyAttribute(rightElement, "data-panel-group-id", "test-group");
|
|
706
|
+
verifyAttribute(rightElement, "data-panel-size", "25.0");
|
|
707
|
+
verifyAttribute(rightElement, "data-panel-collapsible", "true");
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it("should update the data-panel-size attribute when the panel resizes", () => {
|
|
711
|
+
const leftPanelRef = createRef<ImperativePanelHandle>();
|
|
712
|
+
|
|
713
|
+
act(() => {
|
|
714
|
+
root.render(
|
|
715
|
+
<PanelGroup direction="horizontal" id="test-group">
|
|
716
|
+
<Panel defaultSize={75} id="left-panel" ref={leftPanelRef} />
|
|
717
|
+
<PanelResizeHandle />
|
|
718
|
+
<Panel collapsible id="right-panel" />
|
|
719
|
+
</PanelGroup>
|
|
720
|
+
);
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
const leftElement = getPanelElement("left-panel", container);
|
|
724
|
+
const rightElement = getPanelElement("right-panel", container);
|
|
725
|
+
|
|
726
|
+
assert(leftElement);
|
|
727
|
+
assert(rightElement);
|
|
728
|
+
|
|
729
|
+
verifyAttribute(leftElement, "data-panel-size", "75.0");
|
|
730
|
+
verifyAttribute(rightElement, "data-panel-size", "25.0");
|
|
731
|
+
|
|
732
|
+
act(() => {
|
|
733
|
+
leftPanelRef.current?.resize(30);
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
verifyAttribute(leftElement, "data-panel-size", "30.0");
|
|
737
|
+
verifyAttribute(rightElement, "data-panel-size", "70.0");
|
|
738
|
+
});
|
|
739
|
+
});
|
|
740
|
+
|
|
678
741
|
describe("DEV warnings", () => {
|
|
679
742
|
it("should warn about server rendered panels with no default size", () => {
|
|
680
743
|
jest.resetModules();
|
package/src/PanelGroup.test.tsx
CHANGED
|
@@ -13,7 +13,10 @@ import {
|
|
|
13
13
|
} from ".";
|
|
14
14
|
import { assert } from "./utils/assert";
|
|
15
15
|
import { getPanelGroupElement } from "./utils/dom/getPanelGroupElement";
|
|
16
|
-
import {
|
|
16
|
+
import {
|
|
17
|
+
mockPanelGroupOffsetWidthAndHeight,
|
|
18
|
+
verifyAttribute,
|
|
19
|
+
} from "./utils/test-utils";
|
|
17
20
|
import { createRef } from "./vendor/react";
|
|
18
21
|
|
|
19
22
|
describe("PanelGroup", () => {
|
|
@@ -256,6 +259,23 @@ describe("PanelGroup", () => {
|
|
|
256
259
|
});
|
|
257
260
|
});
|
|
258
261
|
|
|
262
|
+
describe("data attributes", () => {
|
|
263
|
+
it("should initialize with the correct props based attributes", () => {
|
|
264
|
+
act(() => {
|
|
265
|
+
root.render(
|
|
266
|
+
<PanelGroup direction="horizontal" id="test-group"></PanelGroup>
|
|
267
|
+
);
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
const element = getPanelGroupElement("test-group", container);
|
|
271
|
+
assert(element);
|
|
272
|
+
|
|
273
|
+
verifyAttribute(element, "data-panel-group", "");
|
|
274
|
+
verifyAttribute(element, "data-panel-group-direction", "horizontal");
|
|
275
|
+
verifyAttribute(element, "data-panel-group-id", "test-group");
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
259
279
|
describe("DEV warnings", () => {
|
|
260
280
|
it("should warn about unstable layouts without id and order props", () => {
|
|
261
281
|
act(() => {
|
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
import { Root, createRoot } from "react-dom/client";
|
|
2
2
|
import { act } from "react-dom/test-utils";
|
|
3
|
+
import type { PanelResizeHandleProps } from "react-resizable-panels";
|
|
3
4
|
import { Panel, PanelGroup, PanelResizeHandle } from ".";
|
|
4
5
|
import { assert } from "./utils/assert";
|
|
5
6
|
import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
dispatchPointerEvent,
|
|
9
|
+
mockBoundingClientRect,
|
|
10
|
+
verifyAttribute,
|
|
11
|
+
} from "./utils/test-utils";
|
|
7
12
|
|
|
8
13
|
describe("PanelResizeHandle", () => {
|
|
9
14
|
let expectedWarnings: string[] = [];
|
|
@@ -67,47 +72,201 @@ describe("PanelResizeHandle", () => {
|
|
|
67
72
|
expect(element.title).toBe("bar");
|
|
68
73
|
});
|
|
69
74
|
|
|
75
|
+
function setupMockedGroup({
|
|
76
|
+
leftProps = {},
|
|
77
|
+
rightProps = {},
|
|
78
|
+
}: {
|
|
79
|
+
leftProps?: Partial<PanelResizeHandleProps>;
|
|
80
|
+
rightProps?: Partial<PanelResizeHandleProps>;
|
|
81
|
+
} = {}) {
|
|
82
|
+
act(() => {
|
|
83
|
+
root.render(
|
|
84
|
+
<PanelGroup direction="horizontal" id="test-group">
|
|
85
|
+
<Panel />
|
|
86
|
+
<PanelResizeHandle id="handle-left" tabIndex={1} {...leftProps} />
|
|
87
|
+
<Panel />
|
|
88
|
+
<PanelResizeHandle id="handle-right" tabIndex={2} {...rightProps} />
|
|
89
|
+
<Panel />
|
|
90
|
+
</PanelGroup>
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
const leftElement = getResizeHandleElement("handle-left", container);
|
|
95
|
+
const rightElement = getResizeHandleElement("handle-right", container);
|
|
96
|
+
|
|
97
|
+
assert(leftElement);
|
|
98
|
+
assert(rightElement);
|
|
99
|
+
|
|
100
|
+
// JSDom doesn't properly handle bounding rects
|
|
101
|
+
mockBoundingClientRect(leftElement, {
|
|
102
|
+
x: 50,
|
|
103
|
+
y: 0,
|
|
104
|
+
height: 50,
|
|
105
|
+
width: 2,
|
|
106
|
+
});
|
|
107
|
+
mockBoundingClientRect(rightElement, {
|
|
108
|
+
x: 100,
|
|
109
|
+
y: 0,
|
|
110
|
+
height: 50,
|
|
111
|
+
width: 2,
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
return {
|
|
115
|
+
leftElement,
|
|
116
|
+
rightElement,
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
describe("callbacks", () => {
|
|
71
121
|
describe("onDragging", () => {
|
|
72
|
-
it("should fire when dragging starts/stops",
|
|
122
|
+
it("should fire when dragging starts/stops", () => {
|
|
73
123
|
const onDragging = jest.fn();
|
|
74
124
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
<PanelGroup direction="horizontal">
|
|
78
|
-
<Panel />
|
|
79
|
-
<PanelResizeHandle
|
|
80
|
-
id="handle"
|
|
81
|
-
onDragging={onDragging}
|
|
82
|
-
tabIndex={123}
|
|
83
|
-
title="bar"
|
|
84
|
-
/>
|
|
85
|
-
<Panel />
|
|
86
|
-
</PanelGroup>
|
|
87
|
-
);
|
|
125
|
+
const { leftElement } = setupMockedGroup({
|
|
126
|
+
leftProps: { onDragging },
|
|
88
127
|
});
|
|
89
128
|
|
|
90
|
-
const handleElement = container.querySelector(
|
|
91
|
-
'[data-panel-resize-handle-id="handle"]'
|
|
92
|
-
) as HTMLElement;
|
|
93
|
-
|
|
94
129
|
act(() => {
|
|
95
|
-
dispatchPointerEvent("
|
|
130
|
+
dispatchPointerEvent("mousemove", leftElement);
|
|
96
131
|
});
|
|
97
132
|
expect(onDragging).not.toHaveBeenCalled();
|
|
98
133
|
|
|
99
134
|
act(() => {
|
|
100
|
-
dispatchPointerEvent("mousedown",
|
|
135
|
+
dispatchPointerEvent("mousedown", leftElement);
|
|
101
136
|
});
|
|
102
137
|
expect(onDragging).toHaveBeenCalledTimes(1);
|
|
103
138
|
expect(onDragging).toHaveBeenCalledWith(true);
|
|
104
139
|
|
|
105
140
|
act(() => {
|
|
106
|
-
dispatchPointerEvent("mouseup",
|
|
141
|
+
dispatchPointerEvent("mouseup", leftElement);
|
|
107
142
|
});
|
|
108
143
|
expect(onDragging).toHaveBeenCalledTimes(2);
|
|
109
144
|
expect(onDragging).toHaveBeenCalledWith(false);
|
|
110
145
|
});
|
|
146
|
+
|
|
147
|
+
it("should only fire for the handle that has been dragged", () => {
|
|
148
|
+
const onDraggingLeft = jest.fn();
|
|
149
|
+
const onDraggingRight = jest.fn();
|
|
150
|
+
|
|
151
|
+
const { leftElement } = setupMockedGroup({
|
|
152
|
+
leftProps: { onDragging: onDraggingLeft },
|
|
153
|
+
rightProps: { onDragging: onDraggingRight },
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
act(() => {
|
|
157
|
+
dispatchPointerEvent("mousemove", leftElement);
|
|
158
|
+
});
|
|
159
|
+
expect(onDraggingLeft).not.toHaveBeenCalled();
|
|
160
|
+
expect(onDraggingRight).not.toHaveBeenCalled();
|
|
161
|
+
|
|
162
|
+
act(() => {
|
|
163
|
+
dispatchPointerEvent("mousedown", leftElement);
|
|
164
|
+
});
|
|
165
|
+
expect(onDraggingLeft).toHaveBeenCalledTimes(1);
|
|
166
|
+
expect(onDraggingLeft).toHaveBeenCalledWith(true);
|
|
167
|
+
expect(onDraggingRight).not.toHaveBeenCalled();
|
|
168
|
+
|
|
169
|
+
act(() => {
|
|
170
|
+
dispatchPointerEvent("mouseup", leftElement);
|
|
171
|
+
});
|
|
172
|
+
expect(onDraggingLeft).toHaveBeenCalledTimes(2);
|
|
173
|
+
expect(onDraggingLeft).toHaveBeenCalledWith(false);
|
|
174
|
+
expect(onDraggingRight).not.toHaveBeenCalled();
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
describe("data attributes", () => {
|
|
180
|
+
it("should initialize with the correct props based attributes", () => {
|
|
181
|
+
const { leftElement, rightElement } = setupMockedGroup();
|
|
182
|
+
|
|
183
|
+
verifyAttribute(leftElement, "data-panel-group-id", "test-group");
|
|
184
|
+
verifyAttribute(leftElement, "data-resize-handle", "");
|
|
185
|
+
verifyAttribute(leftElement, "data-panel-group-direction", "horizontal");
|
|
186
|
+
verifyAttribute(leftElement, "data-panel-resize-handle-enabled", "true");
|
|
187
|
+
verifyAttribute(
|
|
188
|
+
leftElement,
|
|
189
|
+
"data-panel-resize-handle-id",
|
|
190
|
+
"handle-left"
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
verifyAttribute(rightElement, "data-panel-group-id", "test-group");
|
|
194
|
+
verifyAttribute(rightElement, "data-resize-handle", "");
|
|
195
|
+
verifyAttribute(rightElement, "data-panel-group-direction", "horizontal");
|
|
196
|
+
verifyAttribute(rightElement, "data-panel-resize-handle-enabled", "true");
|
|
197
|
+
verifyAttribute(
|
|
198
|
+
rightElement,
|
|
199
|
+
"data-panel-resize-handle-id",
|
|
200
|
+
"handle-right"
|
|
201
|
+
);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it("should update data-resize-handle-active and data-resize-handle-state when dragging starts/stops", () => {
|
|
205
|
+
const { leftElement, rightElement } = setupMockedGroup();
|
|
206
|
+
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
207
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
208
|
+
verifyAttribute(leftElement, "data-resize-handle-state", "inactive");
|
|
209
|
+
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
210
|
+
|
|
211
|
+
act(() => {
|
|
212
|
+
dispatchPointerEvent("mousemove", leftElement);
|
|
213
|
+
});
|
|
214
|
+
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
215
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
216
|
+
verifyAttribute(leftElement, "data-resize-handle-state", "hover");
|
|
217
|
+
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
218
|
+
|
|
219
|
+
act(() => {
|
|
220
|
+
dispatchPointerEvent("mousedown", leftElement);
|
|
221
|
+
});
|
|
222
|
+
verifyAttribute(leftElement, "data-resize-handle-active", "pointer");
|
|
223
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
224
|
+
verifyAttribute(leftElement, "data-resize-handle-state", "drag");
|
|
225
|
+
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
226
|
+
|
|
227
|
+
act(() => {
|
|
228
|
+
dispatchPointerEvent("mousemove", leftElement);
|
|
229
|
+
});
|
|
230
|
+
verifyAttribute(leftElement, "data-resize-handle-active", "pointer");
|
|
231
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
232
|
+
verifyAttribute(leftElement, "data-resize-handle-state", "drag");
|
|
233
|
+
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
234
|
+
|
|
235
|
+
act(() => {
|
|
236
|
+
dispatchPointerEvent("mouseup", leftElement);
|
|
237
|
+
});
|
|
238
|
+
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
239
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
240
|
+
verifyAttribute(leftElement, "data-resize-handle-state", "hover");
|
|
241
|
+
verifyAttribute(rightElement, "data-resize-handle-state", "inactive");
|
|
242
|
+
|
|
243
|
+
act(() => {
|
|
244
|
+
dispatchPointerEvent("mousemove", rightElement);
|
|
245
|
+
});
|
|
246
|
+
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
247
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
248
|
+
verifyAttribute(leftElement, "data-resize-handle-state", "inactive");
|
|
249
|
+
verifyAttribute(rightElement, "data-resize-handle-state", "hover");
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
it("should update data-resize-handle-active when focused", () => {
|
|
253
|
+
const { leftElement, rightElement } = setupMockedGroup();
|
|
254
|
+
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
255
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
256
|
+
|
|
257
|
+
act(() => {
|
|
258
|
+
leftElement.focus();
|
|
259
|
+
});
|
|
260
|
+
expect(document.activeElement).toBe(leftElement);
|
|
261
|
+
verifyAttribute(leftElement, "data-resize-handle-active", "keyboard");
|
|
262
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
263
|
+
|
|
264
|
+
act(() => {
|
|
265
|
+
leftElement.blur();
|
|
266
|
+
});
|
|
267
|
+
expect(document.activeElement).not.toBe(leftElement);
|
|
268
|
+
verifyAttribute(leftElement, "data-resize-handle-active", null);
|
|
269
|
+
verifyAttribute(rightElement, "data-resize-handle-active", null);
|
|
111
270
|
});
|
|
112
271
|
});
|
|
113
272
|
});
|