Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 61 additions & 1 deletion packages/core/src/components/slider/handle.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ export interface InternalHandleProps extends HandleProps {
tickSize: number;
tickSizeRatio: number;
vertical: boolean;
ensureParentOverflowVisible?: boolean;
}

export interface HandleState {
Expand All @@ -53,9 +54,21 @@ export class Handle extends AbstractPureComponent<InternalHandleProps, HandleSta
};

private handleElement: HTMLElement | null = null;
private sliderElement: HTMLElement | null = null;
private modifiedElements: Array<{ element: HTMLElement; originalOverflow: string }> = [];

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() {
Expand Down Expand Up @@ -91,8 +104,19 @@ export class Handle extends AbstractPureComponent<InternalHandleProps, HandleSta
);
}

public componentDidUpdate(prevProps: InternalHandleProps) {
if (this.props.ensureParentOverflowVisible && !prevProps.ensureParentOverflowVisible) {
this.setParentOverflow();
} else if (!this.props.ensureParentOverflowVisible && prevProps.ensureParentOverflowVisible) {
this.restoreParentOverflow();
}
}

public componentWillUnmount() {
this.removeDocumentEventListeners();
if (this.props.ensureParentOverflowVisible) {
this.restoreParentOverflow();
}
}

/** Convert client pixel to value between min and max. */
Expand Down Expand Up @@ -267,4 +291,40 @@ export class Handle extends AbstractPureComponent<InternalHandleProps, HandleSta
document.removeEventListener("touchend", this.endHandleTouchMovement);
document.removeEventListener("touchcancel", this.endHandleTouchMovement);
}

private setParentOverflow = () => {
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 = [];
};
}
11 changes: 10 additions & 1 deletion packages/core/src/components/slider/multiSlider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -320,7 +328,7 @@ export class MultiSlider extends AbstractPureComponent<MultiSliderProps, SliderS
}

private renderHandles() {
const { disabled, max, min, stepSize, vertical } = this.props;
const { disabled, max, min, stepSize, vertical, ensureParentOverflowVisible } = this.props;
const handleProps = getSortedInteractiveHandleProps(this.props);

if (handleProps.length === 0) {
Expand Down Expand Up @@ -350,6 +358,7 @@ export class MultiSlider extends AbstractPureComponent<MultiSliderProps, SliderS
tickSizeRatio={this.state.tickSizeRatio}
value={value}
vertical={vertical!}
ensureParentOverflowVisible={ensureParentOverflowVisible}
/>
));
}
Expand Down
59 changes: 58 additions & 1 deletion packages/docs-app/src/examples/core-examples/sliderExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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<ExampleProps> = props => {
Expand Down Expand Up @@ -73,6 +73,63 @@ export const SliderExample: React.FC<ExampleProps> = props => {
vertical={vertical}
/>
</Card>

{/* TODO: Remove this test section before merging PR - temporary demonstration of ensureParentOverflowVisible */}
<div style={{ marginTop: "40px" }}>
<H3 style={{ marginBottom: "10px" }}>Test: Slider in overflow container</H3>
<p style={{ color: "#666", fontSize: "14px", marginBottom: "20px" }}>
Red box has overflow:hidden. Toggle "Ensure parent overflow visible" to test if
labels escape.
</p>

<div
style={{
border: "2px solid red",
height: "100px",
marginBottom: "20px",
overflow: "hidden",
padding: "40px 20px",
}}
>
<p style={{ fontSize: "12px", marginBottom: "10px" }}>
Without ensureParentOverflowVisible (label will clip):
</p>
<Slider
handleHtmlProps={{ "aria-label": "overflow test without fix" }}
labelStepSize={2500}
max={10000}
min={0}
onChange={setValue1}
stepSize={100}
value={value1}
vertical={vertical}
/>
</div>

<div
style={{
border: "2px solid green",
height: "100px",
overflow: "hidden",
padding: "40px 20px",
}}
>
<p style={{ fontSize: "12px", marginBottom: "10px" }}>
With ensureParentOverflowVisible (label escapes):
</p>
<Slider
handleHtmlProps={{ "aria-label": "overflow test with fix" }}
labelStepSize={2500}
max={10000}
min={0}
onChange={setValue1}
stepSize={100}
value={value1}
vertical={vertical}
ensureParentOverflowVisible={true}
/>
</div>
</div>
</Example>
);
};
Expand Down