You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
This issue (Discover's document flyout): wants a manually-resized flyout to keep the user's pixel width, and let the sibling content absorb the leftover space.
Both are valid, and they don't actually conflict — they're different consumer contracts. typeof size === 'number' is the discriminator that satisfies both:
Filing separately because the root cause and the fix are specific and testable. Happy to fold this into #9683 if the team prefers a single thread.
Describe the bug
A resizable flyout given a numericsize does not keep the width the user dragged it to. When the container width changes, the flyout is rescaled to preserve its percentage of the container.
For a type="push" flyout this is especially visible, because push mode sets padding-inline-end on the container from a ResizeObserver measurement. So a single container resize moves both panes: the flyout rescales, and the content beside it reflows to match — a frame or two later.
In Kibana this is Discover's document details flyout. Kibana scopes all flyouts to the app workspace container (#app-main-scroll), so a window resize, a sidebar resize, or opening the AI Assistant all change referenceWidth and therefore rescale the flyout. The data grid (histogram + virtualized table) re-lays-out alongside it.
There's a secondary effect: callOnResize is left true after a drag ends, so these non-user rescales also fire onResize. Consumers that persist the callback value (Discover writes it to localStorage) have the user's stored width permanently overwritten by a window resize.
Root cause
packages/eui/src/components/flyout/use_flyout_resizable.ts — the constraint-change branch multiplies the current pixel width by the reference-width ratio:
So an m flyout that stays at 50% through a container resize is behaving as designed, and the existing code comment says so explicitly ("preserves the flyout's percentage position in both directions"). Removing the scale factor unconditionally would be a silent semantic change for every resizable named-size flyout.
A numeric size, by contrast, is a pixel contract: the consumer measured, persisted, and re-supplied a pixel value. Scaling it discards exactly the information the consumer is trying to preserve.
Proposed fix
In the constraint-change branch, branch on the size type — clamp for numeric, keep scaling for named:
}else{constprevRefWidth=prevReferenceWidthRef.current??_referenceWidth;prevReferenceWidthRef.current=_referenceWidth;setCallOnResize(false);// container resize is not a user resizesetFlyoutWidth((currentWidth)=>{if(!currentWidth||_referenceWidth<=0)returncurrentWidth;// Numeric `size` is a pixel contract — re-clamp, don't rescale.if(typeof_size==='number')returngetFlyoutMinMaxWidth(currentWidth);if(prevRefWidth>0){returngetFlyoutMinMaxWidth(currentWidth*(_referenceWidth/prevRefWidth));}returncurrentWidth;});}
Two notes:
The % round-trip is lossless on this path.referenceWidth comes from useResizeObserver(container, 'width'), which reports borderBoxSize.inlineSize, and the back-conversion uses containerRect.width from getBoundingClientRect() — both border-box, so flyoutWidth / referenceWidth * 100 resolves back to the same pixels. Worth confirming: the container.clientWidth fallback in flyout.component.tsx (used when the observer hasn't reported yet) is content-box, so it's off by the container's padding on the first frame — including the push padding this component itself applies.
setCallOnResize(false) on this path stops container resizes from firing onResize and corrupting consumer-persisted widths.
An explicit resizeMode: 'pixel' | 'percent' prop would be the more discoverable API if the team would rather not infer intent from the size type. The inferred version needs no consumer changes.
Existing tests
use_flyout_resizable.test.ts has no coverage of scaling on referenceWidth change — every current test uses a static referenceWidth and asserts clamping or percentage output. So this change shouldn't break existing tests, and it's worth adding cases for both branches (numeric size holds pixels; named size holds percentage).
Minimum reproduction
I wasn't able to isolate this in a sandbox, but it reproduces in EUI alone without Kibana:
Render a resizable EuiFlyout with type="push", a numericsize (e.g. 544), and a container element narrower than the viewport.
Drag the resize handle to a chosen width and note the pixel value.
Change the container's width (resize the window, or toggle a sidebar beside it).
The flyout's pixel width changes proportionally instead of staying put, the pushed content reflows with it, and onResize fires with the scaled value.
Expected: the flyout stays at the dragged pixel width (re-clamped if it no longer fits), only the pushed content resizes, and onResize does not fire.
Impact and severity
Both panes of a push layout move on every window/sidebar resize, where only one should. In Discover the neighbouring pane is an expensive data grid, so the reflow is very visible.
The user's persisted width is silently destroyed — this one is permanent, not transient.
Shared surface, not one app: in Kibana resizable is plumbed through the core overlay service (src/core/packages/overlays/browser-internal/src/flyout/flyout_service.tsx) and used directly by Discover's doc viewer, metrics_insights_flyout, and agent_builder's canvas_flyout.
No good consumer-side workaround. Forcing the hook's reset branch (by making size change) runs after the scale has already applied, producing two reflows instead of one.
Relationship to #9683 (please read first)
This issue and #9683 fire on the same event — the flyout container's width changing — and request opposite outcomes:
s/msize.Both are valid, and they don't actually conflict — they're different consumer contracts.
typeof size === 'number'is the discriminator that satisfies both:sizeprop's'/'m'/'l'/'fill'544)Filing separately because the root cause and the fix are specific and testable. Happy to fold this into #9683 if the team prefers a single thread.
Describe the bug
A resizable flyout given a numeric
sizedoes not keep the width the user dragged it to. When the container width changes, the flyout is rescaled to preserve its percentage of the container.For a
type="push"flyout this is especially visible, because push mode setspadding-inline-endon the container from aResizeObservermeasurement. So a single container resize moves both panes: the flyout rescales, and the content beside it reflows to match — a frame or two later.In Kibana this is Discover's document details flyout. Kibana scopes all flyouts to the app workspace container (
#app-main-scroll), so a window resize, a sidebar resize, or opening the AI Assistant all changereferenceWidthand therefore rescale the flyout. The data grid (histogram + virtualized table) re-lays-out alongside it.There's a secondary effect:
callOnResizeis lefttrueafter a drag ends, so these non-user rescales also fireonResize. Consumers that persist the callback value (Discover writes it tolocalStorage) have the user's stored width permanently overwritten by a window resize.Root cause
packages/eui/src/components/flyout/use_flyout_resizable.ts— the constraint-change branch multiplies the current pixel width by the reference-width ratio:The hook then emits the width as a percentage:
and
flyout.component.tsxconverts it back withcontainerRect.width * (pct / 100).Why the fix must be conditional
Scaling is correct for named sizes — EUI defines them as percentages in
packages/eui/src/components/flyout/flyout.styles.ts:So an
mflyout that stays at 50% through a container resize is behaving as designed, and the existing code comment says so explicitly ("preserves the flyout's percentage position in both directions"). Removing the scale factor unconditionally would be a silent semantic change for every resizable named-size flyout.A numeric
size, by contrast, is a pixel contract: the consumer measured, persisted, and re-supplied a pixel value. Scaling it discards exactly the information the consumer is trying to preserve.Proposed fix
In the constraint-change branch, branch on the
sizetype — clamp for numeric, keep scaling for named:Two notes:
%round-trip is lossless on this path.referenceWidthcomes fromuseResizeObserver(container, 'width'), which reportsborderBoxSize.inlineSize, and the back-conversion usescontainerRect.widthfromgetBoundingClientRect()— both border-box, soflyoutWidth / referenceWidth * 100resolves back to the same pixels. Worth confirming: thecontainer.clientWidthfallback inflyout.component.tsx(used when the observer hasn't reported yet) is content-box, so it's off by the container's padding on the first frame — including the push padding this component itself applies.setCallOnResize(false)on this path stops container resizes from firingonResizeand corrupting consumer-persisted widths.An explicit
resizeMode: 'pixel' | 'percent'prop would be the more discoverable API if the team would rather not infer intent from thesizetype. The inferred version needs no consumer changes.Existing tests
use_flyout_resizable.test.tshas no coverage of scaling onreferenceWidthchange — every current test uses a staticreferenceWidthand asserts clamping or percentage output. So this change shouldn't break existing tests, and it's worth adding cases for both branches (numeric size holds pixels; named size holds percentage).Minimum reproduction
I wasn't able to isolate this in a sandbox, but it reproduces in EUI alone without Kibana:
EuiFlyoutwithtype="push", a numericsize(e.g.544), and acontainerelement narrower than the viewport.onResizefires with the scaled value.Expected: the flyout stays at the dragged pixel width (re-clamped if it no longer fits), only the pushed content resizes, and
onResizedoes not fire.Impact and severity
resizableis plumbed through the core overlay service (src/core/packages/overlays/browser-internal/src/flyout/flyout_service.tsx) and used directly by Discover's doc viewer,metrics_insights_flyout, and agent_builder'scanvas_flyout.No good consumer-side workaround. Forcing the hook's reset branch (by making
sizechange) runs after the scale has already applied, producing two reflows instead of one.Environment and versions
containerprop for app-level or global flyouts + fixes for resizable flyouts #9377)mainKibana-side tracking issue (the two consumer bugs that do not need this fix): elastic/kibana#287943