Skip to content

Commit 44d1f63

Browse files
Kenan Yıldızbvaughn
andcommitted
feat(Group): report user interactions via onLayoutChanged meta (#716, #721)
`onLayoutChanged` now receives a second argument: a `LayoutChangedMeta` object whose `isUserInteraction` field is `true` only when the user directly manipulated a separator — releasing a pointer drag (including the missed-pointerup fallback) or pressing a resize key (arrow keys, Home/End, Enter collapse/expand). Both originate from a real DOM event, so the library can attribute them. It is `false` for every other source — initial mount, programmatic `setLayout` and other imperative API calls, constraint recompute, and default-size changes — because the library cannot attribute the caller's intent there. The flag is wrapped in a meta object (rather than a raw boolean) so call sites are self-documenting and further metadata can be added later without another signature change. `LayoutChangedMeta` is exported from the public entry point. `useDefaultLayout` persists every commit regardless of the flag, since it owns its storage; consumers that only want to save on user interaction should branch on `meta.isUserInteraction` in their own callback. Co-authored-by: Brian Vaughn <brian.david.vaughn@gmail.com>
1 parent 335d7f2 commit 44d1f63

10 files changed

Lines changed: 259 additions & 124 deletions

File tree

lib/components/group/Group.test.tsx

Lines changed: 132 additions & 72 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,11 @@ describe("Group", () => {
100100
groupRef: RefObject<GroupImperativeHandle | null>;
101101
panelRef: RefObject<PanelImperativeHandle | null>;
102102
}) => Promise<void>,
103-
expectedLayout: Layout
103+
expectedLayout: Layout,
104+
// `true` when the callback simulates a direct separator manipulation —
105+
// a pointer drag or a keyboard resize (see #716); `false` for imperative
106+
// API / mount which fall under the "library-driven" bucket of the flag.
107+
expectedIsUserInteraction: boolean
104108
) {
105109
setElementBoundsFunction((element) => {
106110
switch (element.id) {
@@ -133,15 +137,20 @@ describe("Group", () => {
133137
);
134138

135139
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
136-
expect(onLayoutChanged).toHaveBeenLastCalledWith({
137-
left: 50,
138-
right: 50
139-
});
140+
expect(onLayoutChanged).toHaveBeenLastCalledWith(
141+
{
142+
left: 50,
143+
right: 50
144+
},
145+
{ isUserInteraction: false }
146+
);
140147

141148
await callback({ container, groupRef, panelRef });
142149

143150
expect(onLayoutChanged).toHaveBeenCalledTimes(2);
144-
expect(onLayoutChanged).toHaveBeenLastCalledWith(expectedLayout);
151+
expect(onLayoutChanged).toHaveBeenLastCalledWith(expectedLayout, {
152+
isUserInteraction: expectedIsUserInteraction
153+
});
145154

146155
rerender(
147156
<Group groupRef={groupRef} onLayoutChanged={onLayoutChanged}>
@@ -150,7 +159,10 @@ describe("Group", () => {
150159
);
151160

152161
expect(onLayoutChanged).toHaveBeenCalledTimes(3);
153-
expect(onLayoutChanged).toHaveBeenLastCalledWith({ right: 100 });
162+
expect(onLayoutChanged).toHaveBeenLastCalledWith(
163+
{ right: 100 },
164+
{ isUserInteraction: false }
165+
);
154166

155167
rerender(
156168
<Group groupRef={groupRef} onLayoutChanged={onLayoutChanged}>
@@ -161,7 +173,9 @@ describe("Group", () => {
161173
);
162174

163175
expect(onLayoutChanged).toHaveBeenCalledTimes(4);
164-
expect(onLayoutChanged).toHaveBeenLastCalledWith(expectedLayout);
176+
expect(onLayoutChanged).toHaveBeenLastCalledWith(expectedLayout, {
177+
isUserInteraction: false
178+
});
165179
}
166180

167181
test("should update when resized via pointer", async () => {
@@ -172,7 +186,8 @@ describe("Group", () => {
172186
{
173187
left: 60,
174188
right: 40
175-
}
189+
},
190+
true
176191
);
177192
});
178193

@@ -185,7 +200,8 @@ describe("Group", () => {
185200
{
186201
left: 55,
187202
right: 45
188-
}
203+
},
204+
true
189205
);
190206
});
191207

@@ -200,7 +216,8 @@ describe("Group", () => {
200216
{
201217
left: 75,
202218
right: 25
203-
}
219+
},
220+
false
204221
);
205222
});
206223

@@ -212,7 +229,8 @@ describe("Group", () => {
212229
{
213230
left: 35,
214231
right: 65
215-
}
232+
},
233+
false
216234
);
217235
});
218236
});
@@ -264,10 +282,13 @@ describe("Group", () => {
264282
);
265283

266284
expect(onLayoutChanged).toBeCalledTimes(1);
267-
expect(onLayoutChanged).toHaveBeenLastCalledWith({
268-
left: 25,
269-
right: 75
270-
});
285+
expect(onLayoutChanged).toHaveBeenLastCalledWith(
286+
{
287+
left: 25,
288+
right: 75
289+
},
290+
{ isUserInteraction: false }
291+
);
271292
expect(groupRef.current?.getLayout()).toEqual({
272293
left: 25,
273294
right: 75
@@ -310,10 +331,13 @@ describe("Group", () => {
310331
);
311332

312333
expect(onLayoutChanged).toBeCalledTimes(1);
313-
expect(onLayoutChanged).toHaveBeenLastCalledWith({
314-
left: 25,
315-
right: 75
316-
});
334+
expect(onLayoutChanged).toHaveBeenLastCalledWith(
335+
{
336+
left: 25,
337+
right: 75
338+
},
339+
{ isUserInteraction: false }
340+
);
317341
expect(groupRef.current?.getLayout()).toEqual({
318342
left: 25,
319343
right: 75
@@ -328,10 +352,13 @@ describe("Group", () => {
328352
});
329353

330354
expect(onLayoutChanged).toBeCalledTimes(2);
331-
expect(onLayoutChanged).toHaveBeenLastCalledWith({
332-
left: 20,
333-
right: 80
334-
});
355+
expect(onLayoutChanged).toHaveBeenLastCalledWith(
356+
{
357+
left: 20,
358+
right: 80
359+
},
360+
{ isUserInteraction: false }
361+
);
335362
expect(groupRef.current?.getLayout()).toEqual({
336363
left: 20,
337364
right: 80
@@ -429,19 +456,25 @@ describe("Group", () => {
429456
);
430457

431458
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
432-
expect(onLayoutChanged).toHaveBeenCalledWith({
433-
left: 60,
434-
right: 40
435-
});
459+
expect(onLayoutChanged).toHaveBeenCalledWith(
460+
{
461+
left: 60,
462+
right: 40
463+
},
464+
{ isUserInteraction: false }
465+
);
436466

437467
// Simulate a drag from the draggable element to the target area
438468
await moveSeparator(10);
439469

440470
expect(onLayoutChanged).toHaveBeenCalledTimes(2);
441-
expect(onLayoutChanged).toHaveBeenCalledWith({
442-
left: 70,
443-
right: 30
444-
});
471+
expect(onLayoutChanged).toHaveBeenCalledWith(
472+
{
473+
left: 70,
474+
right: 30
475+
},
476+
{ isUserInteraction: true }
477+
);
445478
});
446479

447480
test("three panel vertical group", async () => {
@@ -489,21 +522,27 @@ describe("Group", () => {
489522
);
490523

491524
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
492-
expect(onLayoutChanged).toHaveBeenCalledWith({
493-
bottom: 50,
494-
middle: 30,
495-
top: 20
496-
});
525+
expect(onLayoutChanged).toHaveBeenCalledWith(
526+
{
527+
bottom: 50,
528+
middle: 30,
529+
top: 20
530+
},
531+
{ isUserInteraction: false }
532+
);
497533

498534
// Simulate a drag from the draggable element to the target area
499535
await moveSeparator(15, "top-separator");
500536

501537
expect(onLayoutChanged).toHaveBeenCalledTimes(2);
502-
expect(onLayoutChanged).toHaveBeenCalledWith({
503-
bottom: 50,
504-
middle: 20,
505-
top: 30
506-
});
538+
expect(onLayoutChanged).toHaveBeenCalledWith(
539+
{
540+
bottom: 50,
541+
middle: 20,
542+
top: 30
543+
},
544+
{ isUserInteraction: true }
545+
);
507546
});
508547
});
509548

@@ -706,10 +745,13 @@ describe("Group", () => {
706745
});
707746

708747
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
709-
expect(onLayoutChanged).toHaveBeenCalledWith({
710-
a: 50,
711-
b: 50
712-
});
748+
expect(onLayoutChanged).toHaveBeenCalledWith(
749+
{
750+
a: 50,
751+
b: 50
752+
},
753+
{ isUserInteraction: false }
754+
);
713755

714756
rerender(
715757
<Group
@@ -746,10 +788,13 @@ describe("Group", () => {
746788
});
747789

748790
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
749-
expect(onLayoutChanged).toHaveBeenCalledWith({
750-
a: 40,
751-
b: 60
752-
});
791+
expect(onLayoutChanged).toHaveBeenCalledWith(
792+
{
793+
a: 40,
794+
b: 60
795+
},
796+
{ isUserInteraction: false }
797+
);
753798

754799
rerender(
755800
<Group
@@ -787,10 +832,13 @@ describe("Group", () => {
787832
});
788833

789834
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
790-
expect(onLayoutChanged).toHaveBeenCalledWith({
791-
a: 50,
792-
b: 50
793-
});
835+
expect(onLayoutChanged).toHaveBeenCalledWith(
836+
{
837+
a: 50,
838+
b: 50
839+
},
840+
{ isUserInteraction: false }
841+
);
794842

795843
rerender(
796844
<Group
@@ -813,12 +861,15 @@ describe("Group", () => {
813861
});
814862

815863
expect(onLayoutChanged).toHaveBeenCalledTimes(2);
816-
expect(onLayoutChanged).toHaveBeenCalledWith({
817-
a: 25,
818-
b: 25,
819-
c: 25,
820-
d: 25
821-
});
864+
expect(onLayoutChanged).toHaveBeenCalledWith(
865+
{
866+
a: 25,
867+
b: 25,
868+
c: 25,
869+
d: 25
870+
},
871+
{ isUserInteraction: false }
872+
);
822873
});
823874

824875
test("should be called once per layout change", async () => {
@@ -857,10 +908,13 @@ describe("Group", () => {
857908
});
858909

859910
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
860-
expect(onLayoutChanged).toHaveBeenCalledWith({
861-
a: 50,
862-
c: 50
863-
});
911+
expect(onLayoutChanged).toHaveBeenCalledWith(
912+
{
913+
a: 50,
914+
c: 50
915+
},
916+
{ isUserInteraction: false }
917+
);
864918

865919
onLayoutChange.mockReset();
866920
onLayoutChanged.mockReset();
@@ -875,10 +929,13 @@ describe("Group", () => {
875929
});
876930

877931
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
878-
expect(onLayoutChanged).toHaveBeenCalledWith({
879-
a: 75,
880-
c: 25
881-
});
932+
expect(onLayoutChanged).toHaveBeenCalledWith(
933+
{
934+
a: 75,
935+
c: 25
936+
},
937+
{ isUserInteraction: true }
938+
);
882939

883940
onLayoutChange.mockReset();
884941
onLayoutChanged.mockReset();
@@ -919,10 +976,13 @@ describe("Group", () => {
919976
});
920977

921978
expect(onLayoutChanged).toHaveBeenCalledTimes(1);
922-
expect(onLayoutChanged).toHaveBeenCalledWith({
923-
a: 25,
924-
b: 75
925-
});
979+
expect(onLayoutChanged).toHaveBeenCalledWith(
980+
{
981+
a: 25,
982+
b: 75
983+
},
984+
{ isUserInteraction: false }
985+
);
926986

927987
rerender(
928988
<Group

0 commit comments

Comments
 (0)