react-resizable-panels 2.1.6 → 2.1.8

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 (92) hide show
  1. package/README.md +4 -0
  2. package/dist/declarations/src/Panel.d.ts +1 -1
  3. package/dist/declarations/src/PanelGroup.d.ts +1 -1
  4. package/dist/declarations/src/PanelResizeHandle.d.ts +6 -3
  5. package/dist/react-resizable-panels.browser.cjs.js +90 -94
  6. package/dist/react-resizable-panels.browser.development.cjs.js +90 -94
  7. package/dist/react-resizable-panels.browser.development.esm.js +38 -41
  8. package/dist/react-resizable-panels.browser.esm.js +38 -41
  9. package/dist/react-resizable-panels.cjs.js +90 -94
  10. package/dist/react-resizable-panels.development.cjs.js +90 -94
  11. package/dist/react-resizable-panels.development.esm.js +38 -41
  12. package/dist/react-resizable-panels.development.node.cjs.js +89 -92
  13. package/dist/react-resizable-panels.development.node.esm.js +37 -39
  14. package/dist/react-resizable-panels.esm.js +38 -41
  15. package/dist/react-resizable-panels.node.cjs.js +89 -92
  16. package/dist/react-resizable-panels.node.esm.js +37 -39
  17. package/package.json +7 -2
  18. package/.eslintrc.cjs +0 -27
  19. package/CHANGELOG.md +0 -574
  20. package/dist/declarations/src/vendor/react.d.ts +0 -7
  21. package/jest.config.js +0 -10
  22. package/src/Panel.test.tsx +0 -1084
  23. package/src/Panel.ts +0 -259
  24. package/src/PanelGroup.test.tsx +0 -443
  25. package/src/PanelGroup.ts +0 -999
  26. package/src/PanelGroupContext.ts +0 -42
  27. package/src/PanelResizeHandle.test.tsx +0 -367
  28. package/src/PanelResizeHandle.ts +0 -246
  29. package/src/PanelResizeHandleRegistry.ts +0 -336
  30. package/src/constants.ts +0 -1
  31. package/src/env-conditions/browser.ts +0 -1
  32. package/src/env-conditions/development.ts +0 -1
  33. package/src/env-conditions/node.ts +0 -1
  34. package/src/env-conditions/production.ts +0 -1
  35. package/src/env-conditions/unknown.ts +0 -1
  36. package/src/hooks/useForceUpdate.ts +0 -7
  37. package/src/hooks/useIsomorphicEffect.ts +0 -8
  38. package/src/hooks/useUniqueId.ts +0 -19
  39. package/src/hooks/useWindowSplitterBehavior.ts +0 -90
  40. package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +0 -201
  41. package/src/index.ts +0 -77
  42. package/src/types.ts +0 -5
  43. package/src/utils/adjustLayoutByDelta.test.ts +0 -2061
  44. package/src/utils/adjustLayoutByDelta.ts +0 -308
  45. package/src/utils/arrays.ts +0 -13
  46. package/src/utils/assert.ts +0 -10
  47. package/src/utils/calculateAriaValues.test.ts +0 -106
  48. package/src/utils/calculateAriaValues.ts +0 -45
  49. package/src/utils/calculateDeltaPercentage.ts +0 -63
  50. package/src/utils/calculateDragOffsetPercentage.ts +0 -40
  51. package/src/utils/calculateUnsafeDefaultLayout.test.ts +0 -87
  52. package/src/utils/calculateUnsafeDefaultLayout.ts +0 -50
  53. package/src/utils/callPanelCallbacks.ts +0 -49
  54. package/src/utils/compareLayouts.test.ts +0 -9
  55. package/src/utils/compareLayouts.ts +0 -12
  56. package/src/utils/computePanelFlexBoxStyle.test.ts +0 -123
  57. package/src/utils/computePanelFlexBoxStyle.ts +0 -50
  58. package/src/utils/csp.ts +0 -9
  59. package/src/utils/cursor.ts +0 -103
  60. package/src/utils/debounce.ts +0 -18
  61. package/src/utils/determinePivotIndices.ts +0 -15
  62. package/src/utils/dom/getPanelElement.ts +0 -10
  63. package/src/utils/dom/getPanelElementsForGroup.ts +0 -8
  64. package/src/utils/dom/getPanelGroupElement.ts +0 -21
  65. package/src/utils/dom/getResizeHandleElement.ts +0 -10
  66. package/src/utils/dom/getResizeHandleElementIndex.ts +0 -13
  67. package/src/utils/dom/getResizeHandleElementsForGroup.ts +0 -10
  68. package/src/utils/dom/getResizeHandlePanelIds.ts +0 -19
  69. package/src/utils/events/getResizeEventCoordinates.ts +0 -23
  70. package/src/utils/events/getResizeEventCursorPosition.ts +0 -14
  71. package/src/utils/events/index.ts +0 -13
  72. package/src/utils/getInputType.ts +0 -5
  73. package/src/utils/initializeDefaultStorage.ts +0 -26
  74. package/src/utils/numbers/fuzzyCompareNumbers.test.ts +0 -16
  75. package/src/utils/numbers/fuzzyCompareNumbers.ts +0 -21
  76. package/src/utils/numbers/fuzzyLayoutsEqual.ts +0 -22
  77. package/src/utils/numbers/fuzzyNumbersEqual.ts +0 -9
  78. package/src/utils/rects/getIntersectingRectangle.test.ts +0 -198
  79. package/src/utils/rects/getIntersectingRectangle.ts +0 -28
  80. package/src/utils/rects/intersects.test.ts +0 -197
  81. package/src/utils/rects/intersects.ts +0 -23
  82. package/src/utils/rects/types.ts +0 -6
  83. package/src/utils/resizePanel.test.ts +0 -59
  84. package/src/utils/resizePanel.ts +0 -47
  85. package/src/utils/serialization.ts +0 -87
  86. package/src/utils/test-utils.ts +0 -205
  87. package/src/utils/validatePanelConstraints.test.ts +0 -143
  88. package/src/utils/validatePanelConstraints.ts +0 -69
  89. package/src/utils/validatePanelGroupLayout.test.ts +0 -148
  90. package/src/utils/validatePanelGroupLayout.ts +0 -95
  91. package/src/vendor/react.ts +0 -73
  92. package/src/vendor/stacking-order.ts +0 -139
package/src/PanelGroup.ts DELETED
@@ -1,999 +0,0 @@
1
- import { isDevelopment } from "#is-development";
2
- import { PanelConstraints, PanelData } from "./Panel";
3
- import {
4
- DragState,
5
- PanelGroupContext,
6
- ResizeEvent,
7
- TPanelGroupContext,
8
- } from "./PanelGroupContext";
9
- import {
10
- EXCEEDED_HORIZONTAL_MAX,
11
- EXCEEDED_HORIZONTAL_MIN,
12
- EXCEEDED_VERTICAL_MAX,
13
- EXCEEDED_VERTICAL_MIN,
14
- reportConstraintsViolation,
15
- } from "./PanelResizeHandleRegistry";
16
- import { useForceUpdate } from "./hooks/useForceUpdate";
17
- import useIsomorphicLayoutEffect from "./hooks/useIsomorphicEffect";
18
- import useUniqueId from "./hooks/useUniqueId";
19
- import { useWindowSplitterPanelGroupBehavior } from "./hooks/useWindowSplitterPanelGroupBehavior";
20
- import { Direction } from "./types";
21
- import { adjustLayoutByDelta } from "./utils/adjustLayoutByDelta";
22
- import { areEqual } from "./utils/arrays";
23
- import { assert } from "./utils/assert";
24
- import { calculateDeltaPercentage } from "./utils/calculateDeltaPercentage";
25
- import { calculateUnsafeDefaultLayout } from "./utils/calculateUnsafeDefaultLayout";
26
- import { callPanelCallbacks } from "./utils/callPanelCallbacks";
27
- import { compareLayouts } from "./utils/compareLayouts";
28
- import { computePanelFlexBoxStyle } from "./utils/computePanelFlexBoxStyle";
29
- import debounce from "./utils/debounce";
30
- import { determinePivotIndices } from "./utils/determinePivotIndices";
31
- import { getResizeHandleElement } from "./utils/dom/getResizeHandleElement";
32
- import { isKeyDown, isMouseEvent, isPointerEvent } from "./utils/events";
33
- import { getResizeEventCursorPosition } from "./utils/events/getResizeEventCursorPosition";
34
- import { initializeDefaultStorage } from "./utils/initializeDefaultStorage";
35
- import {
36
- fuzzyCompareNumbers,
37
- fuzzyNumbersEqual,
38
- } from "./utils/numbers/fuzzyCompareNumbers";
39
- import {
40
- loadPanelGroupState,
41
- savePanelGroupState,
42
- } from "./utils/serialization";
43
- import { validatePanelConstraints } from "./utils/validatePanelConstraints";
44
- import { validatePanelGroupLayout } from "./utils/validatePanelGroupLayout";
45
- import {
46
- CSSProperties,
47
- ForwardedRef,
48
- HTMLAttributes,
49
- PropsWithChildren,
50
- ReactElement,
51
- createElement,
52
- forwardRef,
53
- useCallback,
54
- useEffect,
55
- useImperativeHandle,
56
- useMemo,
57
- useRef,
58
- useState,
59
- } from "./vendor/react";
60
-
61
- const LOCAL_STORAGE_DEBOUNCE_INTERVAL = 100;
62
-
63
- export type ImperativePanelGroupHandle = {
64
- getId: () => string;
65
- getLayout: () => number[];
66
- setLayout: (layout: number[]) => void;
67
- };
68
-
69
- export type PanelGroupStorage = {
70
- getItem(name: string): string | null;
71
- setItem(name: string, value: string): void;
72
- };
73
-
74
- export type PanelGroupOnLayout = (layout: number[]) => void;
75
-
76
- const defaultStorage: PanelGroupStorage = {
77
- getItem: (name: string) => {
78
- initializeDefaultStorage(defaultStorage);
79
- return defaultStorage.getItem(name);
80
- },
81
- setItem: (name: string, value: string) => {
82
- initializeDefaultStorage(defaultStorage);
83
- defaultStorage.setItem(name, value);
84
- },
85
- };
86
-
87
- export type PanelGroupProps = Omit<
88
- HTMLAttributes<keyof HTMLElementTagNameMap>,
89
- "id"
90
- > &
91
- PropsWithChildren<{
92
- autoSaveId?: string | null;
93
- className?: string;
94
- direction: Direction;
95
- id?: string | null;
96
- keyboardResizeBy?: number | null;
97
- onLayout?: PanelGroupOnLayout | null;
98
- storage?: PanelGroupStorage;
99
- style?: CSSProperties;
100
- tagName?: keyof HTMLElementTagNameMap;
101
-
102
- // Better TypeScript hinting
103
- dir?: "auto" | "ltr" | "rtl" | undefined;
104
- }>;
105
-
106
- const debounceMap: {
107
- [key: string]: typeof savePanelGroupState;
108
- } = {};
109
-
110
- function PanelGroupWithForwardedRef({
111
- autoSaveId = null,
112
- children,
113
- className: classNameFromProps = "",
114
- direction,
115
- forwardedRef,
116
- id: idFromProps = null,
117
- onLayout = null,
118
- keyboardResizeBy = null,
119
- storage = defaultStorage,
120
- style: styleFromProps,
121
- tagName: Type = "div",
122
- ...rest
123
- }: PanelGroupProps & {
124
- forwardedRef: ForwardedRef<ImperativePanelGroupHandle>;
125
- }): ReactElement {
126
- const groupId = useUniqueId(idFromProps);
127
- const panelGroupElementRef = useRef<HTMLDivElement | null>(null);
128
- const [dragState, setDragState] = useState<DragState | null>(null);
129
- const [layout, setLayout] = useState<number[]>([]);
130
- const forceUpdate = useForceUpdate();
131
-
132
- const panelIdToLastNotifiedSizeMapRef = useRef<Record<string, number>>({});
133
- const panelSizeBeforeCollapseRef = useRef<Map<string, number>>(new Map());
134
- const prevDeltaRef = useRef<number>(0);
135
-
136
- const committedValuesRef = useRef<{
137
- autoSaveId: string | null;
138
- direction: Direction;
139
- dragState: DragState | null;
140
- id: string;
141
- keyboardResizeBy: number | null;
142
- onLayout: PanelGroupOnLayout | null;
143
- storage: PanelGroupStorage;
144
- }>({
145
- autoSaveId,
146
- direction,
147
- dragState,
148
- id: groupId,
149
- keyboardResizeBy,
150
- onLayout,
151
- storage,
152
- });
153
-
154
- const eagerValuesRef = useRef<{
155
- layout: number[];
156
- panelDataArray: PanelData[];
157
- panelDataArrayChanged: boolean;
158
- }>({
159
- layout,
160
- panelDataArray: [],
161
- panelDataArrayChanged: false,
162
- });
163
-
164
- const devWarningsRef = useRef<{
165
- didLogIdAndOrderWarning: boolean;
166
- didLogPanelConstraintsWarning: boolean;
167
- prevPanelIds: string[];
168
- }>({
169
- didLogIdAndOrderWarning: false,
170
- didLogPanelConstraintsWarning: false,
171
- prevPanelIds: [],
172
- });
173
-
174
- useImperativeHandle(
175
- forwardedRef,
176
- () => ({
177
- getId: () => committedValuesRef.current.id,
178
- getLayout: () => {
179
- const { layout } = eagerValuesRef.current;
180
-
181
- return layout;
182
- },
183
- setLayout: (unsafeLayout: number[]) => {
184
- const { onLayout } = committedValuesRef.current;
185
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
186
-
187
- const safeLayout = validatePanelGroupLayout({
188
- layout: unsafeLayout,
189
- panelConstraints: panelDataArray.map(
190
- (panelData) => panelData.constraints
191
- ),
192
- });
193
-
194
- if (!areEqual(prevLayout, safeLayout)) {
195
- setLayout(safeLayout);
196
-
197
- eagerValuesRef.current.layout = safeLayout;
198
-
199
- if (onLayout) {
200
- onLayout(safeLayout);
201
- }
202
-
203
- callPanelCallbacks(
204
- panelDataArray,
205
- safeLayout,
206
- panelIdToLastNotifiedSizeMapRef.current
207
- );
208
- }
209
- },
210
- }),
211
- []
212
- );
213
-
214
- useIsomorphicLayoutEffect(() => {
215
- committedValuesRef.current.autoSaveId = autoSaveId;
216
- committedValuesRef.current.direction = direction;
217
- committedValuesRef.current.dragState = dragState;
218
- committedValuesRef.current.id = groupId;
219
- committedValuesRef.current.onLayout = onLayout;
220
- committedValuesRef.current.storage = storage;
221
- });
222
-
223
- useWindowSplitterPanelGroupBehavior({
224
- committedValuesRef,
225
- eagerValuesRef,
226
- groupId,
227
- layout,
228
- panelDataArray: eagerValuesRef.current.panelDataArray,
229
- setLayout,
230
- panelGroupElement: panelGroupElementRef.current,
231
- });
232
-
233
- useEffect(() => {
234
- const { panelDataArray } = eagerValuesRef.current;
235
-
236
- // If this panel has been configured to persist sizing information, save sizes to local storage.
237
- if (autoSaveId) {
238
- if (layout.length === 0 || layout.length !== panelDataArray.length) {
239
- return;
240
- }
241
-
242
- let debouncedSave = debounceMap[autoSaveId];
243
-
244
- // Limit the frequency of localStorage updates.
245
- if (debouncedSave == null) {
246
- debouncedSave = debounce(
247
- savePanelGroupState,
248
- LOCAL_STORAGE_DEBOUNCE_INTERVAL
249
- );
250
-
251
- debounceMap[autoSaveId] = debouncedSave;
252
- }
253
-
254
- // Clone mutable data before passing to the debounced function,
255
- // else we run the risk of saving an incorrect combination of mutable and immutable values to state.
256
- const clonedPanelDataArray = [...panelDataArray];
257
- const clonedPanelSizesBeforeCollapse = new Map(
258
- panelSizeBeforeCollapseRef.current
259
- );
260
- debouncedSave(
261
- autoSaveId,
262
- clonedPanelDataArray,
263
- clonedPanelSizesBeforeCollapse,
264
- layout,
265
- storage
266
- );
267
- }
268
- }, [autoSaveId, layout, storage]);
269
-
270
- // DEV warnings
271
- useEffect(() => {
272
- if (isDevelopment) {
273
- const { panelDataArray } = eagerValuesRef.current;
274
-
275
- const {
276
- didLogIdAndOrderWarning,
277
- didLogPanelConstraintsWarning,
278
- prevPanelIds,
279
- } = devWarningsRef.current;
280
-
281
- if (!didLogIdAndOrderWarning) {
282
- const panelIds = panelDataArray.map(({ id }) => id);
283
-
284
- devWarningsRef.current.prevPanelIds = panelIds;
285
-
286
- const panelsHaveChanged =
287
- prevPanelIds.length > 0 && !areEqual(prevPanelIds, panelIds);
288
- if (panelsHaveChanged) {
289
- if (
290
- panelDataArray.find(
291
- ({ idIsFromProps, order }) => !idIsFromProps || order == null
292
- )
293
- ) {
294
- devWarningsRef.current.didLogIdAndOrderWarning = true;
295
-
296
- console.warn(
297
- `WARNING: Panel id and order props recommended when panels are dynamically rendered`
298
- );
299
- }
300
- }
301
- }
302
-
303
- if (!didLogPanelConstraintsWarning) {
304
- const panelConstraints = panelDataArray.map(
305
- (panelData) => panelData.constraints
306
- );
307
-
308
- for (
309
- let panelIndex = 0;
310
- panelIndex < panelConstraints.length;
311
- panelIndex++
312
- ) {
313
- const panelData = panelDataArray[panelIndex];
314
- assert(panelData, `Panel data not found for index ${panelIndex}`);
315
-
316
- const isValid = validatePanelConstraints({
317
- panelConstraints,
318
- panelId: panelData.id,
319
- panelIndex,
320
- });
321
-
322
- if (!isValid) {
323
- devWarningsRef.current.didLogPanelConstraintsWarning = true;
324
-
325
- break;
326
- }
327
- }
328
- }
329
- }
330
- });
331
-
332
- // External APIs are safe to memoize via committed values ref
333
- const collapsePanel = useCallback((panelData: PanelData) => {
334
- const { onLayout } = committedValuesRef.current;
335
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
336
-
337
- if (panelData.constraints.collapsible) {
338
- const panelConstraintsArray = panelDataArray.map(
339
- (panelData) => panelData.constraints
340
- );
341
-
342
- const {
343
- collapsedSize = 0,
344
- panelSize,
345
- pivotIndices,
346
- } = panelDataHelper(panelDataArray, panelData, prevLayout);
347
-
348
- assert(
349
- panelSize != null,
350
- `Panel size not found for panel "${panelData.id}"`
351
- );
352
-
353
- if (!fuzzyNumbersEqual(panelSize, collapsedSize)) {
354
- // Store size before collapse;
355
- // This is the size that gets restored if the expand() API is used.
356
- panelSizeBeforeCollapseRef.current.set(panelData.id, panelSize);
357
-
358
- const isLastPanel =
359
- findPanelDataIndex(panelDataArray, panelData) ===
360
- panelDataArray.length - 1;
361
- const delta = isLastPanel
362
- ? panelSize - collapsedSize
363
- : collapsedSize - panelSize;
364
-
365
- const nextLayout = adjustLayoutByDelta({
366
- delta,
367
- initialLayout: prevLayout,
368
- panelConstraints: panelConstraintsArray,
369
- pivotIndices,
370
- prevLayout,
371
- trigger: "imperative-api",
372
- });
373
-
374
- if (!compareLayouts(prevLayout, nextLayout)) {
375
- setLayout(nextLayout);
376
-
377
- eagerValuesRef.current.layout = nextLayout;
378
-
379
- if (onLayout) {
380
- onLayout(nextLayout);
381
- }
382
-
383
- callPanelCallbacks(
384
- panelDataArray,
385
- nextLayout,
386
- panelIdToLastNotifiedSizeMapRef.current
387
- );
388
- }
389
- }
390
- }
391
- }, []);
392
-
393
- // External APIs are safe to memoize via committed values ref
394
- const expandPanel = useCallback(
395
- (panelData: PanelData, minSizeOverride?: number) => {
396
- const { onLayout } = committedValuesRef.current;
397
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
398
-
399
- if (panelData.constraints.collapsible) {
400
- const panelConstraintsArray = panelDataArray.map(
401
- (panelData) => panelData.constraints
402
- );
403
-
404
- const {
405
- collapsedSize = 0,
406
- panelSize = 0,
407
- minSize: minSizeFromProps = 0,
408
- pivotIndices,
409
- } = panelDataHelper(panelDataArray, panelData, prevLayout);
410
-
411
- const minSize = minSizeOverride ?? minSizeFromProps;
412
-
413
- if (fuzzyNumbersEqual(panelSize, collapsedSize)) {
414
- // Restore this panel to the size it was before it was collapsed, if possible.
415
- const prevPanelSize = panelSizeBeforeCollapseRef.current.get(
416
- panelData.id
417
- );
418
-
419
- const baseSize =
420
- prevPanelSize != null && prevPanelSize >= minSize
421
- ? prevPanelSize
422
- : minSize;
423
-
424
- const isLastPanel =
425
- findPanelDataIndex(panelDataArray, panelData) ===
426
- panelDataArray.length - 1;
427
- const delta = isLastPanel
428
- ? panelSize - baseSize
429
- : baseSize - panelSize;
430
-
431
- const nextLayout = adjustLayoutByDelta({
432
- delta,
433
- initialLayout: prevLayout,
434
- panelConstraints: panelConstraintsArray,
435
- pivotIndices,
436
- prevLayout,
437
- trigger: "imperative-api",
438
- });
439
-
440
- if (!compareLayouts(prevLayout, nextLayout)) {
441
- setLayout(nextLayout);
442
-
443
- eagerValuesRef.current.layout = nextLayout;
444
-
445
- if (onLayout) {
446
- onLayout(nextLayout);
447
- }
448
-
449
- callPanelCallbacks(
450
- panelDataArray,
451
- nextLayout,
452
- panelIdToLastNotifiedSizeMapRef.current
453
- );
454
- }
455
- }
456
- }
457
- },
458
- []
459
- );
460
-
461
- // External APIs are safe to memoize via committed values ref
462
- const getPanelSize = useCallback((panelData: PanelData) => {
463
- const { layout, panelDataArray } = eagerValuesRef.current;
464
-
465
- const { panelSize } = panelDataHelper(panelDataArray, panelData, layout);
466
-
467
- assert(
468
- panelSize != null,
469
- `Panel size not found for panel "${panelData.id}"`
470
- );
471
-
472
- return panelSize;
473
- }, []);
474
-
475
- // This API should never read from committedValuesRef
476
- const getPanelStyle = useCallback(
477
- (panelData: PanelData, defaultSize: number | undefined) => {
478
- const { panelDataArray } = eagerValuesRef.current;
479
-
480
- const panelIndex = findPanelDataIndex(panelDataArray, panelData);
481
-
482
- return computePanelFlexBoxStyle({
483
- defaultSize,
484
- dragState,
485
- layout,
486
- panelData: panelDataArray,
487
- panelIndex,
488
- });
489
- },
490
- [dragState, layout]
491
- );
492
-
493
- // External APIs are safe to memoize via committed values ref
494
- const isPanelCollapsed = useCallback((panelData: PanelData) => {
495
- const { layout, panelDataArray } = eagerValuesRef.current;
496
-
497
- const {
498
- collapsedSize = 0,
499
- collapsible,
500
- panelSize,
501
- } = panelDataHelper(panelDataArray, panelData, layout);
502
-
503
- assert(
504
- panelSize != null,
505
- `Panel size not found for panel "${panelData.id}"`
506
- );
507
-
508
- return collapsible === true && fuzzyNumbersEqual(panelSize, collapsedSize);
509
- }, []);
510
-
511
- // External APIs are safe to memoize via committed values ref
512
- const isPanelExpanded = useCallback((panelData: PanelData) => {
513
- const { layout, panelDataArray } = eagerValuesRef.current;
514
-
515
- const {
516
- collapsedSize = 0,
517
- collapsible,
518
- panelSize,
519
- } = panelDataHelper(panelDataArray, panelData, layout);
520
-
521
- assert(
522
- panelSize != null,
523
- `Panel size not found for panel "${panelData.id}"`
524
- );
525
-
526
- return !collapsible || fuzzyCompareNumbers(panelSize, collapsedSize) > 0;
527
- }, []);
528
-
529
- const registerPanel = useCallback(
530
- (panelData: PanelData) => {
531
- const { panelDataArray } = eagerValuesRef.current;
532
-
533
- panelDataArray.push(panelData);
534
- panelDataArray.sort((panelA, panelB) => {
535
- const orderA = panelA.order;
536
- const orderB = panelB.order;
537
- if (orderA == null && orderB == null) {
538
- return 0;
539
- } else if (orderA == null) {
540
- return -1;
541
- } else if (orderB == null) {
542
- return 1;
543
- } else {
544
- return orderA - orderB;
545
- }
546
- });
547
-
548
- eagerValuesRef.current.panelDataArrayChanged = true;
549
-
550
- forceUpdate();
551
- },
552
- [forceUpdate]
553
- );
554
-
555
- // (Re)calculate group layout whenever panels are registered or unregistered.
556
- // eslint-disable-next-line react-hooks/exhaustive-deps
557
- useIsomorphicLayoutEffect(() => {
558
- if (eagerValuesRef.current.panelDataArrayChanged) {
559
- eagerValuesRef.current.panelDataArrayChanged = false;
560
-
561
- const { autoSaveId, onLayout, storage } = committedValuesRef.current;
562
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
563
-
564
- // If this panel has been configured to persist sizing information,
565
- // default size should be restored from local storage if possible.
566
- let unsafeLayout: number[] | null = null;
567
- if (autoSaveId) {
568
- const state = loadPanelGroupState(autoSaveId, panelDataArray, storage);
569
- if (state) {
570
- panelSizeBeforeCollapseRef.current = new Map(
571
- Object.entries(state.expandToSizes)
572
- );
573
- unsafeLayout = state.layout;
574
- }
575
- }
576
-
577
- if (unsafeLayout == null) {
578
- unsafeLayout = calculateUnsafeDefaultLayout({
579
- panelDataArray,
580
- });
581
- }
582
-
583
- // Validate even saved layouts in case something has changed since last render
584
- // e.g. for pixel groups, this could be the size of the window
585
- const nextLayout = validatePanelGroupLayout({
586
- layout: unsafeLayout,
587
- panelConstraints: panelDataArray.map(
588
- (panelData) => panelData.constraints
589
- ),
590
- });
591
-
592
- if (!areEqual(prevLayout, nextLayout)) {
593
- setLayout(nextLayout);
594
-
595
- eagerValuesRef.current.layout = nextLayout;
596
-
597
- if (onLayout) {
598
- onLayout(nextLayout);
599
- }
600
-
601
- callPanelCallbacks(
602
- panelDataArray,
603
- nextLayout,
604
- panelIdToLastNotifiedSizeMapRef.current
605
- );
606
- }
607
- }
608
- });
609
-
610
- // Reset the cached layout if hidden by the Activity/Offscreen API
611
- useIsomorphicLayoutEffect(() => {
612
- const eagerValues = eagerValuesRef.current;
613
- return () => {
614
- eagerValues.layout = [];
615
- };
616
- }, []);
617
-
618
- const registerResizeHandle = useCallback((dragHandleId: string) => {
619
- let isRTL = false;
620
-
621
- const panelGroupElement = panelGroupElementRef.current;
622
- if (panelGroupElement) {
623
- const style = window.getComputedStyle(panelGroupElement, null);
624
- if (style.getPropertyValue("direction") === "rtl") {
625
- isRTL = true;
626
- }
627
- }
628
-
629
- return function resizeHandler(event: ResizeEvent) {
630
- event.preventDefault();
631
-
632
- const panelGroupElement = panelGroupElementRef.current;
633
- if (!panelGroupElement) {
634
- return () => null;
635
- }
636
-
637
- const {
638
- direction,
639
- dragState,
640
- id: groupId,
641
- keyboardResizeBy,
642
- onLayout,
643
- } = committedValuesRef.current;
644
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
645
-
646
- const { initialLayout } = dragState ?? {};
647
-
648
- const pivotIndices = determinePivotIndices(
649
- groupId,
650
- dragHandleId,
651
- panelGroupElement
652
- );
653
-
654
- let delta = calculateDeltaPercentage(
655
- event,
656
- dragHandleId,
657
- direction,
658
- dragState,
659
- keyboardResizeBy,
660
- panelGroupElement
661
- );
662
-
663
- const isHorizontal = direction === "horizontal";
664
-
665
- if (isHorizontal && isRTL) {
666
- delta = -delta;
667
- }
668
-
669
- const panelConstraints = panelDataArray.map(
670
- (panelData) => panelData.constraints
671
- );
672
-
673
- const nextLayout = adjustLayoutByDelta({
674
- delta,
675
- initialLayout: initialLayout ?? prevLayout,
676
- panelConstraints,
677
- pivotIndices,
678
- prevLayout,
679
- trigger: isKeyDown(event) ? "keyboard" : "mouse-or-touch",
680
- });
681
-
682
- const layoutChanged = !compareLayouts(prevLayout, nextLayout);
683
-
684
- // Only update the cursor for layout changes triggered by touch/mouse events (not keyboard)
685
- // Update the cursor even if the layout hasn't changed (we may need to show an invalid cursor state)
686
- if (isPointerEvent(event) || isMouseEvent(event)) {
687
- // Watch for multiple subsequent deltas; this might occur for tiny cursor movements.
688
- // In this case, Panel sizes might not change–
689
- // but updating cursor in this scenario would cause a flicker.
690
- if (prevDeltaRef.current != delta) {
691
- prevDeltaRef.current = delta;
692
-
693
- if (!layoutChanged && delta !== 0) {
694
- // If the pointer has moved too far to resize the panel any further, note this so we can update the cursor.
695
- // This mimics VS Code behavior.
696
- if (isHorizontal) {
697
- reportConstraintsViolation(
698
- dragHandleId,
699
- delta < 0 ? EXCEEDED_HORIZONTAL_MIN : EXCEEDED_HORIZONTAL_MAX
700
- );
701
- } else {
702
- reportConstraintsViolation(
703
- dragHandleId,
704
- delta < 0 ? EXCEEDED_VERTICAL_MIN : EXCEEDED_VERTICAL_MAX
705
- );
706
- }
707
- } else {
708
- reportConstraintsViolation(dragHandleId, 0);
709
- }
710
- }
711
- }
712
-
713
- if (layoutChanged) {
714
- setLayout(nextLayout);
715
-
716
- eagerValuesRef.current.layout = nextLayout;
717
-
718
- if (onLayout) {
719
- onLayout(nextLayout);
720
- }
721
-
722
- callPanelCallbacks(
723
- panelDataArray,
724
- nextLayout,
725
- panelIdToLastNotifiedSizeMapRef.current
726
- );
727
- }
728
- };
729
- }, []);
730
-
731
- // External APIs are safe to memoize via committed values ref
732
- const resizePanel = useCallback(
733
- (panelData: PanelData, unsafePanelSize: number) => {
734
- const { onLayout } = committedValuesRef.current;
735
-
736
- const { layout: prevLayout, panelDataArray } = eagerValuesRef.current;
737
-
738
- const panelConstraintsArray = panelDataArray.map(
739
- (panelData) => panelData.constraints
740
- );
741
-
742
- const { panelSize, pivotIndices } = panelDataHelper(
743
- panelDataArray,
744
- panelData,
745
- prevLayout
746
- );
747
-
748
- assert(
749
- panelSize != null,
750
- `Panel size not found for panel "${panelData.id}"`
751
- );
752
-
753
- const isLastPanel =
754
- findPanelDataIndex(panelDataArray, panelData) ===
755
- panelDataArray.length - 1;
756
- const delta = isLastPanel
757
- ? panelSize - unsafePanelSize
758
- : unsafePanelSize - panelSize;
759
-
760
- const nextLayout = adjustLayoutByDelta({
761
- delta,
762
- initialLayout: prevLayout,
763
- panelConstraints: panelConstraintsArray,
764
- pivotIndices,
765
- prevLayout,
766
- trigger: "imperative-api",
767
- });
768
-
769
- if (!compareLayouts(prevLayout, nextLayout)) {
770
- setLayout(nextLayout);
771
-
772
- eagerValuesRef.current.layout = nextLayout;
773
-
774
- if (onLayout) {
775
- onLayout(nextLayout);
776
- }
777
-
778
- callPanelCallbacks(
779
- panelDataArray,
780
- nextLayout,
781
- panelIdToLastNotifiedSizeMapRef.current
782
- );
783
- }
784
- },
785
- []
786
- );
787
-
788
- const reevaluatePanelConstraints = useCallback(
789
- (panelData: PanelData, prevConstraints: PanelConstraints) => {
790
- const { layout, panelDataArray } = eagerValuesRef.current;
791
-
792
- const {
793
- collapsedSize: prevCollapsedSize = 0,
794
- collapsible: prevCollapsible,
795
- } = prevConstraints;
796
-
797
- const {
798
- collapsedSize: nextCollapsedSize = 0,
799
- collapsible: nextCollapsible,
800
- maxSize: nextMaxSize = 100,
801
- minSize: nextMinSize = 0,
802
- } = panelData.constraints;
803
-
804
- const { panelSize: prevPanelSize } = panelDataHelper(
805
- panelDataArray,
806
- panelData,
807
- layout
808
- );
809
- if (prevPanelSize == null) {
810
- // It's possible that the panels in this group have changed since the last render
811
- return;
812
- }
813
-
814
- if (
815
- prevCollapsible &&
816
- nextCollapsible &&
817
- fuzzyNumbersEqual(prevPanelSize, prevCollapsedSize)
818
- ) {
819
- if (!fuzzyNumbersEqual(prevCollapsedSize, nextCollapsedSize)) {
820
- resizePanel(panelData, nextCollapsedSize);
821
- } else {
822
- // Stay collapsed
823
- }
824
- } else if (prevPanelSize < nextMinSize) {
825
- resizePanel(panelData, nextMinSize);
826
- } else if (prevPanelSize > nextMaxSize) {
827
- resizePanel(panelData, nextMaxSize);
828
- }
829
- },
830
- [resizePanel]
831
- );
832
-
833
- // TODO Multiple drag handles can be active at the same time so this API is a bit awkward now
834
- const startDragging = useCallback(
835
- (dragHandleId: string, event: ResizeEvent) => {
836
- const { direction } = committedValuesRef.current;
837
- const { layout } = eagerValuesRef.current;
838
- if (!panelGroupElementRef.current) {
839
- return;
840
- }
841
- const handleElement = getResizeHandleElement(
842
- dragHandleId,
843
- panelGroupElementRef.current
844
- );
845
- assert(
846
- handleElement,
847
- `Drag handle element not found for id "${dragHandleId}"`
848
- );
849
-
850
- const initialCursorPosition = getResizeEventCursorPosition(
851
- direction,
852
- event
853
- );
854
-
855
- setDragState({
856
- dragHandleId,
857
- dragHandleRect: handleElement.getBoundingClientRect(),
858
- initialCursorPosition,
859
- initialLayout: layout,
860
- });
861
- },
862
- []
863
- );
864
-
865
- const stopDragging = useCallback(() => {
866
- setDragState(null);
867
- }, []);
868
-
869
- const unregisterPanel = useCallback(
870
- (panelData: PanelData) => {
871
- const { panelDataArray } = eagerValuesRef.current;
872
-
873
- const index = findPanelDataIndex(panelDataArray, panelData);
874
- if (index >= 0) {
875
- panelDataArray.splice(index, 1);
876
-
877
- // TRICKY
878
- // When a panel is removed from the group, we should delete the most recent prev-size entry for it.
879
- // If we don't do this, then a conditionally rendered panel might not call onResize when it's re-mounted.
880
- // Strict effects mode makes this tricky though because all panels will be registered, unregistered, then re-registered on mount.
881
- delete panelIdToLastNotifiedSizeMapRef.current[panelData.id];
882
-
883
- eagerValuesRef.current.panelDataArrayChanged = true;
884
-
885
- forceUpdate();
886
- }
887
- },
888
- [forceUpdate]
889
- );
890
-
891
- const context = useMemo(
892
- () =>
893
- ({
894
- collapsePanel,
895
- direction,
896
- dragState,
897
- expandPanel,
898
- getPanelSize,
899
- getPanelStyle,
900
- groupId,
901
- isPanelCollapsed,
902
- isPanelExpanded,
903
- reevaluatePanelConstraints,
904
- registerPanel,
905
- registerResizeHandle,
906
- resizePanel,
907
- startDragging,
908
- stopDragging,
909
- unregisterPanel,
910
- panelGroupElement: panelGroupElementRef.current,
911
- }) satisfies TPanelGroupContext,
912
- [
913
- collapsePanel,
914
- dragState,
915
- direction,
916
- expandPanel,
917
- getPanelSize,
918
- getPanelStyle,
919
- groupId,
920
- isPanelCollapsed,
921
- isPanelExpanded,
922
- reevaluatePanelConstraints,
923
- registerPanel,
924
- registerResizeHandle,
925
- resizePanel,
926
- startDragging,
927
- stopDragging,
928
- unregisterPanel,
929
- ]
930
- );
931
-
932
- const style: CSSProperties = {
933
- display: "flex",
934
- flexDirection: direction === "horizontal" ? "row" : "column",
935
- height: "100%",
936
- overflow: "hidden",
937
- width: "100%",
938
- };
939
-
940
- return createElement(
941
- PanelGroupContext.Provider,
942
- { value: context },
943
- createElement(Type, {
944
- ...rest,
945
-
946
- children,
947
- className: classNameFromProps,
948
- id: idFromProps,
949
- ref: panelGroupElementRef,
950
- style: {
951
- ...style,
952
- ...styleFromProps,
953
- },
954
-
955
- // CSS selectors
956
- "data-panel-group": "",
957
- "data-panel-group-direction": direction,
958
- "data-panel-group-id": groupId,
959
- })
960
- );
961
- }
962
-
963
- export const PanelGroup = forwardRef<
964
- ImperativePanelGroupHandle,
965
- PanelGroupProps
966
- >((props: PanelGroupProps, ref: ForwardedRef<ImperativePanelGroupHandle>) =>
967
- createElement(PanelGroupWithForwardedRef, { ...props, forwardedRef: ref })
968
- );
969
-
970
- PanelGroupWithForwardedRef.displayName = "PanelGroup";
971
- PanelGroup.displayName = "forwardRef(PanelGroup)";
972
-
973
- function findPanelDataIndex(panelDataArray: PanelData[], panelData: PanelData) {
974
- return panelDataArray.findIndex(
975
- (prevPanelData) =>
976
- prevPanelData === panelData || prevPanelData.id === panelData.id
977
- );
978
- }
979
-
980
- function panelDataHelper(
981
- panelDataArray: PanelData[],
982
- panelData: PanelData,
983
- layout: number[]
984
- ) {
985
- const panelIndex = findPanelDataIndex(panelDataArray, panelData);
986
-
987
- const isLastPanel = panelIndex === panelDataArray.length - 1;
988
- const pivotIndices = isLastPanel
989
- ? [panelIndex - 1, panelIndex]
990
- : [panelIndex, panelIndex + 1];
991
-
992
- const panelSize = layout[panelIndex];
993
-
994
- return {
995
- ...panelData.constraints,
996
- panelSize,
997
- pivotIndices,
998
- };
999
- }