react-resizable-panels 2.0.4 → 2.0.6

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/dist/declarations/src/index.d.ts +3 -1
  3. package/dist/declarations/src/utils/rects/getIntersectingRectangle.d.ts +2 -0
  4. package/dist/declarations/src/utils/rects/intersects.d.ts +2 -0
  5. package/dist/declarations/src/utils/rects/types.d.ts +6 -0
  6. package/dist/declarations/src/vendor/react.d.ts +3 -2
  7. package/dist/react-resizable-panels.browser.cjs.js +87 -7
  8. package/dist/react-resizable-panels.browser.cjs.mjs +3 -1
  9. package/dist/react-resizable-panels.browser.development.cjs.js +87 -7
  10. package/dist/react-resizable-panels.browser.development.cjs.mjs +3 -1
  11. package/dist/react-resizable-panels.browser.development.esm.js +86 -8
  12. package/dist/react-resizable-panels.browser.esm.js +86 -8
  13. package/dist/react-resizable-panels.cjs.js +87 -7
  14. package/dist/react-resizable-panels.cjs.mjs +3 -1
  15. package/dist/react-resizable-panels.development.cjs.js +87 -7
  16. package/dist/react-resizable-panels.development.cjs.mjs +3 -1
  17. package/dist/react-resizable-panels.development.esm.js +86 -8
  18. package/dist/react-resizable-panels.development.node.cjs.js +84 -8
  19. package/dist/react-resizable-panels.development.node.cjs.mjs +3 -1
  20. package/dist/react-resizable-panels.development.node.esm.js +83 -9
  21. package/dist/react-resizable-panels.esm.js +86 -8
  22. package/dist/react-resizable-panels.node.cjs.js +84 -8
  23. package/dist/react-resizable-panels.node.cjs.mjs +3 -1
  24. package/dist/react-resizable-panels.node.esm.js +83 -9
  25. package/package.json +4 -1
  26. package/src/PanelResizeHandle.ts +2 -2
  27. package/src/PanelResizeHandleRegistry.ts +75 -8
  28. package/src/hooks/useIsomorphicEffect.ts +4 -2
  29. package/src/index.ts +4 -0
  30. package/src/utils/rects/getIntersectingRectangle.test.ts +198 -0
  31. package/src/utils/rects/getIntersectingRectangle.ts +28 -0
  32. package/src/utils/rects/intersects.test.ts +197 -0
  33. package/src/utils/rects/intersects.ts +23 -0
  34. package/src/utils/rects/types.ts +6 -0
  35. package/src/vendor/react.ts +3 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,13 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.6
4
+
5
+ - Replace `useLayoutEffect` usage with SSR-safe wrapper hook (#294)
6
+
7
+ ## 2.0.5
8
+
9
+ - Resize handle hit detection considers [stacking context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context) when determining hit detection (#291)
10
+
3
11
  ## 2.0.4
4
12
 
5
13
  - Fixed `PanelResizeHandle` `onDragging` prop to only be called for the handle being dragged (#289)
@@ -9,7 +9,9 @@ import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement.js";
9
9
  import { getResizeHandleElementIndex } from "./utils/dom/getResizeHandleElementIndex.js";
10
10
  import { getResizeHandleElementsForGroup } from "./utils/dom/getResizeHandleElementsForGroup.js";
11
11
  import { getResizeHandlePanelIds } from "./utils/dom/getResizeHandlePanelIds.js";
12
+ import { getIntersectingRectangle } from "./utils/rects/getIntersectingRectangle.js";
13
+ import { intersects } from "./utils/rects/intersects.js";
12
14
  import type { ImperativePanelHandle, PanelOnCollapse, PanelOnExpand, PanelOnResize, PanelProps } from "./Panel.js";
13
15
  import type { ImperativePanelGroupHandle, PanelGroupOnLayout, PanelGroupProps, PanelGroupStorage } from "./PanelGroup.js";
14
16
  import type { PanelResizeHandleOnDragging, PanelResizeHandleProps } from "./PanelResizeHandle.js";
15
- export { ImperativePanelGroupHandle, ImperativePanelHandle, PanelGroupOnLayout, PanelGroupProps, PanelGroupStorage, PanelOnCollapse, PanelOnExpand, PanelOnResize, PanelProps, PanelResizeHandleOnDragging, PanelResizeHandleProps, Panel, PanelGroup, PanelResizeHandle, assert, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, };
17
+ export { ImperativePanelGroupHandle, ImperativePanelHandle, PanelGroupOnLayout, PanelGroupProps, PanelGroupStorage, PanelOnCollapse, PanelOnExpand, PanelOnResize, PanelProps, PanelResizeHandleOnDragging, PanelResizeHandleProps, Panel, PanelGroup, PanelResizeHandle, assert, getIntersectingRectangle, intersects, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, };
@@ -0,0 +1,2 @@
1
+ import { Rectangle } from "./types.js";
2
+ export declare function getIntersectingRectangle(rectOne: Rectangle, rectTwo: Rectangle, strict: boolean): Rectangle;
@@ -0,0 +1,2 @@
1
+ import { Rectangle } from "./types.js";
2
+ export declare function intersects(rectOne: Rectangle, rectTwo: Rectangle, strict: boolean): boolean;
@@ -0,0 +1,6 @@
1
+ export interface Rectangle {
2
+ x: number;
3
+ y: number;
4
+ width: number;
5
+ height: number;
6
+ }
@@ -1,6 +1,7 @@
1
1
  import * as React from "react";
2
2
  import type { CSSProperties, ElementType, ForwardedRef, HTMLAttributes, MouseEvent, PropsWithChildren, ReactElement, ReactNode, RefObject, TouchEvent } from "react";
3
- declare const createElement: typeof React.createElement, createContext: typeof React.createContext, createRef: typeof React.createRef, forwardRef: typeof React.forwardRef, useCallback: typeof React.useCallback, useContext: typeof React.useContext, useEffect: typeof React.useEffect, useImperativeHandle: typeof React.useImperativeHandle, useLayoutEffect: typeof React.useLayoutEffect, useMemo: typeof React.useMemo, useRef: typeof React.useRef, useState: typeof React.useState;
3
+ declare const createElement: typeof React.createElement, createContext: typeof React.createContext, createRef: typeof React.createRef, forwardRef: typeof React.forwardRef, useCallback: typeof React.useCallback, useContext: typeof React.useContext, useEffect: typeof React.useEffect, useImperativeHandle: typeof React.useImperativeHandle, useMemo: typeof React.useMemo, useRef: typeof React.useRef, useState: typeof React.useState;
4
4
  declare const useId: () => string;
5
- export { createElement, createContext, createRef, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, };
5
+ declare const useLayoutEffect_do_not_use_directly: typeof React.useLayoutEffect;
6
+ export { createElement, createContext, createRef, forwardRef, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect_do_not_use_directly, useMemo, useRef, useState, };
6
7
  export type { CSSProperties, ElementType, ForwardedRef, HTMLAttributes, MouseEvent, PropsWithChildren, ReactElement, ReactNode, RefObject, TouchEvent, };
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
+ var stackingOrder = require('stacking-order');
6
7
 
7
8
  function _interopNamespace(e) {
8
9
  if (e && e.__esModule) return e;
@@ -45,11 +46,12 @@ const {
45
46
 
46
47
  // `toString()` prevents bundlers from trying to `import { useId } from 'react'`
47
48
  const useId = React__namespace["useId".toString()];
49
+ const useLayoutEffect_do_not_use_directly = useLayoutEffect;
48
50
 
49
51
  const PanelGroupContext = createContext(null);
50
52
  PanelGroupContext.displayName = "PanelGroupContext";
51
53
 
52
- const useIsomorphicLayoutEffect = useLayoutEffect ;
54
+ const useIsomorphicLayoutEffect = useLayoutEffect_do_not_use_directly ;
53
55
 
54
56
  const wrappedUseId = typeof useId === "function" ? useId : () => null;
55
57
  let counter = 0;
@@ -294,6 +296,14 @@ function getInputType() {
294
296
  }
295
297
  }
296
298
 
299
+ function intersects(rectOne, rectTwo, strict) {
300
+ if (strict) {
301
+ 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;
302
+ } else {
303
+ 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;
304
+ }
305
+ }
306
+
297
307
  const EXCEEDED_HORIZONTAL_MIN = 0b0001;
298
308
  const EXCEEDED_HORIZONTAL_MAX = 0b0010;
299
309
  const EXCEEDED_VERTICAL_MIN = 0b0100;
@@ -332,12 +342,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
332
342
  };
333
343
  }
334
344
  function handlePointerDown(event) {
345
+ const {
346
+ target
347
+ } = event;
335
348
  const {
336
349
  x,
337
350
  y
338
351
  } = getResizeEventCoordinates(event);
339
352
  isPointerDown = true;
340
353
  recalculateIntersectingHandles({
354
+ target,
341
355
  x,
342
356
  y
343
357
  });
@@ -353,10 +367,15 @@ function handlePointerMove(event) {
353
367
  y
354
368
  } = getResizeEventCoordinates(event);
355
369
  if (!isPointerDown) {
370
+ const {
371
+ target
372
+ } = event;
373
+
356
374
  // Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
357
375
  // at that point, the handles may not move with the pointer (depending on constraints)
358
376
  // but the same set of active handles should be locked until the pointer is released
359
377
  recalculateIntersectingHandles({
378
+ target,
360
379
  x,
361
380
  y
362
381
  });
@@ -370,6 +389,9 @@ function handlePointerMove(event) {
370
389
  }
371
390
  }
372
391
  function handlePointerUp(event) {
392
+ const {
393
+ target
394
+ } = event;
373
395
  const {
374
396
  x,
375
397
  y
@@ -379,33 +401,72 @@ function handlePointerUp(event) {
379
401
  if (intersectingHandles.length > 0) {
380
402
  event.preventDefault();
381
403
  }
404
+ updateResizeHandlerStates("up", event);
382
405
  recalculateIntersectingHandles({
406
+ target,
383
407
  x,
384
408
  y
385
409
  });
386
- updateResizeHandlerStates("up", event);
387
410
  updateCursor();
388
411
  updateListeners();
389
412
  }
390
413
  function recalculateIntersectingHandles({
414
+ target,
391
415
  x,
392
416
  y
393
417
  }) {
394
418
  intersectingHandles.splice(0);
419
+ let targetElement = null;
420
+ if (target instanceof HTMLElement) {
421
+ targetElement = target;
422
+ }
395
423
  registeredResizeHandlers.forEach(data => {
396
424
  const {
397
- element,
425
+ element: dragHandleElement,
398
426
  hitAreaMargins
399
427
  } = data;
428
+ const dragHandleRect = dragHandleElement.getBoundingClientRect();
400
429
  const {
401
430
  bottom,
402
431
  left,
403
432
  right,
404
433
  top
405
- } = element.getBoundingClientRect();
434
+ } = dragHandleRect;
406
435
  const margin = isCoarsePointer ? hitAreaMargins.coarse : hitAreaMargins.fine;
407
- const intersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
408
- if (intersects) {
436
+ const eventIntersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
437
+ if (eventIntersects) {
438
+ // TRICKY
439
+ // We listen for pointers events at the root in order to support hit area margins
440
+ // (determining when the pointer is close enough to an element to be considered a "hit")
441
+ // Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
442
+ // so at this point we need to compare stacking order of a potentially intersecting drag handle,
443
+ // and the element that was actually clicked/touched
444
+ if (targetElement !== null && dragHandleElement !== targetElement && !dragHandleElement.contains(targetElement) && !targetElement.contains(dragHandleElement) &&
445
+ // Calculating stacking order has a cost, so we should avoid it if possible
446
+ // That is why we only check potentially intersecting handles,
447
+ // and why we skip if the event target is within the handle's DOM
448
+ stackingOrder.compare(targetElement, dragHandleElement) > 0) {
449
+ // If the target is above the drag handle, then we also need to confirm they overlap
450
+ // If they are beside each other (e.g. a panel and its drag handle) then the handle is still interactive
451
+ //
452
+ // It's not enough to compare only the target
453
+ // The target might be a small element inside of a larger container
454
+ // (For example, a SPAN or a DIV inside of a larger modal dialog)
455
+ let currentElement = targetElement;
456
+ let didIntersect = false;
457
+ while (currentElement) {
458
+ if (currentElement.contains(dragHandleElement)) {
459
+ break;
460
+ } else if (intersects(currentElement.getBoundingClientRect(), dragHandleRect, true)) {
461
+ didIntersect = true;
462
+ break;
463
+ }
464
+ currentElement = currentElement.parentElement;
465
+ }
466
+ if (didIntersect) {
467
+ return;
468
+ }
469
+ }
409
470
  intersectingHandles.push(data);
410
471
  }
411
472
  });
@@ -2038,7 +2099,7 @@ function PanelResizeHandle({
2038
2099
  const committedValuesRef = useRef({
2039
2100
  state
2040
2101
  });
2041
- useLayoutEffect(() => {
2102
+ useIsomorphicLayoutEffect(() => {
2042
2103
  committedValuesRef.current.state = state;
2043
2104
  });
2044
2105
  useEffect(() => {
@@ -2153,10 +2214,28 @@ function getPanelElementsForGroup(groupId, scope = document) {
2153
2214
  return Array.from(scope.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
2154
2215
  }
2155
2216
 
2217
+ function getIntersectingRectangle(rectOne, rectTwo, strict) {
2218
+ if (!intersects(rectOne, rectTwo, strict)) {
2219
+ return {
2220
+ x: 0,
2221
+ y: 0,
2222
+ width: 0,
2223
+ height: 0
2224
+ };
2225
+ }
2226
+ return {
2227
+ x: Math.max(rectOne.x, rectTwo.x),
2228
+ y: Math.max(rectOne.y, rectTwo.y),
2229
+ width: Math.min(rectOne.x + rectOne.width, rectTwo.x + rectTwo.width) - Math.max(rectOne.x, rectTwo.x),
2230
+ height: Math.min(rectOne.y + rectOne.height, rectTwo.y + rectTwo.height) - Math.max(rectOne.y, rectTwo.y)
2231
+ };
2232
+ }
2233
+
2156
2234
  exports.Panel = Panel;
2157
2235
  exports.PanelGroup = PanelGroup;
2158
2236
  exports.PanelResizeHandle = PanelResizeHandle;
2159
2237
  exports.assert = assert;
2238
+ exports.getIntersectingRectangle = getIntersectingRectangle;
2160
2239
  exports.getPanelElement = getPanelElement;
2161
2240
  exports.getPanelElementsForGroup = getPanelElementsForGroup;
2162
2241
  exports.getPanelGroupElement = getPanelGroupElement;
@@ -2164,3 +2243,4 @@ exports.getResizeHandleElement = getResizeHandleElement;
2164
2243
  exports.getResizeHandleElementIndex = getResizeHandleElementIndex;
2165
2244
  exports.getResizeHandleElementsForGroup = getResizeHandleElementsForGroup;
2166
2245
  exports.getResizeHandlePanelIds = getResizeHandlePanelIds;
2246
+ exports.intersects = intersects;
@@ -3,11 +3,13 @@ export {
3
3
  PanelGroup,
4
4
  PanelResizeHandle,
5
5
  assert,
6
+ getIntersectingRectangle,
6
7
  getPanelElement,
7
8
  getPanelElementsForGroup,
8
9
  getPanelGroupElement,
9
10
  getResizeHandleElement,
10
11
  getResizeHandleElementIndex,
11
12
  getResizeHandleElementsForGroup,
12
- getResizeHandlePanelIds
13
+ getResizeHandlePanelIds,
14
+ intersects
13
15
  } from "./react-resizable-panels.browser.cjs.js";
@@ -3,6 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var React = require('react');
6
+ var stackingOrder = require('stacking-order');
6
7
 
7
8
  function _interopNamespace(e) {
8
9
  if (e && e.__esModule) return e;
@@ -45,11 +46,12 @@ const {
45
46
 
46
47
  // `toString()` prevents bundlers from trying to `import { useId } from 'react'`
47
48
  const useId = React__namespace["useId".toString()];
49
+ const useLayoutEffect_do_not_use_directly = useLayoutEffect;
48
50
 
49
51
  const PanelGroupContext = createContext(null);
50
52
  PanelGroupContext.displayName = "PanelGroupContext";
51
53
 
52
- const useIsomorphicLayoutEffect = useLayoutEffect ;
54
+ const useIsomorphicLayoutEffect = useLayoutEffect_do_not_use_directly ;
53
55
 
54
56
  const wrappedUseId = typeof useId === "function" ? useId : () => null;
55
57
  let counter = 0;
@@ -300,6 +302,14 @@ function getInputType() {
300
302
  }
301
303
  }
302
304
 
305
+ function intersects(rectOne, rectTwo, strict) {
306
+ if (strict) {
307
+ 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;
308
+ } else {
309
+ 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;
310
+ }
311
+ }
312
+
303
313
  const EXCEEDED_HORIZONTAL_MIN = 0b0001;
304
314
  const EXCEEDED_HORIZONTAL_MAX = 0b0010;
305
315
  const EXCEEDED_VERTICAL_MIN = 0b0100;
@@ -338,12 +348,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
338
348
  };
339
349
  }
340
350
  function handlePointerDown(event) {
351
+ const {
352
+ target
353
+ } = event;
341
354
  const {
342
355
  x,
343
356
  y
344
357
  } = getResizeEventCoordinates(event);
345
358
  isPointerDown = true;
346
359
  recalculateIntersectingHandles({
360
+ target,
347
361
  x,
348
362
  y
349
363
  });
@@ -359,10 +373,15 @@ function handlePointerMove(event) {
359
373
  y
360
374
  } = getResizeEventCoordinates(event);
361
375
  if (!isPointerDown) {
376
+ const {
377
+ target
378
+ } = event;
379
+
362
380
  // Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
363
381
  // at that point, the handles may not move with the pointer (depending on constraints)
364
382
  // but the same set of active handles should be locked until the pointer is released
365
383
  recalculateIntersectingHandles({
384
+ target,
366
385
  x,
367
386
  y
368
387
  });
@@ -376,6 +395,9 @@ function handlePointerMove(event) {
376
395
  }
377
396
  }
378
397
  function handlePointerUp(event) {
398
+ const {
399
+ target
400
+ } = event;
379
401
  const {
380
402
  x,
381
403
  y
@@ -385,33 +407,72 @@ function handlePointerUp(event) {
385
407
  if (intersectingHandles.length > 0) {
386
408
  event.preventDefault();
387
409
  }
410
+ updateResizeHandlerStates("up", event);
388
411
  recalculateIntersectingHandles({
412
+ target,
389
413
  x,
390
414
  y
391
415
  });
392
- updateResizeHandlerStates("up", event);
393
416
  updateCursor();
394
417
  updateListeners();
395
418
  }
396
419
  function recalculateIntersectingHandles({
420
+ target,
397
421
  x,
398
422
  y
399
423
  }) {
400
424
  intersectingHandles.splice(0);
425
+ let targetElement = null;
426
+ if (target instanceof HTMLElement) {
427
+ targetElement = target;
428
+ }
401
429
  registeredResizeHandlers.forEach(data => {
402
430
  const {
403
- element,
431
+ element: dragHandleElement,
404
432
  hitAreaMargins
405
433
  } = data;
434
+ const dragHandleRect = dragHandleElement.getBoundingClientRect();
406
435
  const {
407
436
  bottom,
408
437
  left,
409
438
  right,
410
439
  top
411
- } = element.getBoundingClientRect();
440
+ } = dragHandleRect;
412
441
  const margin = isCoarsePointer ? hitAreaMargins.coarse : hitAreaMargins.fine;
413
- const intersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
414
- if (intersects) {
442
+ const eventIntersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
443
+ if (eventIntersects) {
444
+ // TRICKY
445
+ // We listen for pointers events at the root in order to support hit area margins
446
+ // (determining when the pointer is close enough to an element to be considered a "hit")
447
+ // Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
448
+ // so at this point we need to compare stacking order of a potentially intersecting drag handle,
449
+ // and the element that was actually clicked/touched
450
+ if (targetElement !== null && dragHandleElement !== targetElement && !dragHandleElement.contains(targetElement) && !targetElement.contains(dragHandleElement) &&
451
+ // Calculating stacking order has a cost, so we should avoid it if possible
452
+ // That is why we only check potentially intersecting handles,
453
+ // and why we skip if the event target is within the handle's DOM
454
+ stackingOrder.compare(targetElement, dragHandleElement) > 0) {
455
+ // If the target is above the drag handle, then we also need to confirm they overlap
456
+ // If they are beside each other (e.g. a panel and its drag handle) then the handle is still interactive
457
+ //
458
+ // It's not enough to compare only the target
459
+ // The target might be a small element inside of a larger container
460
+ // (For example, a SPAN or a DIV inside of a larger modal dialog)
461
+ let currentElement = targetElement;
462
+ let didIntersect = false;
463
+ while (currentElement) {
464
+ if (currentElement.contains(dragHandleElement)) {
465
+ break;
466
+ } else if (intersects(currentElement.getBoundingClientRect(), dragHandleRect, true)) {
467
+ didIntersect = true;
468
+ break;
469
+ }
470
+ currentElement = currentElement.parentElement;
471
+ }
472
+ if (didIntersect) {
473
+ return;
474
+ }
475
+ }
415
476
  intersectingHandles.push(data);
416
477
  }
417
478
  });
@@ -2144,7 +2205,7 @@ function PanelResizeHandle({
2144
2205
  const committedValuesRef = useRef({
2145
2206
  state
2146
2207
  });
2147
- useLayoutEffect(() => {
2208
+ useIsomorphicLayoutEffect(() => {
2148
2209
  committedValuesRef.current.state = state;
2149
2210
  });
2150
2211
  useEffect(() => {
@@ -2259,10 +2320,28 @@ function getPanelElementsForGroup(groupId, scope = document) {
2259
2320
  return Array.from(scope.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
2260
2321
  }
2261
2322
 
2323
+ function getIntersectingRectangle(rectOne, rectTwo, strict) {
2324
+ if (!intersects(rectOne, rectTwo, strict)) {
2325
+ return {
2326
+ x: 0,
2327
+ y: 0,
2328
+ width: 0,
2329
+ height: 0
2330
+ };
2331
+ }
2332
+ return {
2333
+ x: Math.max(rectOne.x, rectTwo.x),
2334
+ y: Math.max(rectOne.y, rectTwo.y),
2335
+ width: Math.min(rectOne.x + rectOne.width, rectTwo.x + rectTwo.width) - Math.max(rectOne.x, rectTwo.x),
2336
+ height: Math.min(rectOne.y + rectOne.height, rectTwo.y + rectTwo.height) - Math.max(rectOne.y, rectTwo.y)
2337
+ };
2338
+ }
2339
+
2262
2340
  exports.Panel = Panel;
2263
2341
  exports.PanelGroup = PanelGroup;
2264
2342
  exports.PanelResizeHandle = PanelResizeHandle;
2265
2343
  exports.assert = assert;
2344
+ exports.getIntersectingRectangle = getIntersectingRectangle;
2266
2345
  exports.getPanelElement = getPanelElement;
2267
2346
  exports.getPanelElementsForGroup = getPanelElementsForGroup;
2268
2347
  exports.getPanelGroupElement = getPanelGroupElement;
@@ -2270,3 +2349,4 @@ exports.getResizeHandleElement = getResizeHandleElement;
2270
2349
  exports.getResizeHandleElementIndex = getResizeHandleElementIndex;
2271
2350
  exports.getResizeHandleElementsForGroup = getResizeHandleElementsForGroup;
2272
2351
  exports.getResizeHandlePanelIds = getResizeHandlePanelIds;
2352
+ exports.intersects = intersects;
@@ -3,11 +3,13 @@ export {
3
3
  PanelGroup,
4
4
  PanelResizeHandle,
5
5
  assert,
6
+ getIntersectingRectangle,
6
7
  getPanelElement,
7
8
  getPanelElementsForGroup,
8
9
  getPanelGroupElement,
9
10
  getResizeHandleElement,
10
11
  getResizeHandleElementIndex,
11
12
  getResizeHandleElementsForGroup,
12
- getResizeHandlePanelIds
13
+ getResizeHandlePanelIds,
14
+ intersects
13
15
  } from "./react-resizable-panels.browser.development.cjs.js";
@@ -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
 
@@ -21,11 +22,12 @@ const {
21
22
 
22
23
  // `toString()` prevents bundlers from trying to `import { useId } from 'react'`
23
24
  const useId = React["useId".toString()];
25
+ const useLayoutEffect_do_not_use_directly = useLayoutEffect;
24
26
 
25
27
  const PanelGroupContext = createContext(null);
26
28
  PanelGroupContext.displayName = "PanelGroupContext";
27
29
 
28
- const useIsomorphicLayoutEffect = useLayoutEffect ;
30
+ const useIsomorphicLayoutEffect = useLayoutEffect_do_not_use_directly ;
29
31
 
30
32
  const wrappedUseId = typeof useId === "function" ? useId : () => null;
31
33
  let counter = 0;
@@ -276,6 +278,14 @@ function getInputType() {
276
278
  }
277
279
  }
278
280
 
281
+ function intersects(rectOne, rectTwo, strict) {
282
+ if (strict) {
283
+ 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;
284
+ } else {
285
+ 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;
286
+ }
287
+ }
288
+
279
289
  const EXCEEDED_HORIZONTAL_MIN = 0b0001;
280
290
  const EXCEEDED_HORIZONTAL_MAX = 0b0010;
281
291
  const EXCEEDED_VERTICAL_MIN = 0b0100;
@@ -314,12 +324,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
314
324
  };
315
325
  }
316
326
  function handlePointerDown(event) {
327
+ const {
328
+ target
329
+ } = event;
317
330
  const {
318
331
  x,
319
332
  y
320
333
  } = getResizeEventCoordinates(event);
321
334
  isPointerDown = true;
322
335
  recalculateIntersectingHandles({
336
+ target,
323
337
  x,
324
338
  y
325
339
  });
@@ -335,10 +349,15 @@ function handlePointerMove(event) {
335
349
  y
336
350
  } = getResizeEventCoordinates(event);
337
351
  if (!isPointerDown) {
352
+ const {
353
+ target
354
+ } = event;
355
+
338
356
  // Recalculate intersecting handles whenever the pointer moves, except if it has already been pressed
339
357
  // at that point, the handles may not move with the pointer (depending on constraints)
340
358
  // but the same set of active handles should be locked until the pointer is released
341
359
  recalculateIntersectingHandles({
360
+ target,
342
361
  x,
343
362
  y
344
363
  });
@@ -352,6 +371,9 @@ function handlePointerMove(event) {
352
371
  }
353
372
  }
354
373
  function handlePointerUp(event) {
374
+ const {
375
+ target
376
+ } = event;
355
377
  const {
356
378
  x,
357
379
  y
@@ -361,33 +383,72 @@ function handlePointerUp(event) {
361
383
  if (intersectingHandles.length > 0) {
362
384
  event.preventDefault();
363
385
  }
386
+ updateResizeHandlerStates("up", event);
364
387
  recalculateIntersectingHandles({
388
+ target,
365
389
  x,
366
390
  y
367
391
  });
368
- updateResizeHandlerStates("up", event);
369
392
  updateCursor();
370
393
  updateListeners();
371
394
  }
372
395
  function recalculateIntersectingHandles({
396
+ target,
373
397
  x,
374
398
  y
375
399
  }) {
376
400
  intersectingHandles.splice(0);
401
+ let targetElement = null;
402
+ if (target instanceof HTMLElement) {
403
+ targetElement = target;
404
+ }
377
405
  registeredResizeHandlers.forEach(data => {
378
406
  const {
379
- element,
407
+ element: dragHandleElement,
380
408
  hitAreaMargins
381
409
  } = data;
410
+ const dragHandleRect = dragHandleElement.getBoundingClientRect();
382
411
  const {
383
412
  bottom,
384
413
  left,
385
414
  right,
386
415
  top
387
- } = element.getBoundingClientRect();
416
+ } = dragHandleRect;
388
417
  const margin = isCoarsePointer ? hitAreaMargins.coarse : hitAreaMargins.fine;
389
- const intersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
390
- if (intersects) {
418
+ const eventIntersects = x >= left - margin && x <= right + margin && y >= top - margin && y <= bottom + margin;
419
+ if (eventIntersects) {
420
+ // TRICKY
421
+ // We listen for pointers events at the root in order to support hit area margins
422
+ // (determining when the pointer is close enough to an element to be considered a "hit")
423
+ // Clicking on an element "above" a handle (e.g. a modal) should prevent a hit though
424
+ // so at this point we need to compare stacking order of a potentially intersecting drag handle,
425
+ // and the element that was actually clicked/touched
426
+ if (targetElement !== null && dragHandleElement !== targetElement && !dragHandleElement.contains(targetElement) && !targetElement.contains(dragHandleElement) &&
427
+ // Calculating stacking order has a cost, so we should avoid it if possible
428
+ // That is why we only check potentially intersecting handles,
429
+ // and why we skip if the event target is within the handle's DOM
430
+ compare(targetElement, dragHandleElement) > 0) {
431
+ // If the target is above the drag handle, then we also need to confirm they overlap
432
+ // If they are beside each other (e.g. a panel and its drag handle) then the handle is still interactive
433
+ //
434
+ // It's not enough to compare only the target
435
+ // The target might be a small element inside of a larger container
436
+ // (For example, a SPAN or a DIV inside of a larger modal dialog)
437
+ let currentElement = targetElement;
438
+ let didIntersect = false;
439
+ while (currentElement) {
440
+ if (currentElement.contains(dragHandleElement)) {
441
+ break;
442
+ } else if (intersects(currentElement.getBoundingClientRect(), dragHandleRect, true)) {
443
+ didIntersect = true;
444
+ break;
445
+ }
446
+ currentElement = currentElement.parentElement;
447
+ }
448
+ if (didIntersect) {
449
+ return;
450
+ }
451
+ }
391
452
  intersectingHandles.push(data);
392
453
  }
393
454
  });
@@ -2120,7 +2181,7 @@ function PanelResizeHandle({
2120
2181
  const committedValuesRef = useRef({
2121
2182
  state
2122
2183
  });
2123
- useLayoutEffect(() => {
2184
+ useIsomorphicLayoutEffect(() => {
2124
2185
  committedValuesRef.current.state = state;
2125
2186
  });
2126
2187
  useEffect(() => {
@@ -2235,4 +2296,21 @@ function getPanelElementsForGroup(groupId, scope = document) {
2235
2296
  return Array.from(scope.querySelectorAll(`[data-panel][data-panel-group-id="${groupId}"]`));
2236
2297
  }
2237
2298
 
2238
- export { Panel, PanelGroup, PanelResizeHandle, assert, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds };
2299
+ function getIntersectingRectangle(rectOne, rectTwo, strict) {
2300
+ if (!intersects(rectOne, rectTwo, strict)) {
2301
+ return {
2302
+ x: 0,
2303
+ y: 0,
2304
+ width: 0,
2305
+ height: 0
2306
+ };
2307
+ }
2308
+ return {
2309
+ x: Math.max(rectOne.x, rectTwo.x),
2310
+ y: Math.max(rectOne.y, rectTwo.y),
2311
+ width: Math.min(rectOne.x + rectOne.width, rectTwo.x + rectTwo.width) - Math.max(rectOne.x, rectTwo.x),
2312
+ height: Math.min(rectOne.y + rectOne.height, rectTwo.y + rectTwo.height) - Math.max(rectOne.y, rectTwo.y)
2313
+ };
2314
+ }
2315
+
2316
+ export { Panel, PanelGroup, PanelResizeHandle, assert, getIntersectingRectangle, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, intersects };