Files
dbx/apps/desktop/src/composables/useMeasuredWidth.ts
T

63 lines
2.1 KiB
TypeScript

import { nextTick, onUnmounted, ref, watch, type Ref, type WatchSource } from "vue";
/**
* Measured border-box width (CSS pixels) of a mirror element.
*
* Measuring primitive for widths that char-count arithmetic cannot predict:
* bind the returned ref to a template ref and it tracks the element's real
* laid-out width — including font-fallback advances, letter-spacing, and
* sub-pixel rounding. Motivated by #9144, where the zh-CN shortcut-capture
* placeholder advanced 15px/char on a machine whose CJK fallback font renders
* wider than 1em, so a `label.length + 2em` width clipped the last glyph.
*
* The width starts at `fallback` so callers render a sane size before the
* first measurement lands (and in environments without real layout, e.g.
* happy-dom). Zero-width measurements are ignored: they mean "not laid out
* yet" (or `display: none` ancestry), never "the text is zero pixels wide",
* and must not clobber a usable width. `revalidateSources` covers content
* changes that resize the element without moving it (e.g. a locale swap)
* for environments where ResizeObserver is unavailable.
*/
export function useMeasuredWidth(targetRef: Ref<HTMLElement | null>, fallback = 0, revalidateSources: WatchSource[] = []): Ref<number> {
const width = ref(fallback);
let resizeObserver: ResizeObserver | undefined;
function measure() {
const element = targetRef.value;
if (!element) {
return;
}
const measured = element.getBoundingClientRect().width;
if (measured > 0) {
width.value = measured;
}
}
if (revalidateSources.length > 0) {
watch(revalidateSources, () => {
void nextTick(measure);
});
}
watch(
targetRef,
(element) => {
resizeObserver?.disconnect();
resizeObserver = undefined;
if (element && typeof ResizeObserver !== "undefined") {
resizeObserver = new ResizeObserver(measure);
resizeObserver.observe(element);
}
void nextTick(measure);
},
{ flush: "post" },
);
onUnmounted(() => {
resizeObserver?.disconnect();
resizeObserver = undefined;
});
return width;
}