52 lines
2.5 KiB
TypeScript
52 lines
2.5 KiB
TypeScript
/**
|
|
* Measured-height CSS vars, scoped to one chat surface.
|
|
*
|
|
* The composer and the out-of-flow status stack both publish their measured
|
|
* height so the thread can reserve bottom clearance for them. Those vars used
|
|
* to be written to `document.documentElement` — a single global slot — while
|
|
* the components writing them mount once per chat surface. Session tiles
|
|
* render a full ChatView (thread + composer + status stack) beside the
|
|
* workspace pane, so N surfaces raced for one value: a background tab with a
|
|
* tall stack inflated the foreground thread's padding and pushed its
|
|
* jump-to-bottom button into mid-screen.
|
|
*
|
|
* Publishing onto the surface's own root element instead makes each ChatView
|
|
* read its own measurement through normal CSS inheritance. Remeasuring on
|
|
* session switch cannot fix this — several surfaces are visible at once, so
|
|
* there is no single correct value to remeasure to.
|
|
*
|
|
* A measurement is only ever meaningful to the surface that owns it, so an
|
|
* unowned node has nowhere to publish to and must publish NOWHERE. Writing to
|
|
* the document root instead is not a graceful fallback: `:root` holds the
|
|
* defaults every surface inherits until it publishes its own, so one stale
|
|
* write there is a global floor under every thread's bottom clearance, on
|
|
* every tab, until reload. That is the "randomly huge gap at the bottom of the
|
|
* thread" bug — see surface-vars.test.ts.
|
|
*/
|
|
|
|
export const COMPOSER_HEIGHT_VAR = '--composer-measured-height'
|
|
export const COMPOSER_SURFACE_HEIGHT_VAR = '--composer-surface-measured-height'
|
|
|
|
/**
|
|
* The surface owning `el`, or null when `el` is detached or outside one.
|
|
*
|
|
* Null is the honest answer, not a failure to be papered over. Every publisher
|
|
* lives inside a `[data-chat-surface]` for its whole visible life (a
|
|
* popped-out composer is `position: fixed`, still a descendant), so the only
|
|
* way to miss is a node React already removed — whose measurement is stale by
|
|
* definition.
|
|
*/
|
|
export function chatSurfaceRoot(el: Element | null): HTMLElement | null {
|
|
return el?.closest<HTMLElement>('[data-chat-surface]') ?? null
|
|
}
|
|
|
|
/** Publish a measured-height var on the surface owning `el`. No owner, no write. */
|
|
export function setSurfaceVar(el: Element | null, name: string, value: string): void {
|
|
chatSurfaceRoot(el)?.style.setProperty(name, value)
|
|
}
|
|
|
|
/** Clear a measured-height var from `root`. */
|
|
export function clearSurfaceVar(root: HTMLElement | null, name: string): void {
|
|
root?.style.removeProperty(name)
|
|
}
|