@octanejs/resizable-panels 0.0.1

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 (91) hide show
  1. package/LICENSE.upstream +21 -0
  2. package/README.md +22 -0
  3. package/UPSTREAM.md +64 -0
  4. package/package.json +61 -0
  5. package/src/components/group/Group.tsrx +244 -0
  6. package/src/components/group/GroupContext.ts +25 -0
  7. package/src/components/group/auto-save/getStorageKey.ts +3 -0
  8. package/src/components/group/auto-save/readLegacyLayout.ts +37 -0
  9. package/src/components/group/sortByElementOffset.ts +20 -0
  10. package/src/components/group/types.ts +50 -0
  11. package/src/components/group/useDefaultLayout.ts +152 -0
  12. package/src/components/group/useGroupCallbackRef.ts +6 -0
  13. package/src/components/group/useGroupContext.ts +12 -0
  14. package/src/components/group/useGroupImperativeHandle.ts +46 -0
  15. package/src/components/group/useGroupRef.ts +6 -0
  16. package/src/components/panel/Panel.tsrx +130 -0
  17. package/src/components/panel/types.ts +66 -0
  18. package/src/components/panel/usePanelCallbackRef.ts +6 -0
  19. package/src/components/panel/usePanelImperativeHandle.ts +42 -0
  20. package/src/components/panel/usePanelRef.ts +6 -0
  21. package/src/components/separator/Separator.tsrx +126 -0
  22. package/src/components/separator/types.ts +22 -0
  23. package/src/constants.ts +25 -0
  24. package/src/global/cursor/getCursorStyle.ts +102 -0
  25. package/src/global/cursor/supportsAdvancedCursorStyles.ts +25 -0
  26. package/src/global/cursor/updateCursorStyle.ts +77 -0
  27. package/src/global/dom/calculateAvailableGroupSize.ts +11 -0
  28. package/src/global/dom/calculateHitRegions.ts +200 -0
  29. package/src/global/dom/calculatePanelConstraints.ts +84 -0
  30. package/src/global/event-handlers/onDocumentContextMenu.ts +9 -0
  31. package/src/global/event-handlers/onDocumentDoubleClick.ts +31 -0
  32. package/src/global/event-handlers/onDocumentKeyDown.ts +133 -0
  33. package/src/global/event-handlers/onDocumentPointerDown.ts +56 -0
  34. package/src/global/event-handlers/onDocumentPointerLeave.ts +21 -0
  35. package/src/global/event-handlers/onDocumentPointerMove.ts +89 -0
  36. package/src/global/event-handlers/onDocumentPointerOut.ts +19 -0
  37. package/src/global/event-handlers/onDocumentPointerUp.ts +14 -0
  38. package/src/global/mountGroup.ts +213 -0
  39. package/src/global/mutable-state/groups.ts +104 -0
  40. package/src/global/mutable-state/interactions.ts +49 -0
  41. package/src/global/mutable-state/types.ts +31 -0
  42. package/src/global/styles/convertEmToPixels.ts +6 -0
  43. package/src/global/styles/convertRemToPixels.ts +6 -0
  44. package/src/global/styles/convertVhToPixels.ts +3 -0
  45. package/src/global/styles/convertVwToPixels.ts +3 -0
  46. package/src/global/styles/parseSizeAndUnit.ts +28 -0
  47. package/src/global/styles/sizeStyleToPixels.ts +48 -0
  48. package/src/global/utils/adjustLayoutByDelta.ts +339 -0
  49. package/src/global/utils/adjustLayoutForSeparator.ts +54 -0
  50. package/src/global/utils/calculateDefaultLayout.ts +37 -0
  51. package/src/global/utils/calculateSeparatorAriaValues.ts +67 -0
  52. package/src/global/utils/compareLayoutNumbers.ts +9 -0
  53. package/src/global/utils/completeActivePointerResize.ts +37 -0
  54. package/src/global/utils/doRectsIntersect.ts +5 -0
  55. package/src/global/utils/findClosestHitRegion.ts +43 -0
  56. package/src/global/utils/findClosestRect.ts +36 -0
  57. package/src/global/utils/findMatchingHitRegions.ts +41 -0
  58. package/src/global/utils/findSeparatorGroup.ts +13 -0
  59. package/src/global/utils/formatLayoutNumber.ts +3 -0
  60. package/src/global/utils/getDistanceBetweenPointAndRect.ts +14 -0
  61. package/src/global/utils/getImperativeGroupMethods.ts +68 -0
  62. package/src/global/utils/getImperativePanelMethods.ts +228 -0
  63. package/src/global/utils/isCoarsePointer.ts +8 -0
  64. package/src/global/utils/isViableHitTarget.ts +53 -0
  65. package/src/global/utils/layoutNumbersEqual.ts +5 -0
  66. package/src/global/utils/layoutsEqual.ts +17 -0
  67. package/src/global/utils/notifyPanelOnResize.ts +33 -0
  68. package/src/global/utils/objectsEqual.ts +14 -0
  69. package/src/global/utils/preserveFixedPanelSizes.ts +74 -0
  70. package/src/global/utils/updateActiveHitRegion.ts +132 -0
  71. package/src/global/utils/validateLayoutKeys.ts +19 -0
  72. package/src/global/utils/validatePanelGroupLayout.ts +92 -0
  73. package/src/global/utils/validatePanelSize.ts +41 -0
  74. package/src/hooks/useForceUpdate.ts +8 -0
  75. package/src/hooks/useId.ts +6 -0
  76. package/src/hooks/useIsomorphicLayoutEffect.ts +4 -0
  77. package/src/hooks/useMergedRefs.ts +18 -0
  78. package/src/hooks/useStableArray.ts +15 -0
  79. package/src/hooks/useStableCallback.ts +18 -0
  80. package/src/hooks/useStableObject.ts +15 -0
  81. package/src/index.tsrx +12 -0
  82. package/src/internal.ts +23 -0
  83. package/src/types.ts +18 -0
  84. package/src/utils/EventEmitter.ts +72 -0
  85. package/src/utils/assert.ts +8 -0
  86. package/src/utils/isArrayEqual.ts +12 -0
  87. package/src/utils/isHTMLElement.ts +9 -0
  88. package/src/utils/isShadowRoot.ts +9 -0
  89. package/src/utils/parseNumericStyleValue.ts +15 -0
  90. package/src/vendor/stacking-order.ts +133 -0
  91. package/status.json +11 -0
@@ -0,0 +1,48 @@
1
+ import { convertEmToPixels } from './convertEmToPixels';
2
+ import { convertRemToPixels } from './convertRemToPixels';
3
+ import { convertVhToPixels } from './convertVhToPixels';
4
+ import { convertVwToPixels } from './convertVwToPixels';
5
+ import { parseSizeAndUnit } from './parseSizeAndUnit';
6
+
7
+ export function sizeStyleToPixels({
8
+ groupSize,
9
+ panelElement,
10
+ styleProp,
11
+ }: {
12
+ groupSize: number;
13
+ panelElement: HTMLElement;
14
+ styleProp: number | string;
15
+ }) {
16
+ let pixels: number | undefined = undefined;
17
+
18
+ const [size, unit] = parseSizeAndUnit(styleProp);
19
+
20
+ switch (unit) {
21
+ case '%': {
22
+ pixels = (size / 100) * groupSize;
23
+ break;
24
+ }
25
+ case 'px': {
26
+ pixels = size;
27
+ break;
28
+ }
29
+ case 'rem': {
30
+ pixels = convertRemToPixels(panelElement, size);
31
+ break;
32
+ }
33
+ case 'em': {
34
+ pixels = convertEmToPixels(panelElement, size);
35
+ break;
36
+ }
37
+ case 'vh': {
38
+ pixels = convertVhToPixels(size);
39
+ break;
40
+ }
41
+ case 'vw': {
42
+ pixels = convertVwToPixels(size);
43
+ break;
44
+ }
45
+ }
46
+
47
+ return pixels;
48
+ }
@@ -0,0 +1,339 @@
1
+ import type { Layout } from '../../components/group/types';
2
+ import type { PanelConstraints } from '../../components/panel/types';
3
+ import { assert } from '../../utils/assert';
4
+ import { isArrayEqual } from '../../utils/isArrayEqual';
5
+ import { compareLayoutNumbers } from '../utils/compareLayoutNumbers';
6
+ import { layoutNumbersEqual } from '../utils/layoutNumbersEqual';
7
+ import { validatePanelSize } from '../utils/validatePanelSize';
8
+
9
+ // All units must be in percentages; pixel values should be pre-converted
10
+ export function adjustLayoutByDelta({
11
+ delta,
12
+ initialLayout: initialLayoutProp,
13
+ panelConstraints: panelConstraintsArray,
14
+ pivotIndices,
15
+ prevLayout: prevLayoutProp,
16
+ trigger,
17
+ }: {
18
+ delta: number;
19
+ initialLayout: Layout;
20
+ panelConstraints: PanelConstraints[];
21
+ pivotIndices: number[];
22
+ prevLayout: Layout;
23
+ trigger?: 'imperative-api' | 'keyboard' | 'mouse-or-touch';
24
+ }): Layout {
25
+ if (layoutNumbersEqual(delta, 0)) {
26
+ return initialLayoutProp;
27
+ }
28
+
29
+ const overrideDisabledPanels = trigger === 'imperative-api';
30
+
31
+ const initialLayout = Object.values(initialLayoutProp);
32
+ const prevLayout = Object.values(prevLayoutProp);
33
+ const nextLayout = [...initialLayout];
34
+
35
+ const [firstPivotIndex, secondPivotIndex] = pivotIndices;
36
+ assert(firstPivotIndex != null, 'Invalid first pivot index');
37
+ assert(secondPivotIndex != null, 'Invalid second pivot index');
38
+
39
+ let deltaApplied = 0;
40
+
41
+ // const DEBUG = [];
42
+ // DEBUG.push(`adjustLayoutByDelta()`);
43
+ // DEBUG.push(` initialLayout: ${initialLayout.join(", ")}`);
44
+ // DEBUG.push(` prevLayout: ${prevLayout.join(", ")}`);
45
+ // DEBUG.push(` delta: ${delta}`);
46
+ // DEBUG.push(` pivotIndices: ${pivotIndices.join(", ")}`);
47
+ // DEBUG.push(` trigger: ${trigger}`);
48
+ // DEBUG.push("");
49
+
50
+ // A resizing panel affects the panels before or after it.
51
+ //
52
+ // A negative delta means the panel(s) immediately after the separator should grow/expand by decreasing its offset.
53
+ // Other panels may also need to shrink/contract (and shift) to make room, depending on the min weights.
54
+ //
55
+ // A positive delta means the panel(s) immediately before the separator should "expand".
56
+ // This is accomplished by shrinking/contracting (and shifting) one or more of the panels after the separator.
57
+
58
+ {
59
+ switch (trigger) {
60
+ case 'keyboard': {
61
+ // If this is a resize triggered by a keyboard event, our logic for expanding/collapsing is different.
62
+ // We no longer check the halfway threshold because this may prevent the panel from expanding at all.
63
+ {
64
+ // Check if we should expand a collapsed panel
65
+ const index = delta < 0 ? secondPivotIndex : firstPivotIndex;
66
+ const panelConstraints = panelConstraintsArray[index];
67
+ assert(panelConstraints, `Panel constraints not found for index ${index}`);
68
+
69
+ const { collapsedSize = 0, collapsible, minSize = 0 } = panelConstraints;
70
+
71
+ // DEBUG.push(`edge case check 1: ${index}`);
72
+ // DEBUG.push(` -> collapsible? ${collapsible}`);
73
+ if (collapsible) {
74
+ const prevSize = initialLayout[index];
75
+ assert(prevSize != null, `Previous layout not found for panel index ${index}`);
76
+
77
+ if (layoutNumbersEqual(prevSize, collapsedSize)) {
78
+ const localDelta = minSize - prevSize;
79
+ // DEBUG.push(` -> expand delta: ${localDelta}`);
80
+
81
+ if (compareLayoutNumbers(localDelta, Math.abs(delta)) > 0) {
82
+ delta = delta < 0 ? 0 - localDelta : localDelta;
83
+ // DEBUG.push(` -> delta: ${delta}`);
84
+ }
85
+ }
86
+ }
87
+ }
88
+
89
+ {
90
+ // Check if we should collapse a panel at its minimum size
91
+ const index = delta < 0 ? firstPivotIndex : secondPivotIndex;
92
+ const panelConstraints = panelConstraintsArray[index];
93
+ assert(panelConstraints, `No panel constraints found for index ${index}`);
94
+
95
+ const { collapsedSize = 0, collapsible, minSize = 0 } = panelConstraints;
96
+
97
+ // DEBUG.push(`edge case check 2: ${index}`);
98
+ // DEBUG.push(` -> collapsible? ${collapsible}`);
99
+ if (collapsible) {
100
+ const prevSize = initialLayout[index];
101
+ assert(prevSize != null, `Previous layout not found for panel index ${index}`);
102
+
103
+ if (layoutNumbersEqual(prevSize, minSize)) {
104
+ const localDelta = prevSize - collapsedSize;
105
+ // DEBUG.push(` -> expand delta: ${localDelta}`);
106
+
107
+ if (compareLayoutNumbers(localDelta, Math.abs(delta)) > 0) {
108
+ delta = delta < 0 ? 0 - localDelta : localDelta;
109
+ // DEBUG.push(` -> delta: ${delta}`);
110
+ }
111
+ }
112
+ }
113
+ }
114
+ break;
115
+ }
116
+ default: {
117
+ // If we're starting from a collapsed state, dragging past the halfway point should cause the panel to expand
118
+ // This can happen for positive or negative drags, and panels on either side of the separator can be collapsible
119
+ // The easiest way to support this is to detect this scenario and pre-adjust the delta before applying the rest of the layout algorithm
120
+ // DEBUG.push(`edge case check 3: collapsible panels`);
121
+
122
+ const index = delta < 0 ? secondPivotIndex : firstPivotIndex;
123
+ const panelConstraints = panelConstraintsArray[index];
124
+ assert(panelConstraints, `Panel constraints not found for index ${index}`);
125
+
126
+ const prevSize = initialLayout[index];
127
+
128
+ const { collapsible, collapsedSize, minSize } = panelConstraints;
129
+ if (collapsible && compareLayoutNumbers(prevSize, minSize) < 0) {
130
+ // DEBUG.push(` -> collapsible ${delta < 0 ? "2nd" : "1st"} panel`);
131
+ if (delta > 0) {
132
+ const gapSize = minSize - collapsedSize;
133
+ const halfwayDelta = gapSize / 2;
134
+ // DEBUG.push(` -> halfway delta: ${halfwayDelta}`);
135
+ // DEBUG.push(` collapsed: ${collapsedSize}`);
136
+ // DEBUG.push(` min: ${minSize}`);
137
+
138
+ const nextSize = prevSize + delta;
139
+ if (compareLayoutNumbers(nextSize, minSize) < 0) {
140
+ // DEBUG.push(" -> adjusting delta");
141
+ // DEBUG.push(` from: ${delta}`);
142
+ delta = compareLayoutNumbers(delta, halfwayDelta) <= 0 ? 0 : gapSize;
143
+ // DEBUG.push(` to: ${delta}`);
144
+ }
145
+ } else {
146
+ const gapSize = minSize - collapsedSize;
147
+ const halfwayDelta = 100 - gapSize / 2;
148
+ // DEBUG.push(` -> halfway delta: ${halfwayDelta}`);
149
+ // DEBUG.push(` collapsed: ${100 - collapsedSize}`);
150
+ // DEBUG.push(` min: ${100 - minSize}`);
151
+
152
+ const nextSize = prevSize - delta;
153
+ if (compareLayoutNumbers(nextSize, minSize) < 0) {
154
+ // DEBUG.push(" -> adjusting delta");
155
+ // DEBUG.push(` from: ${delta}`);
156
+ delta = compareLayoutNumbers(100 + delta, halfwayDelta) > 0 ? 0 : -gapSize;
157
+ // DEBUG.push(` to: ${delta}`);
158
+ }
159
+ }
160
+ }
161
+ break;
162
+ }
163
+ }
164
+ // DEBUG.push("");
165
+ }
166
+
167
+ {
168
+ // Pre-calculate max available delta in the opposite direction of our pivot.
169
+ // This will be the maximum amount we're allowed to expand/contract the panels in the primary direction.
170
+ // If this amount is less than the requested delta, adjust the requested delta.
171
+ // If this amount is greater than the requested delta, that's useful information too–
172
+ // as an expanding panel might change from collapsed to min size.
173
+
174
+ const increment = delta < 0 ? 1 : -1;
175
+
176
+ let index = delta < 0 ? secondPivotIndex : firstPivotIndex;
177
+ let maxAvailableDelta = 0;
178
+
179
+ // DEBUG.push("pre calc...");
180
+ while (true) {
181
+ const prevSize = initialLayout[index];
182
+ assert(prevSize != null, `Previous layout not found for panel index ${index}`);
183
+
184
+ const maxSafeSize = validatePanelSize({
185
+ overrideDisabledPanels,
186
+ panelConstraints: panelConstraintsArray[index],
187
+ prevSize,
188
+ size: 100,
189
+ });
190
+ const delta = maxSafeSize - prevSize;
191
+ // DEBUG.push(` ${index}: ${prevSize} -> ${maxSafeSize}`);
192
+
193
+ maxAvailableDelta += delta;
194
+ index += increment;
195
+
196
+ if (index < 0 || index >= panelConstraintsArray.length) {
197
+ break;
198
+ }
199
+ }
200
+
201
+ // DEBUG.push(` -> max available delta: ${maxAvailableDelta}`);
202
+ const minAbsDelta = Math.min(Math.abs(delta), Math.abs(maxAvailableDelta));
203
+ delta = delta < 0 ? 0 - minAbsDelta : minAbsDelta;
204
+ // DEBUG.push(` -> adjusted delta: ${delta}`);
205
+ // DEBUG.push("");
206
+ }
207
+
208
+ {
209
+ // Delta added to a panel needs to be subtracted from other panels (within the constraints that those panels allow).
210
+
211
+ const pivotIndex = delta < 0 ? firstPivotIndex : secondPivotIndex;
212
+ let index = pivotIndex;
213
+ while (index >= 0 && index < panelConstraintsArray.length) {
214
+ const deltaRemaining = Math.abs(delta) - Math.abs(deltaApplied);
215
+
216
+ const prevSize = initialLayout[index];
217
+ assert(prevSize != null, `Previous layout not found for panel index ${index}`);
218
+
219
+ const unsafeSize = prevSize - deltaRemaining;
220
+ const safeSize = validatePanelSize({
221
+ overrideDisabledPanels,
222
+ panelConstraints: panelConstraintsArray[index],
223
+ prevSize,
224
+ size: unsafeSize,
225
+ });
226
+
227
+ if (!layoutNumbersEqual(prevSize, safeSize)) {
228
+ deltaApplied += prevSize - safeSize;
229
+
230
+ nextLayout[index] = safeSize;
231
+
232
+ if (
233
+ deltaApplied.toFixed(3).localeCompare(Math.abs(delta).toFixed(3), undefined, {
234
+ numeric: true,
235
+ }) >= 0
236
+ ) {
237
+ break;
238
+ }
239
+ }
240
+
241
+ if (delta < 0) {
242
+ index--;
243
+ } else {
244
+ index++;
245
+ }
246
+ }
247
+ }
248
+ // DEBUG.push(`after 1: ${nextLayout.join(", ")}`);
249
+ // DEBUG.push(` deltaApplied: ${deltaApplied}`);
250
+ // DEBUG.push("");
251
+
252
+ // If we were unable to resize any of the panels panels, return the previous state.
253
+ // This will essentially bailout and ignore e.g. drags past a panel's boundaries
254
+ if (isArrayEqual(prevLayout, nextLayout)) {
255
+ // DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
256
+ // console.log(DEBUG.join("\n"));
257
+
258
+ return prevLayoutProp;
259
+ }
260
+
261
+ {
262
+ // Now distribute the applied delta to the panels in the other direction
263
+ const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
264
+
265
+ const prevSize = initialLayout[pivotIndex];
266
+ assert(prevSize != null, `Previous layout not found for panel index ${pivotIndex}`);
267
+
268
+ const unsafeSize = prevSize + deltaApplied;
269
+ const safeSize = validatePanelSize({
270
+ overrideDisabledPanels,
271
+ panelConstraints: panelConstraintsArray[pivotIndex],
272
+ prevSize,
273
+ size: unsafeSize,
274
+ });
275
+
276
+ // Adjust the pivot panel before, but only by the amount that surrounding panels were able to shrink/contract.
277
+ nextLayout[pivotIndex] = safeSize;
278
+
279
+ // Edge case where expanding or contracting one panel caused another one to change collapsed state
280
+ if (!layoutNumbersEqual(safeSize, unsafeSize)) {
281
+ let deltaRemaining = unsafeSize - safeSize;
282
+
283
+ const pivotIndex = delta < 0 ? secondPivotIndex : firstPivotIndex;
284
+ let index = pivotIndex;
285
+ while (index >= 0 && index < panelConstraintsArray.length) {
286
+ const prevSize = nextLayout[index];
287
+ assert(prevSize != null, `Previous layout not found for panel index ${index}`);
288
+
289
+ const unsafeSize = prevSize + deltaRemaining;
290
+ const safeSize = validatePanelSize({
291
+ overrideDisabledPanels,
292
+ panelConstraints: panelConstraintsArray[index],
293
+ prevSize,
294
+ size: unsafeSize,
295
+ });
296
+
297
+ if (!layoutNumbersEqual(prevSize, safeSize)) {
298
+ deltaRemaining -= safeSize - prevSize;
299
+
300
+ nextLayout[index] = safeSize;
301
+ }
302
+
303
+ if (layoutNumbersEqual(deltaRemaining, 0)) {
304
+ break;
305
+ }
306
+
307
+ if (delta > 0) {
308
+ index--;
309
+ } else {
310
+ index++;
311
+ }
312
+ }
313
+ }
314
+ }
315
+ // DEBUG.push(`after 2: ${nextLayout.join(", ")}`);
316
+ // DEBUG.push(` deltaApplied: ${deltaApplied}`);
317
+ // DEBUG.push("");
318
+
319
+ const totalSize = Object.values(nextLayout).reduce((total, size) => size + total, 0);
320
+ // DEBUG.push(`total size: ${totalSize}`);
321
+
322
+ // If our new layout doesn't add up to 100%, that means the requested delta can't be applied
323
+ // In that case, fall back to our most recent valid layout
324
+ // Allow for a small rounding difference, else e.g. 3 panel layouts may never be considered valid
325
+ if (!layoutNumbersEqual(totalSize, 100, 0.1)) {
326
+ // DEBUG.push(`bailout to previous layout: ${prevLayout.join(", ")}`);
327
+ // console.log(DEBUG.join("\n"));
328
+
329
+ return prevLayoutProp;
330
+ }
331
+
332
+ const prevLayoutKeys = Object.keys(prevLayoutProp);
333
+
334
+ // console.log(DEBUG.join("\n"));
335
+ return nextLayout.reduce<Layout>((accumulated, current, index) => {
336
+ accumulated[prevLayoutKeys[index]] = current;
337
+ return accumulated;
338
+ }, {});
339
+ }
@@ -0,0 +1,54 @@
1
+ import { assert } from '../../utils/assert';
2
+ import { getMountedGroupState, updateMountedGroup } from '../mutable-state/groups';
3
+ import { adjustLayoutByDelta } from './adjustLayoutByDelta';
4
+ import { findSeparatorGroup } from './findSeparatorGroup';
5
+ import { getImperativeGroupMethods } from './getImperativeGroupMethods';
6
+ import { layoutsEqual } from './layoutsEqual';
7
+ import { validatePanelGroupLayout } from './validatePanelGroupLayout';
8
+
9
+ export function adjustLayoutForSeparator(separatorElement: HTMLElement, delta: number) {
10
+ const group = findSeparatorGroup(separatorElement);
11
+ const groupState = getMountedGroupState(group.id, true);
12
+
13
+ const separator = group.separators.find((current) => current.element === separatorElement);
14
+ assert(separator, 'Matching separator not found');
15
+
16
+ const panels = groupState.separatorToPanels.get(separator);
17
+ assert(panels, 'Matching panels not found');
18
+
19
+ const pivotIndices = panels.map((panel) => group.panels.indexOf(panel));
20
+
21
+ const groupAPI = getImperativeGroupMethods({ groupId: group.id });
22
+ const prevLayout = groupAPI.getLayout();
23
+
24
+ const unsafeLayout = adjustLayoutByDelta({
25
+ delta,
26
+ initialLayout: prevLayout,
27
+ panelConstraints: groupState.derivedPanelConstraints,
28
+ pivotIndices,
29
+ prevLayout,
30
+ trigger: 'keyboard',
31
+ });
32
+ const nextLayout = validatePanelGroupLayout({
33
+ layout: unsafeLayout,
34
+ panelConstraints: groupState.derivedPanelConstraints,
35
+ });
36
+
37
+ if (!layoutsEqual(prevLayout, nextLayout)) {
38
+ updateMountedGroup(
39
+ group,
40
+ {
41
+ defaultLayoutDeferred: groupState.defaultLayoutDeferred,
42
+ derivedPanelConstraints: groupState.derivedPanelConstraints,
43
+ groupSize: groupState.groupSize,
44
+ layout: nextLayout,
45
+ separatorToPanels: groupState.separatorToPanels,
46
+ },
47
+ // Keyboard resizes (arrow keys, Home/End, Enter collapse/expand) originate
48
+ // from a real DOM event on the separator, so they are user interactions
49
+ // just like pointer drags. This function is only reached from
50
+ // onDocumentKeyDown. See #716.
51
+ { isUserInteraction: true },
52
+ );
53
+ }
54
+ }
@@ -0,0 +1,37 @@
1
+ import type { Layout } from '../../components/group/types';
2
+ import type { PanelConstraints } from '../../components/panel/types';
3
+ import { formatLayoutNumber } from './formatLayoutNumber';
4
+
5
+ export function calculateDefaultLayout(derivedPanelConstraints: PanelConstraints[]): Layout {
6
+ let explicitCount = 0;
7
+ let total = 0;
8
+
9
+ const layout: Layout = {};
10
+
11
+ for (const current of derivedPanelConstraints) {
12
+ if (current.defaultSize !== undefined) {
13
+ explicitCount++;
14
+
15
+ const size = formatLayoutNumber(current.defaultSize);
16
+
17
+ total += size;
18
+ layout[current.panelId] = size;
19
+ } else {
20
+ // @ts-expect-error Add panel keys in order to simplify traversal elsewhere; we'll fill them in in the loop below
21
+ layout[current.panelId] = undefined;
22
+ }
23
+ }
24
+
25
+ const remainingPanelCount = derivedPanelConstraints.length - explicitCount;
26
+ if (remainingPanelCount !== 0) {
27
+ const size = formatLayoutNumber((100 - total) / remainingPanelCount);
28
+
29
+ for (const current of derivedPanelConstraints) {
30
+ if (current.defaultSize === undefined) {
31
+ layout[current.panelId] = size;
32
+ }
33
+ }
34
+ }
35
+
36
+ return layout;
37
+ }
@@ -0,0 +1,67 @@
1
+ import type { Layout } from '../../components/group/types';
2
+ import type { PanelConstraints } from '../../components/panel/types';
3
+ import { adjustLayoutByDelta } from './adjustLayoutByDelta';
4
+ import { validatePanelGroupLayout } from './validatePanelGroupLayout';
5
+
6
+ export function calculateSeparatorAriaValues({
7
+ layout,
8
+ panelConstraints,
9
+ panelId,
10
+ panelIndex,
11
+ }: {
12
+ layout: Layout;
13
+ panelConstraints: PanelConstraints[];
14
+ panelId: string;
15
+ panelIndex: number;
16
+ }): {
17
+ valueControls: string | undefined;
18
+ valueMax: number | undefined;
19
+ valueMin: number | undefined;
20
+ valueNow: number | undefined;
21
+ } {
22
+ let valueMax: number | undefined = undefined;
23
+ let valueMin: number | undefined = undefined;
24
+
25
+ const panelSize = layout[panelId];
26
+
27
+ const constraints = panelConstraints.find((current) => current.panelId === panelId);
28
+ if (constraints) {
29
+ const maxSize = constraints.maxSize;
30
+ const minSize = constraints.collapsible ? constraints.collapsedSize : constraints.minSize;
31
+
32
+ const pivotIndices = [panelIndex, panelIndex + 1];
33
+
34
+ const minSizeLayout = validatePanelGroupLayout({
35
+ layout: adjustLayoutByDelta({
36
+ delta: minSize - panelSize,
37
+ initialLayout: layout,
38
+ panelConstraints,
39
+ pivotIndices,
40
+ prevLayout: layout,
41
+ }),
42
+ panelConstraints,
43
+ });
44
+
45
+ valueMin = minSizeLayout[panelId];
46
+
47
+ const maxSizeLayout = validatePanelGroupLayout({
48
+ layout: adjustLayoutByDelta({
49
+ delta: maxSize - panelSize,
50
+ initialLayout: layout,
51
+ panelConstraints,
52
+ pivotIndices,
53
+ prevLayout: layout,
54
+ }),
55
+ panelConstraints,
56
+ });
57
+
58
+ valueMax = maxSizeLayout[panelId];
59
+ }
60
+
61
+ return {
62
+ valueControls: panelId,
63
+ valueMax,
64
+ valueMin,
65
+ valueNow: panelSize,
66
+ };
67
+ }
@@ -0,0 +1,9 @@
1
+ import { layoutNumbersEqual } from './layoutNumbersEqual';
2
+
3
+ export function compareLayoutNumbers(actual: number, expected: number) {
4
+ if (layoutNumbersEqual(actual, expected)) {
5
+ return 0;
6
+ } else {
7
+ return actual > expected ? 1 : -1;
8
+ }
9
+ }
@@ -0,0 +1,37 @@
1
+ import { updateCursorStyle } from '../cursor/updateCursorStyle.ts';
2
+ import { getMountedGroupState, updateMountedGroup } from '../mutable-state/groups.ts';
3
+ import { getInteractionState, updateInteractionState } from '../mutable-state/interactions.ts';
4
+
5
+ export function completeActivePointerResize(document: Document) {
6
+ const interactionState = getInteractionState();
7
+
8
+ let match = false;
9
+
10
+ switch (interactionState.state) {
11
+ case 'active': {
12
+ updateInteractionState({
13
+ cursorFlags: 0,
14
+ state: 'inactive',
15
+ });
16
+
17
+ if (interactionState.hitRegions.length > 0) {
18
+ updateCursorStyle(document);
19
+
20
+ match = true;
21
+
22
+ // Dispatch one more "change" event after the interaction state has been reset.
23
+ // Groups use this as a signal to call onLayoutChanged.
24
+ // This is the canonical user-pointer-up site, so flag the dispatch with
25
+ // isUserInteraction: true. See #716.
26
+ interactionState.hitRegions.forEach((hitRegion) => {
27
+ const groupState = getMountedGroupState(hitRegion.group.id, true);
28
+ updateMountedGroup(hitRegion.group, groupState, {
29
+ isUserInteraction: true,
30
+ });
31
+ });
32
+ }
33
+ }
34
+ }
35
+
36
+ return match;
37
+ }
@@ -0,0 +1,5 @@
1
+ import type { Rect } from '../../types';
2
+
3
+ export function doRectsIntersect(a: Rect, b: Rect): boolean {
4
+ return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
5
+ }
@@ -0,0 +1,43 @@
1
+ import type { Orientation } from '../../components/group/types';
2
+ import type { Point } from '../../types';
3
+ import type { HitRegion } from '../dom/calculateHitRegions';
4
+ import { getDistanceBetweenPointAndRect } from './getDistanceBetweenPointAndRect';
5
+
6
+ export function findClosestHitRegion(
7
+ orientation: Orientation,
8
+ hitRegions: HitRegion[],
9
+ point: Point,
10
+ ) {
11
+ let closestHitRegion: HitRegion | undefined = undefined;
12
+ let minDistance = {
13
+ x: Infinity,
14
+ y: Infinity,
15
+ };
16
+
17
+ for (const hitRegion of hitRegions) {
18
+ const data = getDistanceBetweenPointAndRect(point, hitRegion.rect);
19
+ switch (orientation) {
20
+ case 'horizontal': {
21
+ if (data.x <= minDistance.x) {
22
+ closestHitRegion = hitRegion;
23
+ minDistance = data;
24
+ }
25
+ break;
26
+ }
27
+ case 'vertical': {
28
+ if (data.y <= minDistance.y) {
29
+ closestHitRegion = hitRegion;
30
+ minDistance = data;
31
+ }
32
+ break;
33
+ }
34
+ }
35
+ }
36
+
37
+ return closestHitRegion
38
+ ? {
39
+ distance: minDistance,
40
+ hitRegion: closestHitRegion,
41
+ }
42
+ : undefined;
43
+ }
@@ -0,0 +1,36 @@
1
+ import type { Orientation } from '../../components/group/types';
2
+ import { assert } from '../../utils/assert';
3
+ import { getDistanceBetweenPointAndRect } from './getDistanceBetweenPointAndRect';
4
+
5
+ export function findClosestRect({
6
+ orientation,
7
+ rects,
8
+ targetRect,
9
+ }: {
10
+ orientation: Orientation;
11
+ rects: DOMRectReadOnly[];
12
+ targetRect: DOMRectReadOnly;
13
+ }): DOMRectReadOnly {
14
+ const centerPoint = {
15
+ x: targetRect.x + targetRect.width / 2,
16
+ y: targetRect.y + targetRect.height / 2,
17
+ };
18
+
19
+ let closestRect: DOMRectReadOnly | undefined = undefined;
20
+ let minDistance = Number.MAX_VALUE;
21
+
22
+ for (const rect of rects) {
23
+ const { x, y } = getDistanceBetweenPointAndRect(centerPoint, rect);
24
+
25
+ const distance = orientation === 'horizontal' ? x : y;
26
+
27
+ if (distance < minDistance) {
28
+ minDistance = distance;
29
+ closestRect = rect;
30
+ }
31
+ }
32
+
33
+ assert(closestRect, 'No rect found');
34
+
35
+ return closestRect;
36
+ }