refactor(chart): enforce 400-line source limit
Some checks failed
Package component / package (push) Failing after 4m19s
Some checks failed
Package component / package (push) Failing after 4m19s
This commit is contained in:
306
src/components/core/useWaveformChartLifecycle.ts
Normal file
306
src/components/core/useWaveformChartLifecycle.ts
Normal file
@@ -0,0 +1,306 @@
|
||||
import { zoomIdentity, type ZoomTransform } from 'd3'
|
||||
import {
|
||||
nextTick,
|
||||
onBeforeUnmount,
|
||||
onMounted,
|
||||
watch,
|
||||
type ComputedRef,
|
||||
type Ref,
|
||||
type ShallowRef,
|
||||
} from 'vue'
|
||||
|
||||
import type { AnnotationSeriesCandidate } from '../annotation'
|
||||
import type { useWaveformAnnotationInteraction } from '../annotation'
|
||||
import type { DisplaySeries, DisplayTrack } from './types'
|
||||
import type { ResolvedWaveformChartProps, WaveformChartEmit } from './waveformChartTypes'
|
||||
|
||||
interface LifecycleContext {
|
||||
props: ResolvedWaveformChartProps
|
||||
emit: WaveformChartEmit
|
||||
container: Ref<HTMLDivElement | undefined>
|
||||
titleMeasureElement: Ref<HTMLSpanElement | undefined>
|
||||
resizeObserver: ShallowRef<ResizeObserver | undefined>
|
||||
observedWidth: Ref<number>
|
||||
observedHeight: Ref<number>
|
||||
measuredTitleWidth: Ref<number>
|
||||
measuredTitleHeight: Ref<number>
|
||||
resolvedTitleText: ComputedRef<string>
|
||||
titleAreaReserved: ComputedRef<boolean>
|
||||
titleMeasureStyle: ComputedRef<object>
|
||||
spacePressed: Ref<boolean>
|
||||
pointerInsideChart: Ref<boolean>
|
||||
currentPage: Ref<number>
|
||||
pageCount: ComputedRef<number>
|
||||
pagedTracks: ComputedRef<Array<DisplayTrack | undefined>>
|
||||
gridOptions: ComputedRef<{ rowCount: number; columnCount: number }>
|
||||
chartSeries: ComputedRef<DisplaySeries[]>
|
||||
chartTracks: ComputedRef<DisplayTrack[]>
|
||||
innerWidth: ComputedRef<number>
|
||||
innerHeight: ComputedRef<number>
|
||||
activeInteractionMode: ComputedRef<string | undefined>
|
||||
hiddenSeriesIdSet: ComputedRef<Set<string>>
|
||||
internalHiddenSeriesIds: Ref<Set<string>>
|
||||
independentTransforms: ShallowRef<ZoomTransform[]>
|
||||
independentYDomains: Ref<Record<number, [number, number]>>
|
||||
annotationInteraction: ReturnType<typeof useWaveformAnnotationInteraction>
|
||||
editorSeriesOptions: Ref<AnnotationSeriesCandidate[]>
|
||||
clearHover: () => void
|
||||
cancelAnnotation: () => void
|
||||
configureZoom: () => void
|
||||
resetViewport: () => void
|
||||
cancelPendingHover: () => void
|
||||
clearZoomBindings: () => void
|
||||
}
|
||||
|
||||
function isEditableTarget(target: EventTarget | null): boolean {
|
||||
return (
|
||||
target instanceof Element &&
|
||||
Boolean(
|
||||
target.closest(
|
||||
'button, input, select, textarea, [contenteditable]:not([contenteditable="false"])',
|
||||
),
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
export function useWaveformChartLifecycle(context: LifecycleContext) {
|
||||
const {
|
||||
props,
|
||||
emit,
|
||||
container,
|
||||
titleMeasureElement,
|
||||
resizeObserver,
|
||||
observedWidth,
|
||||
observedHeight,
|
||||
measuredTitleWidth,
|
||||
measuredTitleHeight,
|
||||
resolvedTitleText,
|
||||
titleAreaReserved,
|
||||
titleMeasureStyle,
|
||||
spacePressed,
|
||||
pointerInsideChart,
|
||||
currentPage,
|
||||
pageCount,
|
||||
pagedTracks,
|
||||
gridOptions,
|
||||
chartSeries,
|
||||
chartTracks,
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
activeInteractionMode,
|
||||
hiddenSeriesIdSet,
|
||||
internalHiddenSeriesIds,
|
||||
independentTransforms,
|
||||
independentYDomains,
|
||||
annotationInteraction,
|
||||
editorSeriesOptions,
|
||||
clearHover,
|
||||
cancelAnnotation,
|
||||
configureZoom,
|
||||
resetViewport,
|
||||
cancelPendingHover,
|
||||
clearZoomBindings,
|
||||
} = context
|
||||
|
||||
function handleInteractionKeyDown(event: KeyboardEvent) {
|
||||
if (event.code !== 'Space' || !props.pannable || !pointerInsideChart.value) return
|
||||
if (isEditableTarget(event.target)) return
|
||||
spacePressed.value = true
|
||||
event.preventDefault()
|
||||
}
|
||||
|
||||
function handleInteractionKeyUp(event: KeyboardEvent) {
|
||||
if (event.code === 'Space') spacePressed.value = false
|
||||
}
|
||||
|
||||
function goToPage(page: number) {
|
||||
const nextPage = Math.min(pageCount.value, Math.max(1, Math.floor(page)))
|
||||
if (nextPage === currentPage.value) return
|
||||
currentPage.value = nextPage
|
||||
clearHover()
|
||||
annotationInteraction.closeContextMenu()
|
||||
cancelAnnotation()
|
||||
if (props.displayMode === 'independent') {
|
||||
independentTransforms.value = pagedTracks.value.map(() => zoomIdentity)
|
||||
independentYDomains.value = {}
|
||||
}
|
||||
void nextTick(configureZoom)
|
||||
emit('page-change', nextPage, pageCount.value)
|
||||
}
|
||||
|
||||
function handleDataReferenceChange() {
|
||||
if (props.displayMode === 'independent') {
|
||||
const currentTransforms = independentTransforms.value
|
||||
independentTransforms.value = chartTracks.value.map(
|
||||
(_track, index) => currentTransforms[index] ?? zoomIdentity,
|
||||
)
|
||||
}
|
||||
clearHover()
|
||||
editorSeriesOptions.value = []
|
||||
void nextTick(configureZoom)
|
||||
}
|
||||
|
||||
watch(
|
||||
[
|
||||
innerWidth,
|
||||
innerHeight,
|
||||
() => props.zoomable,
|
||||
() => props.minZoomSpan,
|
||||
() => props.initialXDomain,
|
||||
() => props.initialXDomains,
|
||||
() => props.displayMode,
|
||||
() => chartTracks.value.length,
|
||||
currentPage,
|
||||
() => gridOptions.value.rowCount,
|
||||
() => gridOptions.value.columnCount,
|
||||
activeInteractionMode,
|
||||
],
|
||||
async () => {
|
||||
await nextTick()
|
||||
configureZoom()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.displayMode,
|
||||
() => {
|
||||
const previousPage = currentPage.value
|
||||
currentPage.value = 1
|
||||
resetViewport()
|
||||
if (previousPage !== 1) emit('page-change', 1, pageCount.value)
|
||||
},
|
||||
)
|
||||
|
||||
watch([pageCount, () => props.grid?.rowCount, () => props.grid?.columnCount], () => {
|
||||
const previousPage = currentPage.value
|
||||
currentPage.value =
|
||||
currentPage.value > pageCount.value
|
||||
? pageCount.value
|
||||
: currentPage.value !== 1
|
||||
? 1
|
||||
: currentPage.value
|
||||
if (previousPage !== currentPage.value) {
|
||||
emit('page-change', currentPage.value, pageCount.value)
|
||||
}
|
||||
clearHover()
|
||||
void nextTick(configureZoom)
|
||||
})
|
||||
|
||||
watch(activeInteractionMode, () => {
|
||||
editorSeriesOptions.value = []
|
||||
})
|
||||
|
||||
watch(
|
||||
() => chartSeries.value.map((series) => series.id).join('\u0000'),
|
||||
() => {
|
||||
if (props.hiddenSeriesIds !== undefined) return
|
||||
const availableIds = new Set(chartSeries.value.map((series) => series.id))
|
||||
const retainedIds = new Set(
|
||||
Array.from(internalHiddenSeriesIds.value).filter((seriesId) => availableIds.has(seriesId)),
|
||||
)
|
||||
if (
|
||||
retainedIds.size !== internalHiddenSeriesIds.value.size ||
|
||||
Array.from(internalHiddenSeriesIds.value).some((seriesId) => !retainedIds.has(seriesId))
|
||||
) {
|
||||
internalHiddenSeriesIds.value = retainedIds
|
||||
}
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
watch(
|
||||
() =>
|
||||
chartTracks.value
|
||||
.flatMap((track) => track.visibleSeries.map((series) => series.id))
|
||||
.join('\u0000'),
|
||||
() => {
|
||||
clearHover()
|
||||
editorSeriesOptions.value = []
|
||||
const draftSeriesId = annotationInteraction.editorDraft.value?.annotation.seriesId
|
||||
if (
|
||||
draftSeriesId &&
|
||||
(!chartSeries.value.some((series) => series.id === draftSeriesId) ||
|
||||
hiddenSeriesIdSet.value.has(draftSeriesId))
|
||||
) {
|
||||
annotationInteraction.closeEditor()
|
||||
}
|
||||
const contextAnnotationId = annotationInteraction.contextMenu.value?.annotationId
|
||||
const contextAnnotation = props.annotations.find((item) => item.id === contextAnnotationId)
|
||||
if (contextAnnotation && hiddenSeriesIdSet.value.has(contextAnnotation.seriesId)) {
|
||||
annotationInteraction.closeContextMenu()
|
||||
}
|
||||
void nextTick(configureZoom)
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.annotationsVisible,
|
||||
(visible) => {
|
||||
if (!visible) {
|
||||
annotationInteraction.closeContextMenu()
|
||||
cancelAnnotation()
|
||||
}
|
||||
},
|
||||
)
|
||||
|
||||
watch(
|
||||
() => props.annotations,
|
||||
(annotations) => {
|
||||
const menuId = annotationInteraction.contextMenu.value?.annotationId
|
||||
if (menuId && !annotations.some((item) => item.id === menuId)) {
|
||||
annotationInteraction.closeContextMenu()
|
||||
}
|
||||
const draft = annotationInteraction.editorDraft.value
|
||||
if (draft?.mode === 'edit' && !annotations.some((item) => item.id === draft.annotation.id)) {
|
||||
annotationInteraction.closeEditor()
|
||||
}
|
||||
},
|
||||
{ deep: true },
|
||||
)
|
||||
|
||||
function measureTitle() {
|
||||
if (!titleAreaReserved.value || !titleMeasureElement.value) {
|
||||
measuredTitleWidth.value = 0
|
||||
measuredTitleHeight.value = 0
|
||||
return
|
||||
}
|
||||
const bounds = titleMeasureElement.value.getBoundingClientRect()
|
||||
measuredTitleWidth.value = titleMeasureElement.value.scrollWidth || bounds.width
|
||||
measuredTitleHeight.value = titleMeasureElement.value.scrollHeight || bounds.height
|
||||
}
|
||||
|
||||
watch(
|
||||
[resolvedTitleText, titleAreaReserved, titleMeasureStyle],
|
||||
async () => {
|
||||
measuredTitleWidth.value = 0
|
||||
measuredTitleHeight.value = 0
|
||||
await nextTick()
|
||||
measureTitle()
|
||||
},
|
||||
{ immediate: true },
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('keydown', handleInteractionKeyDown)
|
||||
window.addEventListener('keyup', handleInteractionKeyUp)
|
||||
if (!container.value) return
|
||||
resizeObserver.value = new ResizeObserver(([entry]) => {
|
||||
observedWidth.value = Math.max(0, entry?.contentRect.width ?? 0)
|
||||
observedHeight.value = Math.max(0, entry?.contentRect.height ?? 0)
|
||||
void nextTick(measureTitle)
|
||||
})
|
||||
resizeObserver.value.observe(container.value)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('keydown', handleInteractionKeyDown)
|
||||
window.removeEventListener('keyup', handleInteractionKeyUp)
|
||||
cancelPendingHover()
|
||||
resizeObserver.value?.disconnect()
|
||||
clearZoomBindings()
|
||||
editorSeriesOptions.value = []
|
||||
})
|
||||
|
||||
return { goToPage, handleDataReferenceChange }
|
||||
}
|
||||
Reference in New Issue
Block a user