react-resizable-panels 0.0.54 → 0.0.56

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 (86) hide show
  1. package/.eslintrc.cjs +26 -0
  2. package/CHANGELOG.md +253 -80
  3. package/README.md +55 -49
  4. package/dist/declarations/src/Panel.d.ts +76 -20
  5. package/dist/declarations/src/PanelGroup.d.ts +29 -21
  6. package/dist/declarations/src/PanelResizeHandle.d.ts +1 -1
  7. package/dist/declarations/src/index.d.ts +5 -5
  8. package/dist/declarations/src/types.d.ts +3 -25
  9. package/dist/declarations/src/vendor/react.d.ts +4 -4
  10. package/dist/react-resizable-panels.browser.cjs.js +1279 -796
  11. package/dist/react-resizable-panels.browser.development.cjs.js +1404 -809
  12. package/dist/react-resizable-panels.browser.development.esm.js +1398 -803
  13. package/dist/react-resizable-panels.browser.esm.js +1279 -796
  14. package/dist/react-resizable-panels.cjs.js +1279 -796
  15. package/dist/react-resizable-panels.cjs.js.map +1 -0
  16. package/dist/react-resizable-panels.development.cjs.js +1399 -804
  17. package/dist/react-resizable-panels.development.esm.js +1400 -805
  18. package/dist/react-resizable-panels.development.node.cjs.js +1172 -755
  19. package/dist/react-resizable-panels.development.node.esm.js +1173 -756
  20. package/dist/react-resizable-panels.esm.js +1279 -796
  21. package/dist/react-resizable-panels.esm.js.map +1 -0
  22. package/dist/react-resizable-panels.node.cjs.js +1064 -749
  23. package/dist/react-resizable-panels.node.esm.js +1065 -750
  24. package/jest.config.js +10 -0
  25. package/package.json +3 -1
  26. package/src/Panel.test.tsx +308 -0
  27. package/src/Panel.ts +179 -127
  28. package/src/PanelGroup.test.tsx +210 -0
  29. package/src/PanelGroup.ts +751 -580
  30. package/src/PanelGroupContext.ts +33 -0
  31. package/src/PanelResizeHandle.ts +13 -8
  32. package/src/hooks/useUniqueId.ts +1 -1
  33. package/src/hooks/useWindowSplitterBehavior.ts +9 -161
  34. package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +185 -0
  35. package/src/index.ts +24 -11
  36. package/src/types.ts +3 -29
  37. package/src/utils/adjustLayoutByDelta.test.ts +1808 -0
  38. package/src/utils/adjustLayoutByDelta.ts +211 -0
  39. package/src/utils/calculateAriaValues.test.ts +111 -0
  40. package/src/utils/calculateAriaValues.ts +67 -0
  41. package/src/utils/calculateDeltaPercentage.ts +68 -0
  42. package/src/utils/calculateDragOffsetPercentage.ts +30 -0
  43. package/src/utils/calculateUnsafeDefaultLayout.test.ts +92 -0
  44. package/src/utils/calculateUnsafeDefaultLayout.ts +55 -0
  45. package/src/utils/callPanelCallbacks.ts +81 -0
  46. package/src/utils/compareLayouts.test.ts +9 -0
  47. package/src/utils/compareLayouts.ts +12 -0
  48. package/src/utils/computePanelFlexBoxStyle.ts +44 -0
  49. package/src/utils/computePercentagePanelConstraints.test.ts +71 -0
  50. package/src/utils/computePercentagePanelConstraints.ts +56 -0
  51. package/src/utils/convertPercentageToPixels.test.ts +9 -0
  52. package/src/utils/convertPercentageToPixels.ts +6 -0
  53. package/src/utils/convertPixelConstraintsToPercentages.ts +55 -0
  54. package/src/utils/convertPixelsToPercentage.test.ts +9 -0
  55. package/src/utils/convertPixelsToPercentage.ts +6 -0
  56. package/src/utils/determinePivotIndices.ts +10 -0
  57. package/src/utils/dom/calculateAvailablePanelSizeInPixels.ts +29 -0
  58. package/src/utils/dom/getAvailableGroupSizePixels.ts +29 -0
  59. package/src/utils/dom/getPanelElement.ts +7 -0
  60. package/src/utils/dom/getPanelGroupElement.ts +7 -0
  61. package/src/utils/dom/getResizeHandleElement.ts +9 -0
  62. package/src/utils/dom/getResizeHandleElementIndex.ts +12 -0
  63. package/src/utils/dom/getResizeHandleElementsForGroup.ts +9 -0
  64. package/src/utils/dom/getResizeHandlePanelIds.ts +18 -0
  65. package/src/utils/events.ts +13 -0
  66. package/src/utils/getPercentageSizeFromMixedSizes.test.ts +47 -0
  67. package/src/utils/getPercentageSizeFromMixedSizes.ts +15 -0
  68. package/src/utils/getResizeEventCursorPosition.ts +19 -0
  69. package/src/utils/initializeDefaultStorage.ts +26 -0
  70. package/src/utils/numbers/fuzzyCompareNumbers.test.ts +16 -0
  71. package/src/utils/numbers/fuzzyCompareNumbers.ts +17 -0
  72. package/src/utils/numbers/fuzzyNumbersEqual.ts +9 -0
  73. package/src/utils/resizePanel.ts +41 -0
  74. package/src/utils/serialization.ts +9 -4
  75. package/src/utils/shouldMonitorPixelBasedConstraints.test.ts +23 -0
  76. package/src/utils/shouldMonitorPixelBasedConstraints.ts +13 -0
  77. package/src/utils/test-utils.ts +136 -0
  78. package/src/utils/validatePanelConstraints.test.ts +151 -0
  79. package/src/utils/validatePanelConstraints.ts +103 -0
  80. package/src/utils/validatePanelGroupLayout.test.ts +233 -0
  81. package/src/utils/validatePanelGroupLayout.ts +88 -0
  82. package/src/vendor/react.ts +4 -0
  83. package/.eslintrc.json +0 -22
  84. package/src/PanelContexts.ts +0 -20
  85. package/src/utils/coordinates.ts +0 -149
  86. package/src/utils/group.ts +0 -315
@@ -0,0 +1,9 @@
1
+ import { fuzzyCompareNumbers } from "./fuzzyCompareNumbers";
2
+
3
+ export function fuzzyNumbersEqual(
4
+ actual: number,
5
+ expected: number,
6
+ fractionDigits?: number
7
+ ): boolean {
8
+ return fuzzyCompareNumbers(actual, expected, fractionDigits) === 0;
9
+ }
@@ -0,0 +1,41 @@
1
+ import { PanelConstraints } from "../Panel";
2
+ import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints";
3
+ import { fuzzyCompareNumbers } from "./numbers/fuzzyCompareNumbers";
4
+
5
+ // Panel size must be in percentages; pixel values should be pre-converted
6
+ export function resizePanel({
7
+ groupSizePixels,
8
+ panelConstraints,
9
+ panelIndex,
10
+ size,
11
+ }: {
12
+ groupSizePixels: number;
13
+ panelConstraints: PanelConstraints[];
14
+ panelIndex: number;
15
+ size: number;
16
+ }) {
17
+ let { collapsible } = panelConstraints[panelIndex]!;
18
+
19
+ const { collapsedSizePercentage, maxSizePercentage, minSizePercentage } =
20
+ computePercentagePanelConstraints(
21
+ panelConstraints,
22
+ panelIndex,
23
+ groupSizePixels
24
+ );
25
+
26
+ if (minSizePercentage != null) {
27
+ if (fuzzyCompareNumbers(size, minSizePercentage) < 0) {
28
+ if (collapsible) {
29
+ size = collapsedSizePercentage;
30
+ } else {
31
+ size = minSizePercentage;
32
+ }
33
+ }
34
+ }
35
+
36
+ if (maxSizePercentage != null) {
37
+ size = Math.min(maxSizePercentage, size);
38
+ }
39
+
40
+ return size;
41
+ }
@@ -1,16 +1,21 @@
1
- import { PanelData, PanelGroupStorage } from "../types";
1
+ import { PanelData } from "../Panel";
2
+ import { PanelGroupStorage } from "../PanelGroup";
2
3
 
3
4
  type SerializedPanelGroupState = { [panelIds: string]: number[] };
4
5
 
5
6
  // Note that Panel ids might be user-provided (stable) or useId generated (non-deterministic)
6
7
  // so they should not be used as part of the serialization key.
7
- // Using an attribute like minSize instead should work well enough.
8
+ // Using the min/max size attributes should work well enough as a backup.
8
9
  // Pre-sorting by minSize allows remembering layouts even if panels are re-ordered/dragged.
9
10
  function getSerializationKey(panels: PanelData[]): string {
10
11
  return panels
11
12
  .map((panel) => {
12
- const { minSize, order } = panel.current;
13
- return order ? `${order}:${minSize}` : `${minSize}`;
13
+ const { constraints, id, idIsFromProps, order } = panel;
14
+ if (idIsFromProps) {
15
+ return id;
16
+ } else {
17
+ return `${order}:${JSON.stringify(constraints)}`;
18
+ }
14
19
  })
15
20
  .sort((a, b) => a.localeCompare(b))
16
21
  .join(",");
@@ -0,0 +1,23 @@
1
+ import { shouldMonitorPixelBasedConstraints } from "./shouldMonitorPixelBasedConstraints";
2
+
3
+ describe("shouldMonitorPixelBasedConstraints", () => {
4
+ it("should identify min, max, or collapsed size pixel constraints", () => {
5
+ expect(
6
+ shouldMonitorPixelBasedConstraints([{}, { collapsedSizePixels: 100 }, {}])
7
+ ).toBe(true);
8
+
9
+ expect(
10
+ shouldMonitorPixelBasedConstraints([{ minSizePixels: 100 }, {}, {}])
11
+ ).toBe(true);
12
+
13
+ expect(
14
+ shouldMonitorPixelBasedConstraints([{}, {}, { maxSizePixels: 100 }])
15
+ ).toBe(true);
16
+ });
17
+
18
+ it("should ignore default size constraints", () => {
19
+ expect(
20
+ shouldMonitorPixelBasedConstraints([{ defaultSizePixels: 100 }])
21
+ ).toBe(false);
22
+ });
23
+ });
@@ -0,0 +1,13 @@
1
+ import { PanelConstraints } from "../Panel";
2
+
3
+ export function shouldMonitorPixelBasedConstraints(
4
+ constraints: PanelConstraints[]
5
+ ): boolean {
6
+ return constraints.some((constraints) => {
7
+ return (
8
+ constraints.collapsedSizePixels !== undefined ||
9
+ constraints.maxSizePixels !== undefined ||
10
+ constraints.minSizePixels !== undefined
11
+ );
12
+ });
13
+ }
@@ -0,0 +1,136 @@
1
+ import { MixedSizes } from "../types";
2
+
3
+ const util = require("util");
4
+
5
+ export function expectToBeCloseToArray(
6
+ actualNumbers: number[],
7
+ expectedNumbers: number[]
8
+ ) {
9
+ expect(actualNumbers.length).toBe(expectedNumbers.length);
10
+
11
+ try {
12
+ actualNumbers.forEach((actualNumber, index) => {
13
+ const expectedNumber = expectedNumbers[index];
14
+ expect(actualNumber).toBeCloseTo(expectedNumber, 1);
15
+ });
16
+ } catch (error) {
17
+ expect(actualNumbers).toEqual(expectedNumbers);
18
+ }
19
+ }
20
+
21
+ export function mockPanelGroupOffsetWidthAndHeight(
22
+ mockWidth = 1_000,
23
+ mockHeight = 1_000
24
+ ) {
25
+ const offsetHeightPropertyDescriptor = Object.getOwnPropertyDescriptor(
26
+ HTMLElement.prototype,
27
+ "offsetHeight"
28
+ );
29
+
30
+ const offsetWidthPropertyDescriptor = Object.getOwnPropertyDescriptor(
31
+ HTMLElement.prototype,
32
+ "offsetWidth"
33
+ );
34
+
35
+ Object.defineProperty(HTMLElement.prototype, "offsetHeight", {
36
+ configurable: true,
37
+ get: function () {
38
+ if (this.hasAttribute("data-resize-handle")) {
39
+ return 0;
40
+ } else if (this.hasAttribute("data-panel-group")) {
41
+ return mockHeight;
42
+ }
43
+ },
44
+ });
45
+
46
+ Object.defineProperty(HTMLElement.prototype, "offsetWidth", {
47
+ configurable: true,
48
+ get: function () {
49
+ if (this.hasAttribute("data-resize-handle")) {
50
+ return 0;
51
+ } else if (this.hasAttribute("data-panel-group")) {
52
+ return mockWidth;
53
+ }
54
+ },
55
+ });
56
+
57
+ return function uninstallMocks() {
58
+ if (offsetHeightPropertyDescriptor) {
59
+ Object.defineProperty(
60
+ HTMLElement.prototype,
61
+ "offsetHeight",
62
+ offsetHeightPropertyDescriptor
63
+ );
64
+ }
65
+
66
+ if (offsetWidthPropertyDescriptor) {
67
+ Object.defineProperty(
68
+ HTMLElement.prototype,
69
+ "offsetWidth",
70
+ offsetWidthPropertyDescriptor
71
+ );
72
+ }
73
+ };
74
+ }
75
+
76
+ export function verifyExpandedPanelGroupLayout(
77
+ actualLayout: MixedSizes[],
78
+ expectedPercentages: number[]
79
+ ) {
80
+ expect(actualLayout).toHaveLength(expectedPercentages.length);
81
+ expect(actualLayout.map(({ sizePercentage }) => sizePercentage)).toEqual(
82
+ expectedPercentages
83
+ );
84
+ }
85
+
86
+ export function verifyExpectedWarnings(
87
+ callback: Function,
88
+ ...expectedMessages: string[]
89
+ ) {
90
+ const consoleSpy = (format: any, ...args: any[]) => {
91
+ const message = util.format(format, ...args);
92
+
93
+ for (let index = 0; index < expectedMessages.length; index++) {
94
+ const expectedMessage = expectedMessages[index];
95
+ if (message.includes(expectedMessage)) {
96
+ expectedMessages.splice(index, 1);
97
+ return;
98
+ }
99
+ }
100
+
101
+ if (expectedMessages.length === 0) {
102
+ throw new Error(`Unexpected message recorded:\n\n${message}`);
103
+ }
104
+ };
105
+
106
+ const originalError = console.error;
107
+ const originalWarn = console.warn;
108
+
109
+ console.error = consoleSpy;
110
+ console.warn = consoleSpy;
111
+
112
+ let caughtError;
113
+ let didCatch = false;
114
+ try {
115
+ callback();
116
+ } catch (error) {
117
+ caughtError = error;
118
+ didCatch = true;
119
+ } finally {
120
+ console.error = originalError;
121
+ console.warn = originalWarn;
122
+
123
+ if (didCatch) {
124
+ throw caughtError;
125
+ }
126
+
127
+ // Any remaining messages indicate a failed expectations.
128
+ if (expectedMessages.length > 0) {
129
+ throw Error(
130
+ `Expected message(s) not recorded:\n\n${expectedMessages.join("\n")}`
131
+ );
132
+ }
133
+
134
+ return { pass: true };
135
+ }
136
+ }
@@ -0,0 +1,151 @@
1
+ import { verifyExpectedWarnings } from "./test-utils";
2
+ import { validatePanelConstraints } from "./validatePanelConstraints";
3
+
4
+ describe("validatePanelConstraints", () => {
5
+ it("should not warn if there are no validation errors", () => {
6
+ verifyExpectedWarnings(() => {
7
+ validatePanelConstraints({
8
+ groupSizePixels: 100_000,
9
+ panelConstraints: [{}],
10
+ panelIndex: 0,
11
+ panelId: "test",
12
+ });
13
+ });
14
+ });
15
+
16
+ it("should warn about conflicting percentages and pixels", () => {
17
+ verifyExpectedWarnings(() => {
18
+ validatePanelConstraints({
19
+ groupSizePixels: 100_000,
20
+ panelConstraints: [
21
+ {
22
+ collapsedSizePercentage: 5,
23
+ collapsedSizePixels: 10,
24
+ },
25
+ ],
26
+ panelIndex: 0,
27
+ panelId: "test",
28
+ });
29
+ }, "should not specify both percentage and pixel units for: collapsed size");
30
+
31
+ verifyExpectedWarnings(() => {
32
+ validatePanelConstraints({
33
+ groupSizePixels: 100_000,
34
+ panelConstraints: [
35
+ {
36
+ maxSizePercentage: 5,
37
+ maxSizePixels: 10,
38
+ minSizePercentage: 5,
39
+ minSizePixels: 10,
40
+ },
41
+ ],
42
+ panelIndex: 0,
43
+ panelId: "test",
44
+ });
45
+ }, "should not specify both percentage and pixel units for: max size, min size");
46
+
47
+ verifyExpectedWarnings(() => {
48
+ validatePanelConstraints({
49
+ groupSizePixels: 100_000,
50
+ panelConstraints: [
51
+ {
52
+ defaultSizePercentage: 5,
53
+ defaultSizePixels: 10,
54
+ },
55
+ ],
56
+ panelIndex: 0,
57
+ panelId: "test",
58
+ });
59
+ }, "should not specify both percentage and pixel units for: default size");
60
+ });
61
+
62
+ it("should warn about conflicting min/max sizes", () => {
63
+ verifyExpectedWarnings(() => {
64
+ validatePanelConstraints({
65
+ groupSizePixels: 100_000,
66
+ panelConstraints: [
67
+ {
68
+ maxSizePercentage: 5,
69
+ minSizePercentage: 10,
70
+ },
71
+ ],
72
+ panelIndex: 0,
73
+ panelId: "test",
74
+ });
75
+ }, "min size (10%) should not be greater than max size (5%)");
76
+ });
77
+
78
+ it("should warn about conflicting collapsed and min sizes", () => {
79
+ verifyExpectedWarnings(() => {
80
+ validatePanelConstraints({
81
+ groupSizePixels: 100_000,
82
+ panelConstraints: [
83
+ {
84
+ collapsedSizePercentage: 15,
85
+ minSizePercentage: 10,
86
+ },
87
+ ],
88
+ panelIndex: 0,
89
+ panelId: "test",
90
+ });
91
+ }, "collapsed size should not be greater than min size");
92
+ });
93
+
94
+ it("should warn about conflicting default and min/max sizes", () => {
95
+ verifyExpectedWarnings(() => {
96
+ validatePanelConstraints({
97
+ groupSizePixels: 100_000,
98
+ panelConstraints: [
99
+ {
100
+ defaultSizePercentage: -1,
101
+ minSizePercentage: 10,
102
+ },
103
+ ],
104
+ panelIndex: 0,
105
+ panelId: "test",
106
+ });
107
+ }, "default size should not be less than 0");
108
+
109
+ verifyExpectedWarnings(() => {
110
+ validatePanelConstraints({
111
+ groupSizePixels: 100_000,
112
+ panelConstraints: [
113
+ {
114
+ defaultSizePercentage: 5,
115
+ minSizePercentage: 10,
116
+ },
117
+ ],
118
+ panelIndex: 0,
119
+ panelId: "test",
120
+ });
121
+ }, "default size should not be less than min size");
122
+
123
+ verifyExpectedWarnings(() => {
124
+ validatePanelConstraints({
125
+ groupSizePixels: 100_000,
126
+ panelConstraints: [
127
+ {
128
+ defaultSizePercentage: 101,
129
+ maxSizePercentage: 10,
130
+ },
131
+ ],
132
+ panelIndex: 0,
133
+ panelId: "test",
134
+ });
135
+ }, "default size should not be greater than 100");
136
+
137
+ verifyExpectedWarnings(() => {
138
+ validatePanelConstraints({
139
+ groupSizePixels: 100_000,
140
+ panelConstraints: [
141
+ {
142
+ defaultSizePercentage: 15,
143
+ maxSizePercentage: 10,
144
+ },
145
+ ],
146
+ panelIndex: 0,
147
+ panelId: "test",
148
+ });
149
+ }, "default size should not be greater than max size");
150
+ });
151
+ });
@@ -0,0 +1,103 @@
1
+ import { isDevelopment } from "#is-development";
2
+ import { PanelConstraints } from "../Panel";
3
+ import { computePercentagePanelConstraints } from "./computePercentagePanelConstraints";
4
+
5
+ export function validatePanelConstraints({
6
+ groupSizePixels,
7
+ panelConstraints,
8
+ panelId,
9
+ panelIndex,
10
+ }: {
11
+ groupSizePixels: number;
12
+ panelConstraints: PanelConstraints[];
13
+ panelId: string | undefined;
14
+ panelIndex: number;
15
+ }): boolean {
16
+ if (isDevelopment) {
17
+ const warnings = [];
18
+
19
+ {
20
+ const {
21
+ collapsedSizePercentage,
22
+ collapsedSizePixels,
23
+ defaultSizePercentage,
24
+ defaultSizePixels,
25
+ maxSizePercentage,
26
+ maxSizePixels,
27
+ minSizePercentage,
28
+ minSizePixels,
29
+ } = panelConstraints[panelIndex]!;
30
+
31
+ const conflictingUnits: string[] = [];
32
+
33
+ if (collapsedSizePercentage != null && collapsedSizePixels != null) {
34
+ conflictingUnits.push("collapsed size");
35
+ }
36
+ if (defaultSizePercentage != null && defaultSizePixels != null) {
37
+ conflictingUnits.push("default size");
38
+ }
39
+ if (maxSizePercentage != null && maxSizePixels != null) {
40
+ conflictingUnits.push("max size");
41
+ }
42
+ if (minSizePercentage != null && minSizePixels != null) {
43
+ conflictingUnits.push("min size");
44
+ }
45
+
46
+ if (conflictingUnits.length > 0) {
47
+ warnings.push(
48
+ `should not specify both percentage and pixel units for: ${conflictingUnits.join(
49
+ ", "
50
+ )}`
51
+ );
52
+ }
53
+ }
54
+
55
+ {
56
+ const {
57
+ collapsedSizePercentage,
58
+ defaultSizePercentage,
59
+ maxSizePercentage,
60
+ minSizePercentage,
61
+ } = computePercentagePanelConstraints(
62
+ panelConstraints,
63
+ panelIndex,
64
+ groupSizePixels
65
+ );
66
+
67
+ if (minSizePercentage > maxSizePercentage) {
68
+ warnings.push(
69
+ `min size (${minSizePercentage}%) should not be greater than max size (${maxSizePercentage}%)`
70
+ );
71
+ }
72
+
73
+ if (defaultSizePercentage != null) {
74
+ if (defaultSizePercentage < 0) {
75
+ warnings.push("default size should not be less than 0");
76
+ } else if (defaultSizePercentage < minSizePercentage) {
77
+ warnings.push("default size should not be less than min size");
78
+ }
79
+
80
+ if (defaultSizePercentage > 100) {
81
+ warnings.push("default size should not be greater than 100");
82
+ } else if (defaultSizePercentage > maxSizePercentage) {
83
+ warnings.push("default size should not be greater than max size");
84
+ }
85
+ }
86
+
87
+ if (collapsedSizePercentage > minSizePercentage) {
88
+ warnings.push("collapsed size should not be greater than min size");
89
+ }
90
+ }
91
+
92
+ if (warnings.length > 0) {
93
+ const name = panelId != null ? `Panel "${panelId}"` : "Panel";
94
+ console.warn(
95
+ `${name} has an invalid configuration:\n\n${warnings.join("\n")}`
96
+ );
97
+
98
+ return false;
99
+ }
100
+ }
101
+
102
+ return true;
103
+ }