refactor(timetable): extract computeStageRowHeight helper; StageHeaderCell takes :row-height-px prop (B2)
Pure structural seam — no layout changes yet (B3 wires the page through). apps/app/src/lib/timetable/row-height.ts (NEW): computeStageRowHeight(laneCount, laneHeightPx, lanePadPx) — one-line pure function with the existing math: max(1, laneCount) * (laneHeight + lanePad) + lanePad. Math.max(1, laneCount) keeps an empty stage row visible at single-lane height instead of collapsing. apps/app/src/components/timetable/StageRow.vue: Switches its inline rowHeightPx computation to call the helper. Behavior identical (the math was the helper's body). apps/app/src/components/timetable/StageHeaderCell.vue: New optional `rowHeightPx?: number` prop. When provided (B3 will pass it from the page via the same helper), the header root applies blockSize inline so the sticky-left column aligns pixel-for-pixel with the row. When omitted, the legacy `block-size: 100%` CSS still applies — every existing call-site keeps working. apps/app/src/lib/timetable/index.ts: re-export the new helper. Tests still green (389 across 54 files); typecheck clean. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,22 +1,37 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue'
|
||||||
import type { Stage } from '@/types/timetable'
|
import type { Stage } from '@/types/timetable'
|
||||||
|
|
||||||
defineProps<{
|
const props = defineProps<{
|
||||||
stage: Stage
|
stage: Stage
|
||||||
/** Number of conflicts on this stage row for the active day. */
|
/** Number of conflicts on this stage row for the active day. */
|
||||||
conflictCount?: number
|
conflictCount?: number
|
||||||
|
/**
|
||||||
|
* Explicit row height in pixels. When provided (page passes it from
|
||||||
|
* computeStageRowHeight), the header sizes to match the corresponding
|
||||||
|
* StageRow exactly so the sticky-left column aligns pixel-for-pixel.
|
||||||
|
* When omitted, the legacy `block-size: 100%` CSS path takes over.
|
||||||
|
*/
|
||||||
|
rowHeightPx?: number
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
edit: [stage: Stage]
|
edit: [stage: Stage]
|
||||||
delete: [stage: Stage]
|
delete: [stage: Stage]
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
|
const heightStyle = computed(() =>
|
||||||
|
typeof props.rowHeightPx === 'number'
|
||||||
|
? { blockSize: `${props.rowHeightPx}px` }
|
||||||
|
: null,
|
||||||
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div
|
<div
|
||||||
class="tt-stage-header"
|
class="tt-stage-header"
|
||||||
:data-stage-id="stage.id"
|
:data-stage-id="stage.id"
|
||||||
|
:style="heightStyle"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
class="tt-stage-header__swatch"
|
class="tt-stage-header__swatch"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { computed } from 'vue'
|
import { computed } from 'vue'
|
||||||
import PerformanceBlock from './PerformanceBlock.vue'
|
import PerformanceBlock from './PerformanceBlock.vue'
|
||||||
|
import { computeStageRowHeight } from '@/lib/timetable/row-height'
|
||||||
import { isoToMinutes, minutesToPx } from '@/lib/timetable/time-grid'
|
import { isoToMinutes, minutesToPx } from '@/lib/timetable/time-grid'
|
||||||
import type { Performance, Stage } from '@/types/timetable'
|
import type { Performance, Stage } from '@/types/timetable'
|
||||||
|
|
||||||
@@ -30,7 +31,7 @@ const emit = defineEmits<{
|
|||||||
const laneHeightPx = 44
|
const laneHeightPx = 44
|
||||||
const lanePadPx = 4
|
const lanePadPx = 4
|
||||||
const totalWidthPx = computed(() => props.totalMinutes * props.pxPerMin)
|
const totalWidthPx = computed(() => props.totalMinutes * props.pxPerMin)
|
||||||
const rowHeightPx = computed(() => Math.max(1, props.laneCount) * (laneHeightPx + lanePadPx) + lanePadPx)
|
const rowHeightPx = computed(() => computeStageRowHeight(props.laneCount, laneHeightPx, lanePadPx))
|
||||||
|
|
||||||
interface PositionedBlock {
|
interface PositionedBlock {
|
||||||
perf: Performance
|
perf: Performance
|
||||||
|
|||||||
@@ -4,3 +4,4 @@ export * from './conflict'
|
|||||||
export * from './b2b'
|
export * from './b2b'
|
||||||
export * from './capacity'
|
export * from './capacity'
|
||||||
export * from './lane'
|
export * from './lane'
|
||||||
|
export * from './row-height'
|
||||||
|
|||||||
18
apps/app/src/lib/timetable/row-height.ts
Normal file
18
apps/app/src/lib/timetable/row-height.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
/**
|
||||||
|
* Per-stage row height in pixels for the timetable canvas.
|
||||||
|
*
|
||||||
|
* The `Math.max(1, laneCount)` keeps an empty stage row visible at the
|
||||||
|
* single-lane height instead of collapsing to zero. Lanes are stacked
|
||||||
|
* with a small gap between them, plus an outer pad above and below.
|
||||||
|
*
|
||||||
|
* Used by both StageRow (to size its own positioned-absolute lane area)
|
||||||
|
* and StageHeaderCell (so the sticky-left header column aligns with the
|
||||||
|
* scrolling row content pixel-for-pixel).
|
||||||
|
*/
|
||||||
|
export function computeStageRowHeight(
|
||||||
|
laneCount: number,
|
||||||
|
laneHeightPx: number,
|
||||||
|
lanePadPx: number,
|
||||||
|
): number {
|
||||||
|
return Math.max(1, laneCount) * (laneHeightPx + lanePadPx) + lanePadPx
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user