156 lines
4.0 KiB
TypeScript
156 lines
4.0 KiB
TypeScript
"use client"
|
|
|
|
import { useCallback, useMemo, useRef, useState } from "react"
|
|
import type { DocPageLayout, DocPageSetup } from "@/lib/drive/doc-page-setup"
|
|
import { pxToMm } from "@/lib/drive/doc-page-setup"
|
|
import {
|
|
clampPageMarginPx,
|
|
mergeMarginPreview,
|
|
type DocsPageMarginsPx,
|
|
type DocsRulerMarginSide,
|
|
} from "@/lib/drive/docs-ruler-margin-math"
|
|
import { formatMarginDistanceLabel } from "@/lib/drive/docs-ruler-units"
|
|
import type { DocsRulerDragTooltipState } from "@/components/drive/richtext/docs-ruler-markers"
|
|
|
|
function applyMarginDragPx(
|
|
side: DocsRulerMarginSide,
|
|
rawValuePx: number,
|
|
base: DocsPageMarginsPx,
|
|
preview: Partial<DocsPageMarginsPx> | null,
|
|
pageWidth: number,
|
|
pageHeight: number
|
|
): { nextPreview: Partial<DocsPageMarginsPx>; clamped: number } {
|
|
const current = mergeMarginPreview(base, preview)
|
|
let nextValue = rawValuePx
|
|
if (side === "right") {
|
|
nextValue = pageWidth - rawValuePx
|
|
} else if (side === "bottom") {
|
|
nextValue = pageHeight - rawValuePx
|
|
}
|
|
const clamped = clampPageMarginPx(
|
|
side,
|
|
nextValue,
|
|
current,
|
|
pageWidth,
|
|
pageHeight
|
|
)
|
|
return {
|
|
nextPreview: { ...(preview ?? {}), [side]: clamped },
|
|
clamped,
|
|
}
|
|
}
|
|
|
|
export function useDocsRulerMarginDrag({
|
|
pageLayout,
|
|
editable,
|
|
onPageSetupChange,
|
|
}: {
|
|
pageLayout: DocPageLayout
|
|
editable: boolean
|
|
onPageSetupChange?: (
|
|
patch: Partial<DocPageSetup>,
|
|
options?: { immediate?: boolean }
|
|
) => void
|
|
}) {
|
|
const [previewPx, setPreviewPx] = useState<Partial<DocsPageMarginsPx> | null>(null)
|
|
const [dragTooltip, setDragTooltip] = useState<DocsRulerDragTooltipState>(null)
|
|
const dragBaseRef = useRef<DocsPageMarginsPx>(pageLayout.marginsPx)
|
|
/** Sync preview during drag — window pointer events don't flush React state in time. */
|
|
const previewRef = useRef<Partial<DocsPageMarginsPx> | null>(null)
|
|
const pageLayoutRef = useRef(pageLayout)
|
|
pageLayoutRef.current = pageLayout
|
|
|
|
const marginsPx = useMemo(
|
|
() => mergeMarginPreview(pageLayout.marginsPx, previewPx),
|
|
[pageLayout.marginsPx, previewPx]
|
|
)
|
|
|
|
const pageLayoutWithMargins = useMemo(
|
|
(): DocPageLayout => ({
|
|
...pageLayout,
|
|
marginsPx,
|
|
}),
|
|
[pageLayout, marginsPx]
|
|
)
|
|
|
|
const beginMarginDrag = useCallback(
|
|
(_side: DocsRulerMarginSide) => {
|
|
if (!editable) return
|
|
const layout = pageLayoutRef.current
|
|
dragBaseRef.current = mergeMarginPreview(layout.marginsPx, previewRef.current)
|
|
previewRef.current = null
|
|
},
|
|
[editable]
|
|
)
|
|
|
|
const moveMarginDrag = useCallback(
|
|
(side: DocsRulerMarginSide, rawValuePx: number, clientX: number, clientY: number) => {
|
|
if (!editable) return
|
|
|
|
const layout = pageLayoutRef.current
|
|
const { nextPreview, clamped } = applyMarginDragPx(
|
|
side,
|
|
rawValuePx,
|
|
dragBaseRef.current,
|
|
previewRef.current,
|
|
layout.widthPx,
|
|
layout.heightPx
|
|
)
|
|
|
|
previewRef.current = nextPreview
|
|
setPreviewPx(nextPreview)
|
|
setDragTooltip({
|
|
label: formatMarginDistanceLabel(clamped, layout.format.id),
|
|
x: clientX,
|
|
y: clientY,
|
|
})
|
|
},
|
|
[editable]
|
|
)
|
|
|
|
const endMarginDrag = useCallback(() => {
|
|
setDragTooltip(null)
|
|
|
|
const preview = previewRef.current
|
|
previewRef.current = null
|
|
|
|
if (!editable || !preview) {
|
|
setPreviewPx(null)
|
|
return
|
|
}
|
|
|
|
const layout = pageLayoutRef.current
|
|
const merged = mergeMarginPreview(layout.marginsPx, preview)
|
|
setPreviewPx(null)
|
|
|
|
onPageSetupChange?.(
|
|
{
|
|
marginsMm: {
|
|
top: pxToMm(merged.top),
|
|
right: pxToMm(merged.right),
|
|
bottom: pxToMm(merged.bottom),
|
|
left: pxToMm(merged.left),
|
|
},
|
|
},
|
|
{ immediate: true }
|
|
)
|
|
}, [editable, onPageSetupChange])
|
|
|
|
const cancelMarginDrag = useCallback(() => {
|
|
previewRef.current = null
|
|
setPreviewPx(null)
|
|
setDragTooltip(null)
|
|
}, [])
|
|
|
|
return {
|
|
marginsPx,
|
|
pageLayoutWithMargins,
|
|
marginDragActive: previewPx != null,
|
|
dragTooltip,
|
|
beginMarginDrag,
|
|
moveMarginDrag,
|
|
endMarginDrag,
|
|
cancelMarginDrag,
|
|
}
|
|
}
|