В современных интерфейсах позиционирование плавающих элементов (tooltip, dropdown, popover) требует гибкой, но при этом строго контролируемой конфигурации. Floating UI изначально проектировалась с учётом интеграции с TypeScript, что позволяет обеспечить типобезопасность на всех уровнях: от базовых параметров позиционирования до кастомных middleware.
Типобезопасная конфигурация решает несколько задач:
computePositionКлючевая функция библиотеки — computePosition. Она
принимает два DOM-элемента и объект конфигурации:
computePosition(referenceEl, floatingEl, {
placement: 'bottom-start',
middleware: [],
});
Тип конфигурации определяется как:
interface ComputePositionConfig {
placement?: Placement;
strategy?: Strategy;
middleware?: Middleware[];
}
type Placement =
| 'top'
| 'bottom'
| 'left'
| 'right'
| 'top-start'
| 'top-end'
| ...
Тип Placement ограничивает допустимые строки. Попытка
передать, например, "bottom-middle" приведёт к ошибке
компиляции.
Middleware — расширяемый механизм, который влияет на позиционирование. Каждый middleware строго типизирован:
interface Middleware {
name: string;
options?: any;
fn: (state: MiddlewareState) => MiddlewareReturn;
}
Однако при использовании готовых middleware типы уже заданы:
import { offset, flip, shift } from '@floating-ui/dom';
computePosition(el1, el2, {
middleware: [
offset(10),
flip(),
shift({ padding: 8 }),
],
});
offset("10"); // Ошибка: ожидается number
shift({ padding: "8px" }); // Ошибка: padding должен быть number
Floating UI позволяет создавать собственные middleware с полной типизацией:
import type { Middleware, MiddlewareState } from '@floating-ui/dom';
function customMiddleware(): Middleware {
return {
name: 'custom',
fn(state: MiddlewareState) {
return {
x: state.x + 10,
y: state.y,
data: {
customValue: true,
},
};
},
};
}
dataМожно явно описывать возвращаемые данные:
type CustomData = {
customValue: boolean;
};
function customMiddleware(): Middleware<CustomData> {
return {
name: 'custom',
fn(state) {
return {
data: {
customValue: true,
},
};
},
};
}
Теперь middlewareData.custom будет строго
типизирован.
middlewareDataРезультат работы всех middleware доступен в
middlewareData:
const { middlewareData } = await computePosition(...);
Тип:
type MiddlewareData = {
[key: string]: any;
};
Для строгой типизации используется объединение:
type MyMiddlewareData = {
offset?: { x: number; y: number };
custom?: { customValue: boolean };
};
Применение:
const data = middlewareData as MyMiddlewareData;
data.custom?.customValue; // тип boolean
Floating UI поддерживает два варианта стратегии:
type Strategy = 'absolute' | 'fixed';
Пример:
computePosition(ref, floating, {
strategy: 'fixed',
});
Попытка передать "sticky" приведёт к ошибке.
autoUpdateФункция autoUpdate отслеживает изменения DOM и
автоматически обновляет позицию:
autoUpdate(reference, floating, update, options);
Тип опций:
interface AutoUpdateOptions {
ancestorScroll?: boolean;
ancestorResize?: boolean;
elementResize?: boolean;
layoutShift?: boolean;
animationFrame?: boolean;
}
Пример:
autoUpdate(ref, floating, update, {
ancestorScroll: true,
animationFrame: false,
});
Ошибочные значения:
ancestorScroll: "yes"; // ошибка
При создании обёрток или UI-компонентов полезно описывать собственные типы конфигурации:
type TooltipConfig = {
placement?: Placement;
offset?: number;
hasArrow?: boolean;
};
Использование:
function createTooltip(config: TooltipConfig) {
return computePosition(ref, floating, {
placement: config.placement ?? 'top',
middleware: [
offset(config.offset ?? 8),
],
});
}
Теперь любые ошибки в конфигурации выявляются заранее.
as constДля повышения точности типов используется as const:
const config = {
placement: 'bottom-start',
} as const;
Без as const:
placement: string
С as const:
placement: 'bottom-start'
Это важно для строгого сопоставления с типом
Placement.
Иногда требуется сузить допустимые значения:
type VerticalPlacement = 'top' | 'bottom';
function setPlacement(p: VerticalPlacement) {}
Попытка:
setPlacement('left'); // ошибка
Такой подход полезен при создании API компонентов.
В React-обёртках Floating UI используется строгая типизация props:
type FloatingProps = {
placement?: Placement;
middleware?: Middleware[];
};
Пример:
<Floating placement="top-start" />
IDE автоматически подсказывает допустимые значения.
Floating UI строится на принципах:
Это позволяет:
Без TypeScript возможны проблемы:
placement: 'bottommiddle' // опечатка
offset: '10px' // неверный тип
middleware: [flip, shift] // забыли вызвать функции
Типобезопасность устраняет такие ошибки ещё до запуска кода.
Оптимальный подход:
as const для литераловFloating UI демонстрирует подход, при котором библиотека не просто предоставляет API, а формирует строгую типовую модель:
Такой подход особенно важен в крупных проектах, где ошибки конфигурации приводят к трудноуловимым багам в UI.