/** * Responsive chart frame: tracks the container's width via ResizeObserver, * exposes inner plot dimensions, and detects RTL once (charts flip x in RTL). */ import { computed, onMounted, onUnmounted, ref, type Ref } from 'vue' export interface Padding { top: number; right: number; bottom: number; left: number } export interface FrameOptions { height: Ref | number padding?: Partial } const DEFAULT_PAD: Padding = { top: 8, right: 8, bottom: 22, left: 36 } export function useChartFrame(el: Ref, opts: FrameOptions) { const width = ref(0) const height = computed(() => (typeof opts.height === 'number' ? opts.height : opts.height.value)) const pad = computed(() => ({ ...DEFAULT_PAD, ...opts.padding })) const innerWidth = computed(() => Math.max(0, width.value - pad.value.left - pad.value.right)) const innerHeight = computed(() => Math.max(0, height.value - pad.value.top - pad.value.bottom)) const isRTL = ref(false) function detectRTL() { if (typeof document === 'undefined') return const node = el.value const dir = node?.closest('[dir]')?.getAttribute('dir') || document.documentElement.dir isRTL.value = dir === 'rtl' || document.documentElement.lang === 'he' } let ro: ResizeObserver | null = null onMounted(() => { detectRTL() if (el.value) { width.value = el.value.clientWidth || 0 ro = new ResizeObserver((entries) => { for (const e of entries) width.value = e.contentRect.width }) ro.observe(el.value) } }) onUnmounted(() => ro?.disconnect()) /** Map a plot-local x (0..innerWidth) to the painted x, flipping in RTL. */ const flipX = (x: number) => (isRTL.value ? innerWidth.value - x : x) return { width, height, pad, innerWidth, innerHeight, isRTL, flipX } }