react-resizable-panels 2.1.5 → 2.1.7

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