This is an automated email from the ASF dual-hosted git repository.
EnxDev pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/superset.git
The following commit(s) were added to refs/heads/master by this push:
new 1ab6077e060 fix(filters): clamp filter bar row while remeasuring after
items change (#43966)
1ab6077e060 is described below
commit 1ab6077e06066d789480fe9139d0da67a3dd94be
Author: Enzo Martellucci <[email protected]>
AuthorDate: Wed Sep 9 10:52:28 2026 +0200
fix(filters): clamp filter bar row while remeasuring after items change
(#43966)
Co-authored-by: Claude Sonnet 5 <[email protected]>
---
.../DropdownContainer/DropdownContainer.test.tsx | 71 ++++++++++++++++++++++
.../DropdownContainer/DropdownContainer.tsx | 13 +++-
2 files changed, 83 insertions(+), 1 deletion(-)
diff --git
a/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.test.tsx
b/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.test.tsx
index 222a5b72dea..7bde7a1e173 100644
---
a/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.test.tsx
+++
b/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.test.tsx
@@ -16,6 +16,7 @@
* specific language governing permissions and limitations
* under the License.
*/
+import type { CSSProperties } from 'react';
import { screen, render } from '@superset-ui/core/spec';
import { Button, DropdownContainer, Icons } from '..';
@@ -178,3 +179,73 @@ test('component renders and functions without throwing
errors', () => {
// Basic functionality test
expect(screen.getByText('Element 1')).toBeInTheDocument();
});
+
+const ITEM_WIDTH = 100;
+/* Width the flex layout leaves the row once the trigger button is laid out. */
+const ROW_WIDTH = 250;
+/* Item count of the transient row that holds every item while remeasuring. */
+const ALL_ITEMS = 4;
+
+/**
+ * Lays items out at ITEM_WIDTH each. In the steady state the row is reported
at
+ * the flex-bounded ROW_WIDTH, so a three-item row overflows and the trigger
+ * shows. In the all-items frame the row is reported at its own content width
+ * instead, which is the frame Edge can paint before the flex layout bounds the
+ * row and its children spill out. `onRowMeasure` receives the row during that
+ * frame so a test can assert how it is styled.
+ */
+const mockBoundingRects = (onRowMeasure: (row: HTMLElement) => void) => {
+ const getBoundingClientRect: (this: HTMLElement) => DOMRect = function () {
+ let right: number;
+ if (this.dataset.test === 'container') {
+ const allItemsFrame = this.children.length === ALL_ITEMS;
+ right = allItemsFrame ? this.children.length * ITEM_WIDTH : ROW_WIDTH;
+ if (allItemsFrame) {
+ onRowMeasure(this);
+ }
+ } else {
+ const itemNumber = Number(this.textContent?.match(/Element (\d+)/)?.[1]);
+ right = itemNumber ? itemNumber * ITEM_WIDTH : ROW_WIDTH;
+ }
+ return {
+ bottom: 0,
+ height: 0,
+ left: right - ITEM_WIDTH,
+ right,
+ top: 0,
+ width: ITEM_WIDTH,
+ x: right - ITEM_WIDTH,
+ y: 0,
+ toJSON: () => ({}),
+ };
+ };
+ jest
+ .spyOn(HTMLElement.prototype, 'getBoundingClientRect')
+ .mockImplementation(getBoundingClientRect);
+};
+
+/* Grows an overflowing row by one item, which mounts every item for a frame
+ * while the new overflow index is calculated. */
+const remeasureWithExtraItem = (style?: CSSProperties) => {
+ const { rerender } = render(<DropdownContainer items={generateItems(3)} />);
+ rerender(<DropdownContainer items={generateItems(3)} />);
+ expect(screen.getByTestId('dropdown-container-btn')).toBeInTheDocument();
+ rerender(
+ <DropdownContainer items={generateItems(ALL_ITEMS)} style={style} />,
+ );
+};
+
+test('clips the item row while remeasuring, then restores it', () => {
+ const measured: string[] = [];
+ mockBoundingRects(row => {
+ measured.push(row.style.overflow);
+ });
+
+ remeasureWithExtraItem({ overflow: 'visible' });
+
+ /* While the row holds every item its children can spill past its own box, so
+ * clipping wins over the consumer's `overflow: visible` for that frame. Once
+ * the new overflow index is applied the consumer's value comes back. */
+ expect(measured[0]).toBe('hidden');
+ expect(measured.at(-1)).toBe('visible');
+});
diff --git
a/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx
b/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx
index 23ab754d945..e0f9ac876f0 100644
---
a/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx
+++
b/superset-frontend/packages/superset-ui-core/src/components/DropdownContainer/DropdownContainer.tsx
@@ -330,7 +330,18 @@ export const DropdownContainer = forwardRef(
min-width: 0px;
`}
data-test="container"
- style={style}
+ style={
+ recalculating
+ ? {
+ ...style,
+ /* The flex layout already bounds the row's own box, but its
+ * children can spill past that box in the frame Edge paints
+ * before the new overflow index is applied. Clipping keeps
+ * them inside the filter bar. */
+ overflow: 'hidden',
+ }
+ : style
+ }
>
{notOverflowedItems.map(item => item.element)}
</div>