@window-splitter/react 1.1.3 → 1.1.5--canary.f61d2d9.0

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.
@@ -406,3 +406,121 @@ describe("imperative panel API", async () => {
406
406
  expect(leftHandle.current.isExpanded()).toBe(true);
407
407
  });
408
408
  });
409
+
410
+ describe("direct-DOM animation path", () => {
411
+ test("grid template updates on the DOM during collapse animation", async () => {
412
+ const handle = { current: null } as unknown as {
413
+ current: PanelGroupHandle;
414
+ };
415
+ const panelHandle = { current: null } as unknown as {
416
+ current: PanelHandle;
417
+ };
418
+
419
+ render(
420
+ <div style={{ width: 500 }}>
421
+ <PanelGroup
422
+ handle={handle}
423
+ orientation="horizontal"
424
+ style={{ height: 200 }}
425
+ >
426
+ <Panel id="panel1">1</Panel>
427
+ <PanelResizer id="resizer1" size="10px" />
428
+ <Panel
429
+ id="panel2"
430
+ handle={panelHandle}
431
+ min="100px"
432
+ collapsible
433
+ collapsedSize="60px"
434
+ defaultCollapsed
435
+ collapseAnimation={{ easing: "linear", duration: 300 }}
436
+ >
437
+ 2
438
+ </Panel>
439
+ </PanelGroup>
440
+ </div>
441
+ );
442
+
443
+ await waitForMeasurement(handle.current);
444
+ const templateBefore = handle.current.getTemplate();
445
+ expect(templateBefore).toContain("60px");
446
+
447
+ panelHandle.current.expand();
448
+
449
+ // Wait for animation to be in progress
450
+ await new Promise((resolve) => setTimeout(resolve, 100));
451
+ expect(handle.current.getState()).toBe("dragging");
452
+
453
+ const groupEl = document.querySelector(
454
+ "[data-group-id]"
455
+ ) as HTMLElement;
456
+ expect(groupEl).toBeTruthy();
457
+ const domTemplateDuringAnimation = groupEl.style.gridTemplateColumns;
458
+ expect(domTemplateDuringAnimation).toBeTruthy();
459
+ expect(domTemplateDuringAnimation).not.toContain("60px");
460
+
461
+ await new Promise((resolve) => setTimeout(resolve, 500));
462
+ expect(handle.current.getState()).toBe("idle");
463
+
464
+ // After animation ends, onUpdate fires once and React re-renders with the
465
+ // committed template. The DOM should no longer contain the collapsed size.
466
+ const finalDomTemplate = groupEl.style.gridTemplateColumns;
467
+ expect(finalDomTemplate).not.toContain("60px");
468
+ });
469
+
470
+ test("panel children do not re-render during animation", async () => {
471
+ const handle = { current: null } as unknown as {
472
+ current: PanelGroupHandle;
473
+ };
474
+ const panelHandle = { current: null } as unknown as {
475
+ current: PanelHandle;
476
+ };
477
+
478
+ let renderCount = 0;
479
+ const RenderCounter = () => {
480
+ renderCount++;
481
+ return <div data-testid="counter">renders: {renderCount}</div>;
482
+ };
483
+
484
+ render(
485
+ <div style={{ width: 500 }}>
486
+ <PanelGroup
487
+ handle={handle}
488
+ orientation="horizontal"
489
+ style={{ height: 200 }}
490
+ >
491
+ <Panel id="panel1">
492
+ <RenderCounter />
493
+ </Panel>
494
+ <PanelResizer id="resizer1" size="10px" />
495
+ <Panel
496
+ id="panel2"
497
+ handle={panelHandle}
498
+ min="100px"
499
+ collapsible
500
+ collapsedSize="60px"
501
+ defaultCollapsed
502
+ collapseAnimation={{ easing: "linear", duration: 300 }}
503
+ >
504
+ 2
505
+ </Panel>
506
+ </PanelGroup>
507
+ </div>
508
+ );
509
+
510
+ await waitForMeasurement(handle.current);
511
+ const rendersBeforeAnimation = renderCount;
512
+
513
+ panelHandle.current.expand();
514
+
515
+ await new Promise((resolve) => setTimeout(resolve, 100));
516
+ expect(handle.current.getState()).toBe("dragging");
517
+
518
+ const rendersDuringAnimation = renderCount;
519
+ expect(rendersDuringAnimation).toBe(rendersBeforeAnimation);
520
+
521
+ await new Promise((resolve) => setTimeout(resolve, 500));
522
+ expect(handle.current.getState()).toBe("idle");
523
+
524
+ expect(renderCount).toBe(rendersBeforeAnimation);
525
+ });
526
+ });
@@ -75,6 +75,9 @@ const GroupMachineStateContextRef = createContext<
75
75
  const GroupMachineActor = createContext<(e: GroupMachineEvent) => void>(
76
76
  () => {}
77
77
  );
78
+ const GroupMachineAnimationDomRef = createContext<
79
+ React.MutableRefObject<HTMLElement | null>
80
+ >({ current: null });
78
81
  const GroupMachine = {
79
82
  useSelector<R>(
80
83
  selector: (data: { context: GroupMachineContextValue }) => R
@@ -107,12 +110,17 @@ const GroupMachine = {
107
110
  input: GroupMachineInput;
108
111
  children: React.ReactNode;
109
112
  }) => {
113
+ const animationDomRef = useRef<HTMLElement | null>(null);
110
114
  const [initialValue, send, state] = useMemo(
111
115
  () =>
112
- groupMachine(input, (value) => {
113
- currentContextRef.current = value;
114
- setCurrentValue({ ...value });
115
- }),
116
+ groupMachine(
117
+ input,
118
+ (value) => {
119
+ currentContextRef.current = value;
120
+ setCurrentValue({ ...value });
121
+ },
122
+ () => animationDomRef.current
123
+ ),
116
124
  // We only want this to run once, we dont care about changes to the input
117
125
  // eslint-disable-next-line react-hooks/exhaustive-deps
118
126
  []
@@ -136,7 +144,9 @@ const GroupMachine = {
136
144
  <GroupMachineStateContextRef.Provider value={currentContextRef}>
137
145
  <GroupMachineContext.Provider value={currentValue}>
138
146
  <GroupMachineActor.Provider value={send}>
139
- {children}
147
+ <GroupMachineAnimationDomRef.Provider value={animationDomRef}>
148
+ {children}
149
+ </GroupMachineAnimationDomRef.Provider>
140
150
  </GroupMachineActor.Provider>
141
151
  </GroupMachineContext.Provider>
142
152
  </GroupMachineStateContextRef.Provider>
@@ -414,6 +424,7 @@ const PanelGroupImplementation = React.forwardRef<
414
424
  ) {
415
425
  const { send } = GroupMachine.useActorRef();
416
426
  const machineRef = GroupMachine.useContextRef();
427
+ const animationDomRef = useContext(GroupMachineAnimationDomRef);
417
428
  const innerRef = React.useRef<HTMLDivElement>(null);
418
429
  const ref = mergeRefs(outerRef, innerRef);
419
430
  const orientation = GroupMachine.useSelector(
@@ -429,6 +440,16 @@ const PanelGroupImplementation = React.forwardRef<
429
440
  send({ type: "setOrientation", orientation: orientationProp });
430
441
  }
431
442
 
443
+ // Register the group's DOM element so the machine can write
444
+ // grid-template directly during collapse/expand animations,
445
+ // bypassing React re-renders for each animation frame.
446
+ useLayoutEffect(() => {
447
+ animationDomRef.current = innerRef.current;
448
+ return () => {
449
+ animationDomRef.current = null;
450
+ };
451
+ }, [animationDomRef]);
452
+
432
453
  // Track the size of the group
433
454
  useLayoutEffect(() => {
434
455
  const { current: el } = innerRef;
@@ -1,18 +0,0 @@
1
-
2
- 
3
- > @window-splitter/react@1.1.0 lint /Users/alisowski/Documents/react-window-splitter/packages/react
4
- > eslint .
5
-
6
- =============
7
-
8
- WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.
9
-
10
- You may find that it works just fine, or you may not.
11
-
12
- SUPPORTED TYPESCRIPT VERSIONS: >=4.7.4 <5.6.0
13
-
14
- YOUR TYPESCRIPT VERSION: 5.8.3
15
-
16
- Please only submit bug reports when using the officially supported version.
17
-
18
- =============
package/LICENSE DELETED
@@ -1,7 +0,0 @@
1
- Copyright 2025 Andrew Lisowski
2
-
3
- Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
-
5
- The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
-
7
- THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.