diff --git a/packages/core/src/components/slider/handle.tsx b/packages/core/src/components/slider/handle.tsx index 84ecf54c06..ae5ea4b2db 100644 --- a/packages/core/src/components/slider/handle.tsx +++ b/packages/core/src/components/slider/handle.tsx @@ -34,6 +34,7 @@ export interface InternalHandleProps extends HandleProps { tickSize: number; tickSizeRatio: number; vertical: boolean; + ensureParentOverflowVisible?: boolean; } export interface HandleState { @@ -53,9 +54,21 @@ export class Handle extends AbstractPureComponent = []; private refHandlers = { - handle: (el: HTMLSpanElement) => (this.handleElement = el), + handle: (el: HTMLSpanElement) => { + this.handleElement = el; + // Find the slider parent when handle mounts + if (el && !this.sliderElement) { + this.sliderElement = el.closest(`.${Classes.SLIDER}`) as HTMLElement; + if (this.sliderElement && this.props.ensureParentOverflowVisible) { + // Use setTimeout to ensure DOM is fully ready + setTimeout(() => this.setParentOverflow(), 0); + } + } + }, }; public componentDidMount() { @@ -91,8 +104,19 @@ export class Handle extends AbstractPureComponent { + if (!this.sliderElement) { + return; + } + + // Walk up the DOM tree and find all ancestors with overflow hidden/auto/scroll + let element: HTMLElement | null = this.sliderElement.parentElement; + while (element && element !== document.body) { + const computedStyle = window.getComputedStyle(element); + const currentOverflow = computedStyle.overflow; + + // Check if this element clips overflow + if (currentOverflow === "hidden" || currentOverflow === "auto" || currentOverflow === "scroll") { + // Store original value + const originalOverflow = element.style.overflow || currentOverflow; + this.modifiedElements.push({ element, originalOverflow }); + // Set to visible + element.style.overflow = "visible"; + } + + element = element.parentElement; + } + }; + + private restoreParentOverflow = () => { + // Restore all modified elements + for (const { element, originalOverflow } of this.modifiedElements) { + if (originalOverflow === "hidden" || originalOverflow === "auto" || originalOverflow === "scroll") { + element.style.overflow = originalOverflow; + } else { + element.style.removeProperty("overflow"); + } + } + this.modifiedElements = []; + }; } diff --git a/packages/core/src/components/slider/multiSlider.tsx b/packages/core/src/components/slider/multiSlider.tsx index dcde5f28ca..5bc0b7adf2 100644 --- a/packages/core/src/components/slider/multiSlider.tsx +++ b/packages/core/src/components/slider/multiSlider.tsx @@ -119,6 +119,14 @@ export interface SliderBaseProps extends Props, IntentProps { * @default false */ vertical?: boolean; + + /** + * Whether to programmatically set the slider's parent container to overflow: visible + * to prevent label clipping. This modifies the parent element's style directly. + * + * @default false + */ + ensureParentOverflowVisible?: boolean; } export interface MultiSliderProps extends SliderBaseProps { @@ -320,7 +328,7 @@ export class MultiSlider extends AbstractPureComponent )); } diff --git a/packages/docs-app/src/examples/core-examples/sliderExample.tsx b/packages/docs-app/src/examples/core-examples/sliderExample.tsx index 5e064a3cdd..76d0d19fe1 100644 --- a/packages/docs-app/src/examples/core-examples/sliderExample.tsx +++ b/packages/docs-app/src/examples/core-examples/sliderExample.tsx @@ -16,7 +16,7 @@ import { useState } from "react"; -import { Card, H5, Slider, Switch } from "@blueprintjs/core"; +import { Card, H3, H5, Slider, Switch } from "@blueprintjs/core"; import { Example, type ExampleProps, handleBooleanChange } from "@blueprintjs/docs-theme"; export const SliderExample: React.FC = props => { @@ -73,6 +73,63 @@ export const SliderExample: React.FC = props => { vertical={vertical} /> + + {/* TODO: Remove this test section before merging PR - temporary demonstration of ensureParentOverflowVisible */} +
+

Test: Slider in overflow container

+

+ Red box has overflow:hidden. Toggle "Ensure parent overflow visible" to test if + labels escape. +

+ +
+

+ Without ensureParentOverflowVisible (label will clip): +

+ +
+ +
+

+ With ensureParentOverflowVisible (label escapes): +

+ +
+
); };