fix(ui-trajectory): own composer overlay geometry
This commit is contained in:
@@ -138,6 +138,23 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
|||||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-trajectory'))
|
onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-trajectory'))
|
||||||
await page.getByRole('tab', { name: 'Trajectory' }).click()
|
await page.getByRole('tab', { name: 'Trajectory' }).click()
|
||||||
await page.waitForTimeout(100)
|
await page.waitForTimeout(100)
|
||||||
|
const overlayLayout = await page.getByRole('table').evaluate((table) => {
|
||||||
|
const host = table.closest('[data-conversation-scroll]')
|
||||||
|
const seat = host?.querySelector('[data-composer-seat]') ?? null
|
||||||
|
const pane = table.parentElement
|
||||||
|
return {
|
||||||
|
hostPosition: host === null ? null : getComputedStyle(host).position,
|
||||||
|
paneOverflowX: pane === null ? null : getComputedStyle(pane).overflowX,
|
||||||
|
paneScrollableWidth: pane === null ? null : pane.scrollWidth - pane.clientWidth,
|
||||||
|
seatPosition: seat === null ? null : getComputedStyle(seat).position,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
expect(overlayLayout).toEqual({
|
||||||
|
hostPosition: 'relative',
|
||||||
|
paneOverflowX: 'hidden',
|
||||||
|
paneScrollableWidth: 0,
|
||||||
|
seatPosition: 'absolute',
|
||||||
|
})
|
||||||
expect({
|
expect({
|
||||||
pageErrors: tripwire.pageErrors,
|
pageErrors: tripwire.pageErrors,
|
||||||
slotErrors,
|
slotErrors,
|
||||||
@@ -153,6 +170,8 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
|||||||
await page.locator('tr[data-kind="tool"]').first().click()
|
await page.locator('tr[data-kind="tool"]').first().click()
|
||||||
const details = page.getByRole('complementary', { name: 'Event details' })
|
const details = page.getByRole('complementary', { name: 'Event details' })
|
||||||
await expect.poll(() => details.count(), { timeout: 10_000 }).toBe(1)
|
await expect.poll(() => details.count(), { timeout: 10_000 }).toBe(1)
|
||||||
|
expect(await details.getByRole('tabpanel').evaluate(panel => getComputedStyle(panel).overflowX))
|
||||||
|
.toBe('hidden')
|
||||||
await page.evaluate(() => { document.body.setAttribute('data-ds-dark-theme', '') })
|
await page.evaluate(() => { document.body.setAttribute('data-ds-dark-theme', '') })
|
||||||
const darkSummarySurfaces = await details.getByRole('heading', { name: 'Payload' }).evaluate(heading => ({
|
const darkSummarySurfaces = await details.getByRole('heading', { name: 'Payload' }).evaluate(heading => ({
|
||||||
heading: getComputedStyle(heading).backgroundColor,
|
heading: getComputedStyle(heading).backgroundColor,
|
||||||
|
|||||||
@@ -208,6 +208,26 @@
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Views may opt into a composer overlay while ConversationRoot retains
|
||||||
|
ownership of the seat geometry and its active-phase precedence. */
|
||||||
|
.scrollBody:has([data-conversation-composer-overlay]) {
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollBody:has([data-conversation-composer-overlay]) > .viewArea {
|
||||||
|
flex: 1 1 0;
|
||||||
|
min-height: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scrollBody:has([data-conversation-composer-overlay]) > .composerSeat {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
/* Hero phase: the composer stack (hero chrome + workspace row + card) is
|
/* Hero phase: the composer stack (hero chrome + workspace row + card) is
|
||||||
flex-centered in the column; composer phase docks it at the bottom. Flex,
|
flex-centered in the column; composer phase docks it at the bottom. Flex,
|
||||||
NOT absolute+transform: a transform would make this box the containing
|
NOT absolute+transform: a transform would make this box the containing
|
||||||
|
|||||||
@@ -15,7 +15,8 @@
|
|||||||
.tablePane {
|
.tablePane {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: auto;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
padding-bottom: var(--dsh-trajectory-bottom-clearance, 0px);
|
padding-bottom: var(--dsh-trajectory-bottom-clearance, 0px);
|
||||||
container: trajectory-table / inline-size;
|
container: trajectory-table / inline-size;
|
||||||
}
|
}
|
||||||
@@ -28,7 +29,7 @@
|
|||||||
);
|
);
|
||||||
|
|
||||||
width: 100%;
|
width: 100%;
|
||||||
min-width: 480px;
|
min-width: 0;
|
||||||
border-spacing: 0;
|
border-spacing: 0;
|
||||||
table-layout: fixed;
|
table-layout: fixed;
|
||||||
color: var(--dsw-alias-label-primary);
|
color: var(--dsw-alias-label-primary);
|
||||||
@@ -905,7 +906,8 @@
|
|||||||
.detailBody {
|
.detailBody {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
min-height: 0;
|
min-height: 0;
|
||||||
overflow: auto;
|
overflow-x: hidden;
|
||||||
|
overflow-y: auto;
|
||||||
padding-bottom: var(--dsh-trajectory-bottom-clearance, 0px);
|
padding-bottom: var(--dsh-trajectory-bottom-clearance, 0px);
|
||||||
scrollbar-gutter: stable;
|
scrollbar-gutter: stable;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ export function TrajectoryView({
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={css.root}>
|
<div className={css.root} data-conversation-composer-overlay="">
|
||||||
<TrajectoryToolbar
|
<TrajectoryToolbar
|
||||||
actualDuration={actualDuration}
|
actualDuration={actualDuration}
|
||||||
onActualDurationChange={(nextActualDuration) => {
|
onActualDurationChange={(nextActualDuration) => {
|
||||||
|
|||||||
@@ -13,30 +13,6 @@
|
|||||||
background: var(--dsw-alias-bg-layer-1);
|
background: var(--dsw-alias-bg-layer-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Trajectory keeps the ledger and details panel inside the remaining
|
|
||||||
* conversation height; only the internal panes scroll. The composer floats
|
|
||||||
* over the ledger like chat's sticky seat — absolute, not sticky, because
|
|
||||||
* this host does not scroll. */
|
|
||||||
:global([data-conversation-scroll]):has(.root) {
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
:global([data-conversation-scroll]):has(.root) > :first-child {
|
|
||||||
flex: 1 1 0;
|
|
||||||
min-height: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* div qualifier outranks ConversationRoot's active-phase sticky rule (equal
|
|
||||||
* specificity otherwise, and cross-module source order is bundler-defined). */
|
|
||||||
:global([data-conversation-scroll]):has(.root) > :global(div[data-composer-seat]) {
|
|
||||||
position: absolute;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ledger {
|
.ledger {
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 0;
|
z-index: 0;
|
||||||
@@ -47,8 +23,6 @@
|
|||||||
min-width: 0;
|
min-width: 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
|
||||||
/* Internal panes reserve the floating composer's live height plus a 16px
|
/* ConversationRoot publishes the floating composer's live height. */
|
||||||
* breathing gap so end rows and detail bodies can scroll clear of the
|
|
||||||
* overlay. */
|
|
||||||
--dsh-trajectory-bottom-clearance: calc(var(--dsh-composer-height, 152px) + 16px);
|
--dsh-trajectory-bottom-clearance: calc(var(--dsh-composer-height, 152px) + 16px);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -288,6 +288,7 @@ describe('tab switching in ConversationRoot', () => {
|
|||||||
expect(screen.queryByRole('columnheader')).toBeNull()
|
expect(screen.queryByRole('columnheader')).toBeNull()
|
||||||
expect(screen.getByRole('toolbar', { name: 'Trajectory toolbar' })).toBeTruthy()
|
expect(screen.getByRole('toolbar', { name: 'Trajectory toolbar' })).toBeTruthy()
|
||||||
expect(screen.getByRole('region', { name: 'Trajectory timeline' })).toBeTruthy()
|
expect(screen.getByRole('region', { name: 'Trajectory timeline' })).toBeTruthy()
|
||||||
|
expect(view.container.querySelector('[data-conversation-composer-overlay]')).toBeTruthy()
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Collapse turns' }))
|
fireEvent.click(screen.getByRole('button', { name: 'Collapse turns' }))
|
||||||
expect(view.container.querySelector('[data-collapsed-summary="turn"]')).toBeTruthy()
|
expect(view.container.querySelector('[data-collapsed-summary="turn"]')).toBeTruthy()
|
||||||
fireEvent.click(screen.getByRole('button', { name: 'Expand turns' }))
|
fireEvent.click(screen.getByRole('button', { name: 'Expand turns' }))
|
||||||
|
|||||||
Reference in New Issue
Block a user