Selecting a camera in `live.display.mode: grid` lands in two visible passes: the cell gets its 2-column width immediately but keeps its 1-row height for ~400 ms, and the rest of the grid only settles ~700 ms after the click (measured per-frame on `main`; v7.27.4 has the same two passes, v8's early position resolution just leaves the late height correction standing out as a lone vertical jump). **Cause:** a grid cell's height follows its content in the same style recalculation as its width (intrinsic media ratio, `dimensions.aspect_ratio`, or the unsized 16:9 reservation), but the slotted carousel carries an inline `max-height` from `MediaHeightController` (debounced 0.3 s + 0.1 s transition; it sizes the card outside grids). In a grid that cap can only ever delay growth: it pins the freshly widened cell at its old height until the debounce fires, and the correction then also rides the 300 ms `_throttledLayout`. `selectCell()`'s existing `forceReflow()` + `layout()` was already positioned to do this in one pass; the cap starves it of the final height. **Fix:** neutralize the cap on grid cells (`max-height: none !important`; important is needed to beat the inline style). Covers the live and viewer grids; non-grid behaviour is untouched. This also stops the cap clipping cells by their border width (it was measured on the slide's content box but applied to the cell's border box). **Trade-off worth flagging:** in the *viewer* grid a cell's carousel holds all of one camera's media. If those slides do not share one ratio, the cell now sizes to its tallest slide (letterboxing shorter ones, no re-layout per swipe) instead of tracking the selected one after a debounce. If you would rather leave the viewer untouched, the rule can be scoped to `::slotted(advanced-camera-card-live-carousel)` instead (live grid cells always hold exactly one slide); say so and I will rework the PR that way. **Verification:** - New browser test `tests/components/live/grid.browser.test.ts`: fails on any frame where the newly selected cell is selected-wide but still unselected-high. On `main` it fails with 22 such frames; with this change there are none: the click settles in a single frame (~28 ms), every cell at its final size and position. - `yarn run test`, `yarn run test:browser` (chromium, firefox and webkit for the new test), `yarn run lint`, `yarn run typecheck` pass. - No resize oscillation in a cramped viewport with an appearing/disappearing ancestor scrollbar (the #2306 scenario), on window resizes, or under a narrow-screen `grid_columns: 2` override; verified against a live HA 2026.8.3 dashboard (6 go2rtc cameras). --------- Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
36 lines
1.1 KiB
TypeScript
36 lines
1.1 KiB
TypeScript
import { assert, describe, expect, it } from 'vitest';
|
|
|
|
import { deepQuery } from '../../browser/dom';
|
|
import { MountedCardFactory } from '../../browser/mounted-card';
|
|
import {
|
|
createGenericCameraHASS,
|
|
createStillImageCardConfig,
|
|
} from '../../browser/test-utils';
|
|
|
|
describe('AdvancedCameraCardLiveCarousel', () => {
|
|
it('should cap its own height to fit the media it shows', async () => {
|
|
const card = await MountedCardFactory.createFromSource(
|
|
createStillImageCardConfig(),
|
|
createGenericCameraHASS(),
|
|
);
|
|
await card.events.waitForFirst('advanced-camera-card:media:loaded');
|
|
|
|
const carousel = deepQuery<HTMLElement>(
|
|
card.card,
|
|
'advanced-camera-card-live-carousel',
|
|
);
|
|
assert(carousel);
|
|
|
|
await card.waitForRender(
|
|
() => carousel.style.maxHeight || null,
|
|
'the carousel capping its own height',
|
|
);
|
|
|
|
// Outside a grid the carousel fills the card, so the cap is what gives the
|
|
// card the height of its media rather than of whatever contains it.
|
|
expect(parseFloat(carousel.style.maxHeight)).toBe(
|
|
carousel.getBoundingClientRect().height,
|
|
);
|
|
});
|
|
});
|