import { assert } from '@ember/debug'; import { autoUpdate, computePosition, flip, hide, offset, shift } from '@floating-ui/dom'; import { modifier as eModifier } from 'ember-modifier'; import { exposeMetadata } from './middleware.ts'; import type { FlipOptions, HideOptions, Middleware, OffsetOptions, Placement, ShiftOptions, Strategy, } from '@floating-ui/dom'; export interface Signature { /** * */ Element: HTMLElement; Args: { Positional: [ /** * What do use as the reference element. * Can be a selector or element instance. * * Example: * ```gjs * import { anchorTo } from 'ember-primitives/floating-ui'; * * * ``` */ referenceElement: string | HTMLElement | SVGElement, ]; Named: { /** * This is the type of CSS position property to use. * By default this is 'fixed', but can also be 'absolute'. * * See: [The strategy docs](https://floating-ui.com/docs/computePosition#strategy) */ strategy?: Strategy; /** * Options to pass to the [offset middleware](https://floating-ui.com/docs/offset) */ offsetOptions?: OffsetOptions; /** * Where to place the floating element relative to its reference element. * The default is 'bottom'. * * See: [The placement docs](https://floating-ui.com/docs/computePosition#placement) */ placement?: Placement; /** * Options to pass to the [flip middleware](https://floating-ui.com/docs/flip) */ flipOptions?: FlipOptions; /** * Options to pass to the [shift middleware](https://floating-ui.com/docs/shift) */ shiftOptions?: ShiftOptions; /** * Options to pass to the [hide middleware](https://floating-ui.com/docs/hide) */ hideOptions?: HideOptions; /** * Additional middleware to pass to FloatingUI. * * See: [The middleware docs](https://floating-ui.com/docs/middleware) */ middleware?: Middleware[]; /** * A callback for when data changes about the position / placement / etc * of the floating element. */ setData?: Middleware['fn']; }; }; } /** * A modifier to apply to the _floating_ element. * This is what will anchor to the reference element. * * Example * ```gjs * import { anchorTo } from 'ember-primitives/floating-ui'; * * * ``` */ export const anchorTo = eModifier( ( floatingElement, [_referenceElement], { strategy = 'fixed', offsetOptions = 0, placement = 'bottom', flipOptions, shiftOptions, middleware = [], setData, } ) => { const referenceElement: null | HTMLElement | SVGElement = typeof _referenceElement === 'string' ? document.querySelector(_referenceElement) : _referenceElement; assert( 'no reference element defined', referenceElement instanceof HTMLElement || referenceElement instanceof SVGElement ); assert( 'no floating element defined', floatingElement instanceof HTMLElement || _referenceElement instanceof SVGElement ); assert( 'reference and floating elements cannot be the same element', floatingElement !== _referenceElement ); assert('@middleware must be an array of one or more objects', Array.isArray(middleware)); Object.assign(floatingElement.style, { position: strategy, top: '0', left: '0', }); const update = async () => { const { middlewareData, x, y } = await computePosition(referenceElement, floatingElement, { middleware: [ offset(offsetOptions), flip(flipOptions), shift(shiftOptions), ...middleware, hide({ strategy: 'referenceHidden' }), hide({ strategy: 'escaped' }), exposeMetadata(), ], placement, strategy, }); const referenceHidden = middlewareData.hide?.referenceHidden; Object.assign(floatingElement.style, { top: `${y}px`, left: `${x}px`, margin: 0, visibility: referenceHidden ? 'hidden' : 'visible', }); // eslint-disable-next-line @typescript-eslint/no-unsafe-argument void setData?.(middlewareData['metadata']); }; void update(); // eslint-disable-next-line @typescript-eslint/no-misused-promises const cleanup = autoUpdate(referenceElement, floatingElement, update); /** * in the function-modifier manager, teardown of the previous modifier * occurs before setup of the next * https://github.com/ember-modifier/ember-modifier/blob/main/ember-modifier/src/-private/function-based/modifier-manager.ts#L58 */ return cleanup; } );