feat(ee): bases

Table and kanban UI, formula engine package, and the base-embed editor extension
This commit is contained in:
Philipinho
2026-06-14 01:29:06 +01:00
parent d86d51c27e
commit 4e5bff6d55
233 changed files with 22278 additions and 141 deletions
@@ -0,0 +1,53 @@
import { type RefObject, useEffect } from "react";
// Keeps the header's scrollLeft in lockstep with the body's. Also converts
// vertical wheel events on the header into horizontal scroll on the body.
// Generic so callers can pass useRef<HTMLDivElement>(null) without a cast.
export function useHorizontalScrollSync<
TBody extends HTMLElement,
THeader extends HTMLElement,
>(
bodyRef: RefObject<TBody | null>,
headerRef: RefObject<THeader | null>,
): void {
useEffect(() => {
const body = bodyRef.current;
const header = headerRef.current;
if (!body || !header) return;
let rafId = 0;
const sync = () => {
rafId = 0;
header.scrollLeft = body.scrollLeft;
};
const onBodyScroll = () => {
if (rafId !== 0) return;
rafId = requestAnimationFrame(sync);
};
const onHeaderWheel = (e: WheelEvent) => {
// Trackpad horizontal-dominant gestures deliver deltaX; let those
// flow naturally. Convert vertical ticks into horizontal pan.
if (Math.abs(e.deltaX) > Math.abs(e.deltaY)) return;
if (e.deltaY === 0) return;
// preventDefault suppresses the default vertical scroll (requires
// non-passive listener, configured below).
e.preventDefault();
body.scrollLeft += e.deltaY;
};
body.addEventListener("scroll", onBodyScroll, { passive: true });
header.addEventListener("wheel", onHeaderWheel, { passive: false });
// Initial sync in case the body is already scrolled when the hook mounts.
header.scrollLeft = body.scrollLeft;
return () => {
body.removeEventListener("scroll", onBodyScroll);
header.removeEventListener("wheel", onHeaderWheel);
if (rafId !== 0) cancelAnimationFrame(rafId);
};
}, [bodyRef, headerRef]);
}