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
2 changes: 2 additions & 0 deletions openspec/changes/fix-combobox-flex-collapse/.openspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-05-13
40 changes: 40 additions & 0 deletions openspec/changes/fix-combobox-flex-collapse/proposal.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
## Why

Combobox shrinks to 1px when placed in a flex container with `align-items: center` (row). The widget becomes unusable — invisible to the user — without any CSS override from the app developer.

## What changes

`packages/pluggableWidgets/combobox-web/src/ui/Combobox.scss` — `.widget-combobox` rule (line 21).

Replace `min-width: min-content` with `min-width: 15ch`.

`min-content` was the initial fix attempt but does not work — it asks children for their minimum width, and children suppress their own minimums (see Root Cause). An explicit `ch`-based value breaks the dependency on children entirely.

## Root cause

The actual DOM chain in the bug scenario:

```
.row-center (user's CSS: display:flex, flex-flow:row, align-items:center)
└─ .form-group.no-columns (Mendix-injected: flex-direction:column, no explicit width)
└─ .widget-combobox (flex-grow:1 — grows height in column container, not width)
└─ .widget-combobox-input-container (flex-grow:1, no min-width)
└─ .widget-combobox-selected-items (min-width:0 — intentional for tag wrapping)
└─ .widget-combobox-input (max-width:0 unfocused, width:1px multiselect inactive)
```

The user created `.row-center`. Mendix injected `.form-group.no-columns` as an intermediate wrapper. That wrapper has no explicit width and becomes a column-direction flex container. Inside it, `flex-grow:1` on `.widget-combobox` distributes height, not width. The widget's width is sized by content, and `min-width: min-content` resolves to ~0 because `.widget-combobox-selected-items` has `min-width: 0` (needed for multiselect tag wrapping).

`min-width: 15ch` is an explicit value — it does not ask children, so the children's suppressed minimums are irrelevant.

## Why `ch` not `px`

`ch` scales with font-size and font-family, respecting user accessibility settings and Atlas theme font changes. "15 characters wide" is a semantically honest statement about a text input widget. Override via normal CSS specificity (`.widget-combobox { min-width: 0 }`).

## Impact

Must not break:

- Single-select combobox layout in all container types
- Multiselect combobox layout (active and inactive states)
- Atlas UI demo site rendering (already unaffected per ticket)
29 changes: 29 additions & 0 deletions openspec/changes/fix-combobox-flex-collapse/tests.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
## Tests

- [ ] **Combobox root element has min-width: 15ch in stylesheet**
- **Type:** unit
- **Given:** Combobox.scss source
- **When:** `.widget-combobox` rule is inspected
- **Then:** `min-width` is `15ch`
- **Status:** needs update — `src/__tests__/ComboboxStyles.spec.ts` currently asserts `min-content`

- [ ] **Single-select combobox renders with visible width in flex container with align-items center**
- **Type:** e2e
- **Given:** Single-select Combobox on page `/p/combobox-flex-layout` (`.mx-name-comboBoxFlexSingle`)
- **When:** Page loads with no interaction
- **Then:** Combobox bounding rect width is greater than 10px
- **Status:** pending — needs test page in Studio Pro

- [ ] **Multiselect combobox renders with visible width in flex container with align-items center**
- **Type:** e2e
- **Given:** Multiselect Combobox on page `/p/combobox-flex-layout` (`.mx-name-comboBoxFlexMulti`)
- **When:** Page loads with no interaction
- **Then:** Combobox bounding rect width is greater than 10px
- **Status:** pending — needs test page in Studio Pro

- [x] **Multiselect inactive input still collapses to 1px (intentional behavior preserved)**
- **Type:** unit
- **Given:** Combobox.scss source
- **When:** multiselect inactive rule inspected
- **Then:** `.widget-combobox-input` has `width: 1px`
- **Status:** done — `src/__tests__/ComboboxStyles.spec.ts`
22 changes: 22 additions & 0 deletions packages/pluggableWidgets/combobox-web/e2e/Combobox.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,28 @@ test.describe("combobox-web", () => {
});
});

// WC-3409: flex align-items center collapse fix
test.describe("flex container layout (WC-3409)", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/p/combobox-flex-layout");
await waitForMendixApp(page);
});

test("single-select combobox has visible width in flex align-items:center container", async ({ page }) => {
const comboBox = page.locator(".mx-name-comboBoxFlexSingle");
await expect(comboBox).toBeVisible({ timeout: 10000 });
const box = await comboBox.boundingBox();
expect(box.width).toBeGreaterThan(10);
});

test("multiselect combobox has visible width in flex align-items:center container", async ({ page }) => {
const comboBox = page.locator(".mx-name-comboBoxFlexMulti");
await expect(comboBox).toBeVisible({ timeout: 10000 });
const box = await comboBox.boundingBox();
expect(box.width).toBeGreaterThan(10);
});
});

function getOptions(combobox) {
return combobox.locator(`[role=listbox] [role=option]`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { readFileSync } from "fs";
import { join } from "path";

const scss = readFileSync(join(__dirname, "../ui/Combobox.scss"), "utf-8");

describe("Combobox SCSS — flex collapse fix (WC-3409)", () => {
test(".widget-combobox has min-width: 15ch", () => {
// explicit ch value prevents collapse when Mendix injects .form-group.no-columns
// (column flex container with no width), causing min-content to resolve to ~0
expect(scss).toMatch(/\.widget-combobox\s*\{[^}]*min-width:\s*15ch/s);
});

test("inactive multiselect input still has width: 1px (cursor-hiding preserved)", () => {
expect(scss).toMatch(
/widget-combobox-multiselect[\s\S]*?not\(\.widget-combobox-input-container-active\)[\s\S]*?widget-combobox-input[\s\S]*?width:\s*1px/
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ $cb-skeleton-light: rgba(194, 194, 194, 0.2);
$cb-skeleton-dark: #d2d2d2;

.widget-combobox {
min-width: 0;
min-width: 15ch; // prevent collapse in flex containers with no explicit width
flex-grow: 1;
position: relative;
transition: color 150ms ease 0s;
Expand Down
Loading