react-resizable-panels 0.0.55 → 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 (93) hide show
  1. package/.eslintrc.cjs +26 -0
  2. package/CHANGELOG.md +234 -90
  3. package/README.md +55 -49
  4. package/dist/declarations/src/Panel.d.ts +75 -20
  5. package/dist/declarations/src/PanelGroup.d.ts +29 -25
  6. package/dist/declarations/src/PanelResizeHandle.d.ts +1 -1
  7. package/dist/declarations/src/index.d.ts +5 -6
  8. package/dist/declarations/src/types.d.ts +3 -26
  9. package/dist/declarations/src/vendor/react.d.ts +4 -4
  10. package/dist/react-resizable-panels.browser.cjs.js +1241 -1035
  11. package/dist/react-resizable-panels.browser.cjs.mjs +1 -2
  12. package/dist/react-resizable-panels.browser.development.cjs.js +1367 -1081
  13. package/dist/react-resizable-panels.browser.development.cjs.mjs +1 -2
  14. package/dist/react-resizable-panels.browser.development.esm.js +1368 -1081
  15. package/dist/react-resizable-panels.browser.esm.js +1242 -1035
  16. package/dist/react-resizable-panels.cjs.js +1241 -1035
  17. package/dist/react-resizable-panels.cjs.js.map +1 -1
  18. package/dist/react-resizable-panels.cjs.mjs +1 -2
  19. package/dist/react-resizable-panels.development.cjs.js +1370 -1084
  20. package/dist/react-resizable-panels.development.cjs.mjs +1 -2
  21. package/dist/react-resizable-panels.development.esm.js +1371 -1084
  22. package/dist/react-resizable-panels.development.node.cjs.js +1151 -940
  23. package/dist/react-resizable-panels.development.node.cjs.mjs +1 -2
  24. package/dist/react-resizable-panels.development.node.esm.js +1152 -940
  25. package/dist/react-resizable-panels.esm.js +1242 -1035
  26. package/dist/react-resizable-panels.esm.js.map +1 -1
  27. package/dist/react-resizable-panels.node.cjs.js +1049 -912
  28. package/dist/react-resizable-panels.node.cjs.mjs +1 -2
  29. package/dist/react-resizable-panels.node.esm.js +1050 -912
  30. package/jest.config.js +10 -0
  31. package/package.json +3 -1
  32. package/src/Panel.test.tsx +308 -0
  33. package/src/Panel.ts +175 -123
  34. package/src/PanelGroup.test.tsx +210 -0
  35. package/src/PanelGroup.ts +730 -669
  36. package/src/PanelGroupContext.ts +33 -0
  37. package/src/PanelResizeHandle.ts +13 -8
  38. package/src/hooks/useUniqueId.ts +1 -1
  39. package/src/hooks/useWindowSplitterBehavior.ts +9 -164
  40. package/src/hooks/useWindowSplitterPanelGroupBehavior.ts +185 -0
  41. package/src/index.ts +19 -14
  42. package/src/types.ts +3 -30
  43. package/src/utils/adjustLayoutByDelta.test.ts +1808 -0
  44. package/src/utils/adjustLayoutByDelta.ts +211 -0
  45. package/src/utils/calculateAriaValues.test.ts +111 -0
  46. package/src/utils/calculateAriaValues.ts +67 -0
  47. package/src/utils/calculateDeltaPercentage.ts +68 -0
  48. package/src/utils/calculateDragOffsetPercentage.ts +30 -0
  49. package/src/utils/calculateUnsafeDefaultLayout.test.ts +92 -0
  50. package/src/utils/calculateUnsafeDefaultLayout.ts +55 -0
  51. package/src/utils/callPanelCallbacks.ts +81 -0
  52. package/src/utils/compareLayouts.test.ts +9 -0
  53. package/src/utils/compareLayouts.ts +12 -0
  54. package/src/utils/computePanelFlexBoxStyle.ts +44 -0
  55. package/src/utils/computePercentagePanelConstraints.test.ts +71 -0
  56. package/src/utils/computePercentagePanelConstraints.ts +56 -0
  57. package/src/utils/convertPercentageToPixels.test.ts +9 -0
  58. package/src/utils/convertPercentageToPixels.ts +6 -0
  59. package/src/utils/convertPixelConstraintsToPercentages.ts +55 -0
  60. package/src/utils/convertPixelsToPercentage.test.ts +9 -0
  61. package/src/utils/convertPixelsToPercentage.ts +6 -0
  62. package/src/utils/determinePivotIndices.ts +10 -0
  63. package/src/utils/dom/calculateAvailablePanelSizeInPixels.ts +29 -0
  64. package/src/utils/dom/getAvailableGroupSizePixels.ts +29 -0
  65. package/src/utils/dom/getPanelElement.ts +7 -0
  66. package/src/utils/dom/getPanelGroupElement.ts +7 -0
  67. package/src/utils/dom/getResizeHandleElement.ts +9 -0
  68. package/src/utils/dom/getResizeHandleElementIndex.ts +12 -0
  69. package/src/utils/dom/getResizeHandleElementsForGroup.ts +9 -0
  70. package/src/utils/dom/getResizeHandlePanelIds.ts +18 -0
  71. package/src/utils/events.ts +13 -0
  72. package/src/utils/getPercentageSizeFromMixedSizes.test.ts +47 -0
  73. package/src/utils/getPercentageSizeFromMixedSizes.ts +15 -0
  74. package/src/utils/getResizeEventCursorPosition.ts +19 -0
  75. package/src/utils/initializeDefaultStorage.ts +26 -0
  76. package/src/utils/numbers/fuzzyCompareNumbers.test.ts +16 -0
  77. package/src/utils/numbers/fuzzyCompareNumbers.ts +17 -0
  78. package/src/utils/numbers/fuzzyNumbersEqual.ts +9 -0
  79. package/src/utils/resizePanel.ts +41 -0
  80. package/src/utils/serialization.ts +9 -4
  81. package/src/utils/shouldMonitorPixelBasedConstraints.test.ts +23 -0
  82. package/src/utils/shouldMonitorPixelBasedConstraints.ts +13 -0
  83. package/src/utils/test-utils.ts +136 -0
  84. package/src/utils/validatePanelConstraints.test.ts +151 -0
  85. package/src/utils/validatePanelConstraints.ts +103 -0
  86. package/src/utils/validatePanelGroupLayout.test.ts +233 -0
  87. package/src/utils/validatePanelGroupLayout.ts +88 -0
  88. package/src/vendor/react.ts +4 -0
  89. package/.eslintrc.json +0 -22
  90. package/dist/declarations/src/utils/group.d.ts +0 -29
  91. package/src/PanelContexts.ts +0 -22
  92. package/src/utils/coordinates.ts +0 -149
  93. package/src/utils/group.ts +0 -614
@@ -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
+ }