authorgravatar for julius0216@outlook.comJulius Marminge <julius0216@outlook.com> 2026-09-03 18:55:43-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2026-09-03 18:55:43-07:00
log54aef6fbe16f637092505b30bd25230c4b0744d8
tree1acb60227e5720da691c2e4d32c38925d4214dd2
parent5989de44a24888dab02854477ea1d0f50ac3a4a6
signature Signed by PGP key B5690EEEBB952194

fix(web): restore composer controls as space becomes available (#9539)


2 files changed, 34 insertions(+), 3 deletions(-)

apps/web/src/components/composerFooterLayout.test.ts+27
......@@ -333,6 +333,33 @@ describe("resolveRestingComposerControlsLayout hysteresis", () => {
333333 ).toEqual({ hiddenCount: 0, visible: true });
334334 });
335335
336 it("restores the blocks that fit when the full cluster has no slack", () => {
337 const partlyRestored = resolveRestingComposerControlsLayout({
338 ...base,
339 hostWidth: 357,
340 previous: { hiddenCount: 2, visible: true },
341 });
342 expect(partlyRestored).toEqual({ hiddenCount: 1, visible: true });
343 expect(
344 resolveRestingComposerControlsLayout({ ...base, hostWidth: 357, previous: partlyRestored }),
345 ).toEqual(partlyRestored);
346 expect(
347 resolveRestingComposerControlsLayout({ ...base, hostWidth: 358, previous: partlyRestored }),
348 ).toEqual({ hiddenCount: 0, visible: true });
349 });
350
351 it("requires slack before partially restoring a cluster", () => {
352 // One inline block, the picker, and overflow need 149 + 60 + 24 + 8 = 241px.
353 const previous = { hiddenCount: 2, visible: true };
354 expect(resolveRestingComposerControlsLayout({ ...base, hostWidth: 241, previous })).toEqual(
355 previous,
356 );
357 expect(resolveRestingComposerControlsLayout({ ...base, hostWidth: 242, previous })).toEqual({
358 hiddenCount: 1,
359 visible: true,
360 });
361 });
362
336363 it("still resolves from scratch when there is no previous layout", () => {
337364 expect(resolveRestingComposerControlsLayout({ ...base, hostWidth: 357 })).toEqual({
338365 hiddenCount: 0,
apps/web/src/components/composerFooterLayout.ts+7-3
......@@ -134,9 +134,13 @@ export function resolveRestingComposerControlsLayout(
134134 // composer re-measures on every render, so without that margin a host
135135 // sitting exactly on a threshold flips a block in and out until React
136136 // gives up with "Maximum update depth exceeded".
137 if (previous && hiddenCount < previous.hiddenCount) {
138 if (restingComposerControlsWidth(input, hiddenCount) > hostWidth - RESTING_CONTROLS_SLACK_PX) {
139 hiddenCount = Math.min(previous.hiddenCount, blockWidths.length);
137 if (previous) {
138 const previousHiddenCount = Math.min(previous.hiddenCount, blockWidths.length);
139 while (
140 hiddenCount < previousHiddenCount &&
141 restingComposerControlsWidth(input, hiddenCount) > hostWidth - RESTING_CONTROLS_SLACK_PX
142 ) {
143 hiddenCount += 1;
140144 }
141145 }
142146 const minimumWidth = restingComposerControlsWidth(input, hiddenCount, input.minimumFixedWidth);