react-resizable-panels 2.0.19 → 2.0.20

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 CHANGED
@@ -1,5 +1,10 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.20
4
+
5
+ - Reset global cursor if an active resize handle is unmounted (#313)
6
+ - Resize handle supports (optional) `onFocus` or `onBlur` props (#370)
7
+
3
8
  ## 2.0.19
4
9
 
5
10
  - Add optional `minSize` override param to panel `expand` imperative API
package/README.md CHANGED
@@ -223,4 +223,7 @@ export function ClientComponent({
223
223
  }
224
224
  ```
225
225
 
226
+ > [!NOTE]
227
+ > Be sure to specify a `defaultSize` prop for **every** `Panel` component to avoid layout flicker.
228
+
226
229
  A demo of this is available [here](https://github.com/bvaughn/react-resizable-panels-demo-ssr).
@@ -1,18 +1,20 @@
1
- import { CSSProperties, HTMLAttributes, PropsWithChildren, ReactElement } from "./vendor/react.js";
2
1
  import { PointerHitAreaMargins } from "./PanelResizeHandleRegistry.js";
2
+ import { CSSProperties, HTMLAttributes, PropsWithChildren, ReactElement } from "./vendor/react.js";
3
3
  export type PanelResizeHandleOnDragging = (isDragging: boolean) => void;
4
4
  export type ResizeHandlerState = "drag" | "hover" | "inactive";
5
- export type PanelResizeHandleProps = Omit<HTMLAttributes<keyof HTMLElementTagNameMap>, "id"> & PropsWithChildren<{
5
+ export type PanelResizeHandleProps = Omit<HTMLAttributes<keyof HTMLElementTagNameMap>, "id" | "onBlur" | "onFocus"> & PropsWithChildren<{
6
6
  className?: string;
7
7
  disabled?: boolean;
8
8
  hitAreaMargins?: PointerHitAreaMargins;
9
9
  id?: string | null;
10
+ onBlur?: () => void;
10
11
  onDragging?: PanelResizeHandleOnDragging;
12
+ onFocus?: () => void;
11
13
  style?: CSSProperties;
12
14
  tabIndex?: number;
13
15
  tagName?: keyof HTMLElementTagNameMap;
14
16
  }>;
15
- export declare function PanelResizeHandle({ children, className: classNameFromProps, disabled, hitAreaMargins, id: idFromProps, onDragging, style: styleFromProps, tabIndex, tagName: Type, ...rest }: PanelResizeHandleProps): ReactElement;
17
+ export declare function PanelResizeHandle({ children, className: classNameFromProps, disabled, hitAreaMargins, id: idFromProps, onBlur, onDragging, onFocus, style: styleFromProps, tabIndex, tagName: Type, ...rest }: PanelResizeHandleProps): ReactElement;
16
18
  export declare namespace PanelResizeHandle {
17
19
  var displayName: string;
18
20
  }
@@ -14,4 +14,5 @@ import { intersects } from "./utils/rects/intersects.js";
14
14
  import type { ImperativePanelHandle, PanelOnCollapse, PanelOnExpand, PanelOnResize, PanelProps } from "./Panel.js";
15
15
  import type { ImperativePanelGroupHandle, PanelGroupOnLayout, PanelGroupProps, PanelGroupStorage } from "./PanelGroup.js";
16
16
  import type { PanelResizeHandleOnDragging, PanelResizeHandleProps } from "./PanelResizeHandle.js";
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, };
17
+ import type { PointerHitAreaMargins } from "./PanelResizeHandleRegistry.js";
18
+ export { ImperativePanelGroupHandle, ImperativePanelHandle, PanelGroupOnLayout, PanelGroupProps, PanelGroupStorage, PanelOnCollapse, PanelOnExpand, PanelOnResize, PanelProps, PanelResizeHandleOnDragging, PanelResizeHandleProps, PointerHitAreaMargins, Panel, PanelGroup, PanelResizeHandle, assert, getIntersectingRectangle, intersects, getPanelElement, getPanelElementsForGroup, getPanelGroupElement, getResizeHandleElement, getResizeHandleElementIndex, getResizeHandleElementsForGroup, getResizeHandlePanelIds, };
@@ -454,6 +454,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
454
454
  if (count === 1) {
455
455
  ownerDocumentCounts.delete(ownerDocument);
456
456
  }
457
+
458
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
459
+ // update the global pointer to account for the change
460
+ if (intersectingHandles.includes(data)) {
461
+ const index = intersectingHandles.indexOf(data);
462
+ if (index >= 0) {
463
+ intersectingHandles.splice(index, 1);
464
+ }
465
+ updateCursor();
466
+ }
457
467
  };
458
468
  }
459
469
  function handlePointerDown(event) {
@@ -2203,7 +2213,9 @@ function PanelResizeHandle({
2203
2213
  disabled = false,
2204
2214
  hitAreaMargins,
2205
2215
  id: idFromProps,
2216
+ onBlur,
2206
2217
  onDragging,
2218
+ onFocus,
2207
2219
  style: styleFromProps = {},
2208
2220
  tabIndex = 0,
2209
2221
  tagName: Type = "div",
@@ -2323,8 +2335,14 @@ function PanelResizeHandle({
2323
2335
  children,
2324
2336
  className: classNameFromProps,
2325
2337
  id: idFromProps,
2326
- onBlur: () => setIsFocused(false),
2327
- onFocus: () => setIsFocused(true),
2338
+ onBlur: () => {
2339
+ setIsFocused(false);
2340
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2341
+ },
2342
+ onFocus: () => {
2343
+ setIsFocused(true);
2344
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2345
+ },
2328
2346
  ref: elementRef,
2329
2347
  role: "separator",
2330
2348
  style: {
@@ -460,6 +460,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
460
460
  if (count === 1) {
461
461
  ownerDocumentCounts.delete(ownerDocument);
462
462
  }
463
+
464
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
465
+ // update the global pointer to account for the change
466
+ if (intersectingHandles.includes(data)) {
467
+ const index = intersectingHandles.indexOf(data);
468
+ if (index >= 0) {
469
+ intersectingHandles.splice(index, 1);
470
+ }
471
+ updateCursor();
472
+ }
463
473
  };
464
474
  }
465
475
  function handlePointerDown(event) {
@@ -2309,7 +2319,9 @@ function PanelResizeHandle({
2309
2319
  disabled = false,
2310
2320
  hitAreaMargins,
2311
2321
  id: idFromProps,
2322
+ onBlur,
2312
2323
  onDragging,
2324
+ onFocus,
2313
2325
  style: styleFromProps = {},
2314
2326
  tabIndex = 0,
2315
2327
  tagName: Type = "div",
@@ -2429,8 +2441,14 @@ function PanelResizeHandle({
2429
2441
  children,
2430
2442
  className: classNameFromProps,
2431
2443
  id: idFromProps,
2432
- onBlur: () => setIsFocused(false),
2433
- onFocus: () => setIsFocused(true),
2444
+ onBlur: () => {
2445
+ setIsFocused(false);
2446
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2447
+ },
2448
+ onFocus: () => {
2449
+ setIsFocused(true);
2450
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2451
+ },
2434
2452
  ref: elementRef,
2435
2453
  role: "separator",
2436
2454
  style: {
@@ -436,6 +436,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
436
436
  if (count === 1) {
437
437
  ownerDocumentCounts.delete(ownerDocument);
438
438
  }
439
+
440
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
441
+ // update the global pointer to account for the change
442
+ if (intersectingHandles.includes(data)) {
443
+ const index = intersectingHandles.indexOf(data);
444
+ if (index >= 0) {
445
+ intersectingHandles.splice(index, 1);
446
+ }
447
+ updateCursor();
448
+ }
439
449
  };
440
450
  }
441
451
  function handlePointerDown(event) {
@@ -2285,7 +2295,9 @@ function PanelResizeHandle({
2285
2295
  disabled = false,
2286
2296
  hitAreaMargins,
2287
2297
  id: idFromProps,
2298
+ onBlur,
2288
2299
  onDragging,
2300
+ onFocus,
2289
2301
  style: styleFromProps = {},
2290
2302
  tabIndex = 0,
2291
2303
  tagName: Type = "div",
@@ -2405,8 +2417,14 @@ function PanelResizeHandle({
2405
2417
  children,
2406
2418
  className: classNameFromProps,
2407
2419
  id: idFromProps,
2408
- onBlur: () => setIsFocused(false),
2409
- onFocus: () => setIsFocused(true),
2420
+ onBlur: () => {
2421
+ setIsFocused(false);
2422
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2423
+ },
2424
+ onFocus: () => {
2425
+ setIsFocused(true);
2426
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2427
+ },
2410
2428
  ref: elementRef,
2411
2429
  role: "separator",
2412
2430
  style: {
@@ -430,6 +430,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
430
430
  if (count === 1) {
431
431
  ownerDocumentCounts.delete(ownerDocument);
432
432
  }
433
+
434
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
435
+ // update the global pointer to account for the change
436
+ if (intersectingHandles.includes(data)) {
437
+ const index = intersectingHandles.indexOf(data);
438
+ if (index >= 0) {
439
+ intersectingHandles.splice(index, 1);
440
+ }
441
+ updateCursor();
442
+ }
433
443
  };
434
444
  }
435
445
  function handlePointerDown(event) {
@@ -2179,7 +2189,9 @@ function PanelResizeHandle({
2179
2189
  disabled = false,
2180
2190
  hitAreaMargins,
2181
2191
  id: idFromProps,
2192
+ onBlur,
2182
2193
  onDragging,
2194
+ onFocus,
2183
2195
  style: styleFromProps = {},
2184
2196
  tabIndex = 0,
2185
2197
  tagName: Type = "div",
@@ -2299,8 +2311,14 @@ function PanelResizeHandle({
2299
2311
  children,
2300
2312
  className: classNameFromProps,
2301
2313
  id: idFromProps,
2302
- onBlur: () => setIsFocused(false),
2303
- onFocus: () => setIsFocused(true),
2314
+ onBlur: () => {
2315
+ setIsFocused(false);
2316
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2317
+ },
2318
+ onFocus: () => {
2319
+ setIsFocused(true);
2320
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2321
+ },
2304
2322
  ref: elementRef,
2305
2323
  role: "separator",
2306
2324
  style: {
@@ -456,6 +456,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
456
456
  if (count === 1) {
457
457
  ownerDocumentCounts.delete(ownerDocument);
458
458
  }
459
+
460
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
461
+ // update the global pointer to account for the change
462
+ if (intersectingHandles.includes(data)) {
463
+ const index = intersectingHandles.indexOf(data);
464
+ if (index >= 0) {
465
+ intersectingHandles.splice(index, 1);
466
+ }
467
+ updateCursor();
468
+ }
459
469
  };
460
470
  }
461
471
  function handlePointerDown(event) {
@@ -2205,7 +2215,9 @@ function PanelResizeHandle({
2205
2215
  disabled = false,
2206
2216
  hitAreaMargins,
2207
2217
  id: idFromProps,
2218
+ onBlur,
2208
2219
  onDragging,
2220
+ onFocus,
2209
2221
  style: styleFromProps = {},
2210
2222
  tabIndex = 0,
2211
2223
  tagName: Type = "div",
@@ -2325,8 +2337,14 @@ function PanelResizeHandle({
2325
2337
  children,
2326
2338
  className: classNameFromProps,
2327
2339
  id: idFromProps,
2328
- onBlur: () => setIsFocused(false),
2329
- onFocus: () => setIsFocused(true),
2340
+ onBlur: () => {
2341
+ setIsFocused(false);
2342
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2343
+ },
2344
+ onFocus: () => {
2345
+ setIsFocused(true);
2346
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2347
+ },
2330
2348
  ref: elementRef,
2331
2349
  role: "separator",
2332
2350
  style: {
@@ -467,6 +467,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
467
467
  if (count === 1) {
468
468
  ownerDocumentCounts.delete(ownerDocument);
469
469
  }
470
+
471
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
472
+ // update the global pointer to account for the change
473
+ if (intersectingHandles.includes(data)) {
474
+ const index = intersectingHandles.indexOf(data);
475
+ if (index >= 0) {
476
+ intersectingHandles.splice(index, 1);
477
+ }
478
+ updateCursor();
479
+ }
470
480
  };
471
481
  }
472
482
  function handlePointerDown(event) {
@@ -2316,7 +2326,9 @@ function PanelResizeHandle({
2316
2326
  disabled = false,
2317
2327
  hitAreaMargins,
2318
2328
  id: idFromProps,
2329
+ onBlur,
2319
2330
  onDragging,
2331
+ onFocus,
2320
2332
  style: styleFromProps = {},
2321
2333
  tabIndex = 0,
2322
2334
  tagName: Type = "div",
@@ -2436,8 +2448,14 @@ function PanelResizeHandle({
2436
2448
  children,
2437
2449
  className: classNameFromProps,
2438
2450
  id: idFromProps,
2439
- onBlur: () => setIsFocused(false),
2440
- onFocus: () => setIsFocused(true),
2451
+ onBlur: () => {
2452
+ setIsFocused(false);
2453
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2454
+ },
2455
+ onFocus: () => {
2456
+ setIsFocused(true);
2457
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2458
+ },
2441
2459
  ref: elementRef,
2442
2460
  role: "separator",
2443
2461
  style: {
@@ -443,6 +443,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
443
443
  if (count === 1) {
444
444
  ownerDocumentCounts.delete(ownerDocument);
445
445
  }
446
+
447
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
448
+ // update the global pointer to account for the change
449
+ if (intersectingHandles.includes(data)) {
450
+ const index = intersectingHandles.indexOf(data);
451
+ if (index >= 0) {
452
+ intersectingHandles.splice(index, 1);
453
+ }
454
+ updateCursor();
455
+ }
446
456
  };
447
457
  }
448
458
  function handlePointerDown(event) {
@@ -2292,7 +2302,9 @@ function PanelResizeHandle({
2292
2302
  disabled = false,
2293
2303
  hitAreaMargins,
2294
2304
  id: idFromProps,
2305
+ onBlur,
2295
2306
  onDragging,
2307
+ onFocus,
2296
2308
  style: styleFromProps = {},
2297
2309
  tabIndex = 0,
2298
2310
  tagName: Type = "div",
@@ -2412,8 +2424,14 @@ function PanelResizeHandle({
2412
2424
  children,
2413
2425
  className: classNameFromProps,
2414
2426
  id: idFromProps,
2415
- onBlur: () => setIsFocused(false),
2416
- onFocus: () => setIsFocused(true),
2427
+ onBlur: () => {
2428
+ setIsFocused(false);
2429
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2430
+ },
2431
+ onFocus: () => {
2432
+ setIsFocused(true);
2433
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2434
+ },
2417
2435
  ref: elementRef,
2418
2436
  role: "separator",
2419
2437
  style: {
@@ -429,6 +429,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
429
429
  if (count === 1) {
430
430
  ownerDocumentCounts.delete(ownerDocument);
431
431
  }
432
+
433
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
434
+ // update the global pointer to account for the change
435
+ if (intersectingHandles.includes(data)) {
436
+ const index = intersectingHandles.indexOf(data);
437
+ if (index >= 0) {
438
+ intersectingHandles.splice(index, 1);
439
+ }
440
+ updateCursor();
441
+ }
432
442
  };
433
443
  }
434
444
  function handlePointerDown(event) {
@@ -2084,7 +2094,9 @@ function PanelResizeHandle({
2084
2094
  disabled = false,
2085
2095
  hitAreaMargins,
2086
2096
  id: idFromProps,
2097
+ onBlur,
2087
2098
  onDragging,
2099
+ onFocus,
2088
2100
  style: styleFromProps = {},
2089
2101
  tabIndex = 0,
2090
2102
  tagName: Type = "div",
@@ -2201,8 +2213,14 @@ function PanelResizeHandle({
2201
2213
  children,
2202
2214
  className: classNameFromProps,
2203
2215
  id: idFromProps,
2204
- onBlur: () => setIsFocused(false),
2205
- onFocus: () => setIsFocused(true),
2216
+ onBlur: () => {
2217
+ setIsFocused(false);
2218
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2219
+ },
2220
+ onFocus: () => {
2221
+ setIsFocused(true);
2222
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2223
+ },
2206
2224
  ref: elementRef,
2207
2225
  role: "separator",
2208
2226
  style: {
@@ -405,6 +405,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
405
405
  if (count === 1) {
406
406
  ownerDocumentCounts.delete(ownerDocument);
407
407
  }
408
+
409
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
410
+ // update the global pointer to account for the change
411
+ if (intersectingHandles.includes(data)) {
412
+ const index = intersectingHandles.indexOf(data);
413
+ if (index >= 0) {
414
+ intersectingHandles.splice(index, 1);
415
+ }
416
+ updateCursor();
417
+ }
408
418
  };
409
419
  }
410
420
  function handlePointerDown(event) {
@@ -2060,7 +2070,9 @@ function PanelResizeHandle({
2060
2070
  disabled = false,
2061
2071
  hitAreaMargins,
2062
2072
  id: idFromProps,
2073
+ onBlur,
2063
2074
  onDragging,
2075
+ onFocus,
2064
2076
  style: styleFromProps = {},
2065
2077
  tabIndex = 0,
2066
2078
  tagName: Type = "div",
@@ -2177,8 +2189,14 @@ function PanelResizeHandle({
2177
2189
  children,
2178
2190
  className: classNameFromProps,
2179
2191
  id: idFromProps,
2180
- onBlur: () => setIsFocused(false),
2181
- onFocus: () => setIsFocused(true),
2192
+ onBlur: () => {
2193
+ setIsFocused(false);
2194
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2195
+ },
2196
+ onFocus: () => {
2197
+ setIsFocused(true);
2198
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2199
+ },
2182
2200
  ref: elementRef,
2183
2201
  role: "separator",
2184
2202
  style: {
@@ -432,6 +432,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
432
432
  if (count === 1) {
433
433
  ownerDocumentCounts.delete(ownerDocument);
434
434
  }
435
+
436
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
437
+ // update the global pointer to account for the change
438
+ if (intersectingHandles.includes(data)) {
439
+ const index = intersectingHandles.indexOf(data);
440
+ if (index >= 0) {
441
+ intersectingHandles.splice(index, 1);
442
+ }
443
+ updateCursor();
444
+ }
435
445
  };
436
446
  }
437
447
  function handlePointerDown(event) {
@@ -2181,7 +2191,9 @@ function PanelResizeHandle({
2181
2191
  disabled = false,
2182
2192
  hitAreaMargins,
2183
2193
  id: idFromProps,
2194
+ onBlur,
2184
2195
  onDragging,
2196
+ onFocus,
2185
2197
  style: styleFromProps = {},
2186
2198
  tabIndex = 0,
2187
2199
  tagName: Type = "div",
@@ -2301,8 +2313,14 @@ function PanelResizeHandle({
2301
2313
  children,
2302
2314
  className: classNameFromProps,
2303
2315
  id: idFromProps,
2304
- onBlur: () => setIsFocused(false),
2305
- onFocus: () => setIsFocused(true),
2316
+ onBlur: () => {
2317
+ setIsFocused(false);
2318
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2319
+ },
2320
+ onFocus: () => {
2321
+ setIsFocused(true);
2322
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2323
+ },
2306
2324
  ref: elementRef,
2307
2325
  role: "separator",
2308
2326
  style: {
@@ -418,6 +418,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
418
418
  if (count === 1) {
419
419
  ownerDocumentCounts.delete(ownerDocument);
420
420
  }
421
+
422
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
423
+ // update the global pointer to account for the change
424
+ if (intersectingHandles.includes(data)) {
425
+ const index = intersectingHandles.indexOf(data);
426
+ if (index >= 0) {
427
+ intersectingHandles.splice(index, 1);
428
+ }
429
+ updateCursor();
430
+ }
421
431
  };
422
432
  }
423
433
  function handlePointerDown(event) {
@@ -1983,7 +1993,9 @@ function PanelResizeHandle({
1983
1993
  disabled = false,
1984
1994
  hitAreaMargins,
1985
1995
  id: idFromProps,
1996
+ onBlur,
1986
1997
  onDragging,
1998
+ onFocus,
1987
1999
  style: styleFromProps = {},
1988
2000
  tabIndex = 0,
1989
2001
  tagName: Type = "div",
@@ -2100,8 +2112,14 @@ function PanelResizeHandle({
2100
2112
  children,
2101
2113
  className: classNameFromProps,
2102
2114
  id: idFromProps,
2103
- onBlur: () => setIsFocused(false),
2104
- onFocus: () => setIsFocused(true),
2115
+ onBlur: () => {
2116
+ setIsFocused(false);
2117
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2118
+ },
2119
+ onFocus: () => {
2120
+ setIsFocused(true);
2121
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2122
+ },
2105
2123
  ref: elementRef,
2106
2124
  role: "separator",
2107
2125
  style: {
@@ -394,6 +394,16 @@ function registerResizeHandle(resizeHandleId, element, direction, hitAreaMargins
394
394
  if (count === 1) {
395
395
  ownerDocumentCounts.delete(ownerDocument);
396
396
  }
397
+
398
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
399
+ // update the global pointer to account for the change
400
+ if (intersectingHandles.includes(data)) {
401
+ const index = intersectingHandles.indexOf(data);
402
+ if (index >= 0) {
403
+ intersectingHandles.splice(index, 1);
404
+ }
405
+ updateCursor();
406
+ }
397
407
  };
398
408
  }
399
409
  function handlePointerDown(event) {
@@ -1959,7 +1969,9 @@ function PanelResizeHandle({
1959
1969
  disabled = false,
1960
1970
  hitAreaMargins,
1961
1971
  id: idFromProps,
1972
+ onBlur,
1962
1973
  onDragging,
1974
+ onFocus,
1963
1975
  style: styleFromProps = {},
1964
1976
  tabIndex = 0,
1965
1977
  tagName: Type = "div",
@@ -2076,8 +2088,14 @@ function PanelResizeHandle({
2076
2088
  children,
2077
2089
  className: classNameFromProps,
2078
2090
  id: idFromProps,
2079
- onBlur: () => setIsFocused(false),
2080
- onFocus: () => setIsFocused(true),
2091
+ onBlur: () => {
2092
+ setIsFocused(false);
2093
+ onBlur === null || onBlur === void 0 ? void 0 : onBlur();
2094
+ },
2095
+ onFocus: () => {
2096
+ setIsFocused(true);
2097
+ onFocus === null || onFocus === void 0 ? void 0 : onFocus();
2098
+ },
2081
2099
  ref: elementRef,
2082
2100
  role: "separator",
2083
2101
  style: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-resizable-panels",
3
- "version": "2.0.19",
3
+ "version": "2.0.20",
4
4
  "description": "React components for resizable panel groups/layouts",
5
5
  "author": "Brian Vaughn <brian.david.vaughn@gmail.com>",
6
6
  "license": "MIT",
@@ -1,8 +1,13 @@
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";
4
- import { Panel, PanelGroup, PanelResizeHandle } from ".";
3
+ import {
4
+ Panel,
5
+ PanelGroup,
6
+ PanelResizeHandle,
7
+ type PanelResizeHandleProps,
8
+ } from ".";
5
9
  import { assert } from "./utils/assert";
10
+ import * as cursorUtils from "./utils/cursor";
6
11
  import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
7
12
  import {
8
13
  dispatchPointerEvent,
@@ -10,6 +15,12 @@ import {
10
15
  verifyAttribute,
11
16
  } from "./utils/test-utils";
12
17
 
18
+ jest.mock("./utils/cursor", () => ({
19
+ getCursorStyle: jest.fn(),
20
+ resetGlobalCursorStyle: jest.fn(),
21
+ setGlobalCursorStyle: jest.fn(),
22
+ }));
23
+
13
24
  describe("PanelResizeHandle", () => {
14
25
  let expectedWarnings: string[] = [];
15
26
  let root: Root;
@@ -305,4 +316,33 @@ describe("PanelResizeHandle", () => {
305
316
  expect(element?.getAttribute("id")).toBeNull();
306
317
  });
307
318
  });
319
+
320
+ it("resets the global cursor style on unmount", () => {
321
+ const onDraggingLeft = jest.fn();
322
+
323
+ const { leftElement } = setupMockedGroup({
324
+ leftProps: { onDragging: onDraggingLeft },
325
+ rightProps: {},
326
+ });
327
+
328
+ act(() => {
329
+ dispatchPointerEvent("pointermove", leftElement);
330
+ });
331
+
332
+ act(() => {
333
+ dispatchPointerEvent("pointerdown", leftElement);
334
+ });
335
+ expect(onDraggingLeft).toHaveBeenCalledTimes(1);
336
+ expect(onDraggingLeft).toHaveBeenCalledWith(true);
337
+
338
+ expect(cursorUtils.resetGlobalCursorStyle).not.toHaveBeenCalled();
339
+ expect(cursorUtils.setGlobalCursorStyle).toHaveBeenCalledTimes(1);
340
+
341
+ act(() => {
342
+ root.unmount();
343
+ });
344
+
345
+ expect(cursorUtils.resetGlobalCursorStyle).toHaveBeenCalled();
346
+ expect(cursorUtils.setGlobalCursorStyle).toHaveBeenCalledTimes(1);
347
+ });
308
348
  });
@@ -1,16 +1,5 @@
1
+ import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
1
2
  import useUniqueId from "./hooks/useUniqueId";
2
- import {
3
- createElement,
4
- CSSProperties,
5
- HTMLAttributes,
6
- PropsWithChildren,
7
- ReactElement,
8
- useContext,
9
- useEffect,
10
- useRef,
11
- useState,
12
- } from "./vendor/react";
13
-
14
3
  import { useWindowSplitterResizeHandlerBehavior } from "./hooks/useWindowSplitterBehavior";
15
4
  import {
16
5
  PanelGroupContext,
@@ -23,21 +12,33 @@ import {
23
12
  ResizeHandlerAction,
24
13
  } from "./PanelResizeHandleRegistry";
25
14
  import { assert } from "./utils/assert";
26
- import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
15
+ import {
16
+ createElement,
17
+ CSSProperties,
18
+ HTMLAttributes,
19
+ PropsWithChildren,
20
+ ReactElement,
21
+ useContext,
22
+ useEffect,
23
+ useRef,
24
+ useState,
25
+ } from "./vendor/react";
27
26
 
28
27
  export type PanelResizeHandleOnDragging = (isDragging: boolean) => void;
29
28
  export type ResizeHandlerState = "drag" | "hover" | "inactive";
30
29
 
31
30
  export type PanelResizeHandleProps = Omit<
32
31
  HTMLAttributes<keyof HTMLElementTagNameMap>,
33
- "id"
32
+ "id" | "onBlur" | "onFocus"
34
33
  > &
35
34
  PropsWithChildren<{
36
35
  className?: string;
37
36
  disabled?: boolean;
38
37
  hitAreaMargins?: PointerHitAreaMargins;
39
38
  id?: string | null;
39
+ onBlur?: () => void;
40
40
  onDragging?: PanelResizeHandleOnDragging;
41
+ onFocus?: () => void;
41
42
  style?: CSSProperties;
42
43
  tabIndex?: number;
43
44
  tagName?: keyof HTMLElementTagNameMap;
@@ -49,7 +50,9 @@ export function PanelResizeHandle({
49
50
  disabled = false,
50
51
  hitAreaMargins,
51
52
  id: idFromProps,
53
+ onBlur,
52
54
  onDragging,
55
+ onFocus,
53
56
  style: styleFromProps = {},
54
57
  tabIndex = 0,
55
58
  tagName: Type = "div",
@@ -208,8 +211,14 @@ export function PanelResizeHandle({
208
211
  children,
209
212
  className: classNameFromProps,
210
213
  id: idFromProps,
211
- onBlur: () => setIsFocused(false),
212
- onFocus: () => setIsFocused(true),
214
+ onBlur: () => {
215
+ setIsFocused(false);
216
+ onBlur?.();
217
+ },
218
+ onFocus: () => {
219
+ setIsFocused(true);
220
+ onFocus?.();
221
+ },
213
222
  ref: elementRef,
214
223
  role: "separator",
215
224
  style: {
@@ -73,6 +73,17 @@ export function registerResizeHandle(
73
73
  if (count === 1) {
74
74
  ownerDocumentCounts.delete(ownerDocument);
75
75
  }
76
+
77
+ // If the resize handle that is currently unmounting is intersecting with the pointer,
78
+ // update the global pointer to account for the change
79
+ if (intersectingHandles.includes(data)) {
80
+ const index = intersectingHandles.indexOf(data);
81
+ if (index >= 0) {
82
+ intersectingHandles.splice(index, 1);
83
+ }
84
+
85
+ updateCursor();
86
+ }
76
87
  };
77
88
  }
78
89
 
package/src/index.ts CHANGED
@@ -29,6 +29,7 @@ import type {
29
29
  PanelResizeHandleOnDragging,
30
30
  PanelResizeHandleProps,
31
31
  } from "./PanelResizeHandle";
32
+ import type { PointerHitAreaMargins } from "./PanelResizeHandleRegistry";
32
33
 
33
34
  export {
34
35
  // TypeScript types
@@ -43,6 +44,7 @@ export {
43
44
  PanelProps,
44
45
  PanelResizeHandleOnDragging,
45
46
  PanelResizeHandleProps,
47
+ PointerHitAreaMargins,
46
48
 
47
49
  // React components
48
50
  Panel,