fix(trajectory): refine timeline drag and tooltips
This commit is contained in:
@@ -321,6 +321,13 @@
|
|||||||
width: 76px;
|
width: 76px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.kindSlot :global([role='tooltip']) {
|
||||||
|
border: 1px solid var(--dsw-alias-border-l2);
|
||||||
|
background: var(--dsw-alias-bg-layer-2);
|
||||||
|
box-shadow: var(--dsw-shadow-lv2);
|
||||||
|
color: var(--dsw-alias-label-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.content {
|
.content {
|
||||||
padding-left: 4px !important;
|
padding-left: 4px !important;
|
||||||
color: var(--dsw-alias-label-primary);
|
color: var(--dsw-alias-label-primary);
|
||||||
|
|||||||
@@ -1868,7 +1868,10 @@ export function TrajectoryTable({
|
|||||||
<span
|
<span
|
||||||
className={css.kindSlot}
|
className={css.kindSlot}
|
||||||
>
|
>
|
||||||
<Tooltip label={KIND_LABEL[record.cell.kind]} side="bottom">
|
<Tooltip
|
||||||
|
label={KIND_LABEL[record.cell.kind]}
|
||||||
|
side="right"
|
||||||
|
>
|
||||||
<span
|
<span
|
||||||
className={`${css.kindTag} ${
|
className={`${css.kindTag} ${
|
||||||
record.cell.kind === 'system'
|
record.cell.kind === 'system'
|
||||||
|
|||||||
@@ -70,11 +70,17 @@ function rangeFraction(
|
|||||||
range: TrajectoryTimeRange,
|
range: TrajectoryTimeRange,
|
||||||
start: number,
|
start: number,
|
||||||
duration: number,
|
duration: number,
|
||||||
|
minimum: number,
|
||||||
|
maximum: number,
|
||||||
): FractionRange {
|
): FractionRange {
|
||||||
return orderedRange(
|
const bounded = orderedRange(
|
||||||
clampFraction((range.start - start) / duration),
|
Math.min(maximum, Math.max(minimum, range.start)),
|
||||||
clampFraction((range.end - start) / duration),
|
Math.min(maximum, Math.max(minimum, range.end)),
|
||||||
)
|
)
|
||||||
|
return {
|
||||||
|
start: (bounded.start - start) / duration,
|
||||||
|
end: (bounded.end - start) / duration,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function LaneLabels() {
|
function LaneLabels() {
|
||||||
@@ -185,10 +191,10 @@ export const TrajectoryTimeline = memo(function TrajectoryTimeline({
|
|||||||
} as CSSProperties
|
} as CSSProperties
|
||||||
const committed = model === null || range === null
|
const committed = model === null || range === null
|
||||||
? null
|
? null
|
||||||
: rangeFraction(range, domainStart, domainDuration)
|
: rangeFraction(range, domainStart, domainDuration, model.start, model.end)
|
||||||
const draftFraction = model === null || draft === null
|
const draftFraction = model === null || draft === null
|
||||||
? null
|
? null
|
||||||
: rangeFraction(draft, domainStart, domainDuration)
|
: rangeFraction(draft, domainStart, domainDuration, model.start, model.end)
|
||||||
const visibleRange = draftFraction ?? committed
|
const visibleRange = draftFraction ?? committed
|
||||||
const activeRange = draft ?? range
|
const activeRange = draft ?? range
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|||||||
@@ -184,7 +184,9 @@ describe('TrajectoryTable', () => {
|
|||||||
expect(toolTag?.querySelector('[data-role-icon="wrench"]')).toBeTruthy()
|
expect(toolTag?.querySelector('[data-role-icon="wrench"]')).toBeTruthy()
|
||||||
|
|
||||||
fireEvent.mouseEnter(toolTag as HTMLElement)
|
fireEvent.mouseEnter(toolTag as HTMLElement)
|
||||||
expect(screen.getByRole('tooltip').textContent).toBe('TOOL')
|
const tooltip = screen.getByRole('tooltip')
|
||||||
|
expect(tooltip.textContent).toBe('TOOL')
|
||||||
|
expect(tooltip.getAttribute('data-side')).toBe('right')
|
||||||
fireEvent.mouseLeave(toolTag as HTMLElement)
|
fireEvent.mouseLeave(toolTag as HTMLElement)
|
||||||
expect(screen.queryByRole('tooltip')).toBeNull()
|
expect(screen.queryByRole('tooltip')).toBeNull()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -465,7 +465,7 @@ describe('timeline projection', () => {
|
|||||||
|
|
||||||
it('auto-pans a zoomed viewport while a range drag pushes against an edge', () => {
|
it('auto-pans a zoomed viewport while a range drag pushes against an edge', () => {
|
||||||
const onRangeChange = vi.fn()
|
const onRangeChange = vi.fn()
|
||||||
render(
|
const view = render(
|
||||||
<TrajectoryTimeline
|
<TrajectoryTimeline
|
||||||
turns={longTurns}
|
turns={longTurns}
|
||||||
mode="sequence"
|
mode="sequence"
|
||||||
@@ -483,13 +483,26 @@ describe('timeline projection', () => {
|
|||||||
for (let index = 0; index < 24; index++) {
|
for (let index = 0; index < 24; index++) {
|
||||||
fireEvent.pointerMove(plot, { clientX: 99, pointerId: 1 })
|
fireEvent.pointerMove(plot, { clientX: 99, pointerId: 1 })
|
||||||
}
|
}
|
||||||
|
const draftSelection = view.container.querySelectorAll<HTMLElement>(
|
||||||
|
'[data-dragging="true"]',
|
||||||
|
)
|
||||||
|
expect(draftSelection).toHaveLength(2)
|
||||||
|
for (const overlay of draftSelection) {
|
||||||
|
expect(Number.parseFloat(
|
||||||
|
overlay.style.getPropertyValue('--trajectory-selection-left'),
|
||||||
|
)).toBeLessThan(0)
|
||||||
|
}
|
||||||
fireEvent.pointerUp(plot, { clientX: 99, pointerId: 1 })
|
fireEvent.pointerUp(plot, { clientX: 99, pointerId: 1 })
|
||||||
|
|
||||||
const selectedRange = onRangeChange.mock.calls.at(-1)?.[0] as
|
const selectedRange = onRangeChange.mock.calls.at(-1)?.[0] as
|
||||||
| { start: number; end: number }
|
| { start: number; end: number }
|
||||||
| undefined
|
| undefined
|
||||||
|
const fullRange = deriveTrajectoryTimeline(longTurns)
|
||||||
expect(selectedRange).toBeDefined()
|
expect(selectedRange).toBeDefined()
|
||||||
|
expect(fullRange).not.toBeNull()
|
||||||
expect((selectedRange?.end ?? 0) - (selectedRange?.start ?? 0)).toBeGreaterThan(4)
|
expect((selectedRange?.end ?? 0) - (selectedRange?.start ?? 0)).toBeGreaterThan(4)
|
||||||
|
expect(selectedRange?.start).toBeGreaterThanOrEqual(fullRange?.start ?? 0)
|
||||||
|
expect(selectedRange?.end).toBeLessThanOrEqual(fullRange?.end ?? 0)
|
||||||
})
|
})
|
||||||
|
|
||||||
it('uses equal-width operation slots and stable semantic lanes', () => {
|
it('uses equal-width operation slots and stable semantic lanes', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user