@octanejs/floating-ui 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/floating-ui",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "engines": {
@@ -42,14 +42,14 @@
42
42
  "tabbable": "^6.5.0"
43
43
  },
44
44
  "peerDependencies": {
45
- "octane": "0.1.9"
45
+ "octane": "0.1.11"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@floating-ui/react": "0.27.19",
49
49
  "react": "^19.2.0",
50
50
  "react-dom": "^19.2.0",
51
51
  "vitest": "^4.1.9",
52
- "octane": "0.1.9"
52
+ "octane": "0.1.11"
53
53
  },
54
54
  "scripts": {
55
55
  "test": "vitest run"
package/src/Composite.ts CHANGED
@@ -4,6 +4,9 @@
4
4
  // `render` prop (a function, an element to clone, or a default <div>); octane has no
5
5
  // cloneElement, so it's implemented locally over createElement.
6
6
  import { createContext, createElement, useContext, useMemo, useRef, useState } from 'octane';
7
+ import type { OctaneNode } from 'octane';
8
+ import type { OctaneElement } from 'octane/jsx-runtime';
9
+ import type { Dimensions } from '@floating-ui/dom';
7
10
 
8
11
  import { FloatingList, useListItem } from './FloatingList';
9
12
  import { S } from './internal';
@@ -19,7 +22,9 @@ import {
19
22
  isIndexOutOfListBounds,
20
23
  isListIndexDisabled,
21
24
  useEffectEvent,
25
+ type DisabledIndices,
22
26
  } from './utils';
27
+ import type { HTMLProps, MutableRefObject, RefCallback } from './types';
23
28
 
24
29
  const ARROW_LEFT = 'ArrowLeft';
25
30
  const ARROW_RIGHT = 'ArrowRight';
@@ -29,13 +34,22 @@ const horizontalKeys = [ARROW_LEFT, ARROW_RIGHT];
29
34
  const verticalKeys = [ARROW_UP, ARROW_DOWN];
30
35
  const allKeys = [...horizontalKeys, ...verticalKeys];
31
36
 
32
- function cloneElement(el: any, props: any): any {
37
+ // Upstream's (unexported) `RenderProp`, in octane terms: an element descriptor
38
+ // to clone, or a factory receiving the computed HTML props.
39
+ type RenderProp = OctaneElement | ((props: HTMLProps<HTMLElement>) => OctaneNode);
40
+
41
+ type CompositeRef = MutableRefObject<HTMLElement | null> | RefCallback<HTMLElement> | null;
42
+
43
+ function cloneElement(el: OctaneElement, props: Record<string, unknown>): OctaneNode {
33
44
  return createElement(el.type, { ...el.props, ...props });
34
45
  }
35
46
 
36
- function renderJsx(render: any, computedProps: any): any {
47
+ function renderJsx(
48
+ render: RenderProp | undefined,
49
+ computedProps: Record<string, unknown>,
50
+ ): OctaneNode {
37
51
  if (typeof render === 'function') {
38
- return render(computedProps);
52
+ return render(computedProps as HTMLProps<HTMLElement>);
39
53
  }
40
54
  if (render) {
41
55
  return cloneElement(render, computedProps);
@@ -43,12 +57,79 @@ function renderJsx(render: any, computedProps: any): any {
43
57
  return createElement('div', { ...computedProps });
44
58
  }
45
59
 
46
- export const CompositeContext = createContext<any>({
60
+ interface CompositeContextValue {
61
+ activeIndex: number;
62
+ onNavigate(index: number): void;
63
+ }
64
+
65
+ export const CompositeContext = createContext<CompositeContextValue>({
47
66
  activeIndex: 0,
48
67
  onNavigate: () => {},
49
68
  });
50
69
 
51
- export function Composite(props: any): any {
70
+ export interface CompositeProps {
71
+ /**
72
+ * Determines the element to render.
73
+ * @example
74
+ * ```jsx
75
+ * <Composite render={<ul />} />
76
+ * <Composite render={(htmlProps) => <ul {...htmlProps} />} />
77
+ * ```
78
+ */
79
+ render?: RenderProp;
80
+ /**
81
+ * Determines the orientation of the composite.
82
+ */
83
+ orientation?: 'horizontal' | 'vertical' | 'both';
84
+ /**
85
+ * Determines whether focus should loop around when navigating past the first
86
+ * or last item.
87
+ */
88
+ loop?: boolean;
89
+ /**
90
+ * Whether the direction of the composite’s navigation is in RTL layout.
91
+ */
92
+ rtl?: boolean;
93
+ /**
94
+ * Determines the number of columns there are in the composite
95
+ * (i.e. it’s a grid).
96
+ */
97
+ cols?: number;
98
+ /**
99
+ * Determines which items are disabled. The `disabled` or `aria-disabled`
100
+ * attributes are used by default.
101
+ */
102
+ disabledIndices?: DisabledIndices;
103
+ /**
104
+ * Determines which item is active. Used to externally control the active
105
+ * item.
106
+ */
107
+ activeIndex?: number;
108
+ /**
109
+ * Called when the user navigates to a new item. Used to externally control
110
+ * the active item.
111
+ */
112
+ onNavigate?(index: number): void;
113
+ /**
114
+ * Only for `cols > 1`, specify sizes for grid items.
115
+ * `{ width: 2, height: 2 }` means an item is 2 columns wide and 2 rows tall.
116
+ */
117
+ itemSizes?: Dimensions[];
118
+ /**
119
+ * Only relevant for `cols > 1` and items with different sizes, specify if
120
+ * the grid is dense (as defined in the CSS spec for grid-auto-flow).
121
+ */
122
+ dense?: boolean;
123
+ }
124
+
125
+ /**
126
+ * Creates a single tab stop whose items are navigated by arrow keys, which
127
+ * provides list navigation outside of floating element contexts.
128
+ * @see https://floating-ui.com/docs/Composite
129
+ */
130
+ export function Composite(
131
+ props: CompositeProps & HTMLProps<HTMLElement> & { ref?: CompositeRef },
132
+ ): OctaneNode {
52
133
  const render = props.render;
53
134
  const orientation = props.orientation ?? 'both';
54
135
  const loop = props.loop ?? true;
@@ -81,16 +162,16 @@ export function Composite(props: any): any {
81
162
  externalSetActiveIndex != null ? externalSetActiveIndex : internalSetActiveIndex,
82
163
  S('Composite:nav'),
83
164
  );
84
- const elementsRef = useRef<any[]>([], S('Composite:els'));
85
- const renderElementProps = render && typeof render !== 'function' ? render.props : {};
86
- const contextValue = useMemo(
165
+ const elementsRef = useRef<Array<HTMLElement | null>>([], S('Composite:els'));
166
+ const renderElementProps: any = render && typeof render !== 'function' ? render.props : {};
167
+ const contextValue = useMemo<CompositeContextValue>(
87
168
  () => ({ activeIndex, onNavigate }),
88
169
  [activeIndex, onNavigate],
89
170
  S('Composite:ctx'),
90
171
  );
91
172
  const isGrid = cols > 1;
92
173
 
93
- function handleKeyDown(event: any) {
174
+ function handleKeyDown(event: KeyboardEvent) {
94
175
  if (!allKeys.includes(event.key)) return;
95
176
  let nextIndex = activeIndex;
96
177
  const minIndex = getMinListIndex(elementsRef, disabledIndices);
@@ -105,7 +186,7 @@ export function Composite(props: any): any {
105
186
  const minGridIndex = cellMap.findIndex(
106
187
  (index) => index != null && !isListIndexDisabled(elementsRef, index, disabledIndices),
107
188
  );
108
- const maxGridIndex = cellMap.reduce(
189
+ const maxGridIndex = cellMap.reduce<number>(
109
190
  (foundIndex, index, cellIndex) =>
110
191
  index != null && !isListIndexDisabled(elementsRef, index, disabledIndices)
111
192
  ? cellIndex
@@ -129,7 +210,7 @@ export function Composite(props: any): any {
129
210
  disabledIndices: getGridCellIndices(
130
211
  [
131
212
  ...((typeof disabledIndices !== 'function' ? disabledIndices : null) ||
132
- elementsRef.current.map((_: any, index: number) =>
213
+ elementsRef.current.map((_, index) =>
133
214
  isListIndexDisabled(elementsRef, index, disabledIndices) ? index : undefined,
134
215
  )),
135
216
  undefined,
@@ -156,19 +237,19 @@ export function Composite(props: any): any {
156
237
  horizontal: [horizontalEndKey],
157
238
  vertical: [ARROW_DOWN],
158
239
  both: [horizontalEndKey, ARROW_DOWN],
159
- }[orientation as 'horizontal' | 'vertical' | 'both'];
240
+ }[orientation];
160
241
  const toStartKeys = {
161
242
  horizontal: [horizontalStartKey],
162
243
  vertical: [ARROW_UP],
163
244
  both: [horizontalStartKey, ARROW_UP],
164
- }[orientation as 'horizontal' | 'vertical' | 'both'];
245
+ }[orientation];
165
246
  const preventedKeys = isGrid
166
247
  ? allKeys
167
248
  : {
168
249
  horizontal: horizontalKeys,
169
250
  vertical: verticalKeys,
170
251
  both: allKeys,
171
- }[orientation as 'horizontal' | 'vertical' | 'both'];
252
+ }[orientation];
172
253
  if (nextIndex === activeIndex && [...toEndKeys, ...toStartKeys].includes(event.key)) {
173
254
  if (loop && nextIndex === maxIndex && toEndKeys.includes(event.key)) {
174
255
  nextIndex = minIndex;
@@ -197,8 +278,8 @@ export function Composite(props: any): any {
197
278
  ...renderElementProps,
198
279
  ref: forwardedRef,
199
280
  'aria-orientation': orientation === 'both' ? undefined : orientation,
200
- onKeyDown(e: any) {
201
- domProps.onKeyDown?.(e);
281
+ onKeyDown(e: KeyboardEvent) {
282
+ domProps.onKeyDown?.(e as KeyboardEvent & { currentTarget: HTMLElement & EventTarget });
202
283
  renderElementProps.onKeyDown?.(e);
203
284
  handleKeyDown(e);
204
285
  },
@@ -213,11 +294,28 @@ export function Composite(props: any): any {
213
294
  });
214
295
  }
215
296
 
216
- export function CompositeItem(props: any): any {
297
+ export interface CompositeItemProps {
298
+ /**
299
+ * Determines the element to render.
300
+ * @example
301
+ * ```jsx
302
+ * <CompositeItem render={<li />} />
303
+ * <CompositeItem render={(htmlProps) => <li {...htmlProps} />} />
304
+ * ```
305
+ */
306
+ render?: RenderProp;
307
+ }
308
+
309
+ /**
310
+ * @see https://floating-ui.com/docs/Composite
311
+ */
312
+ export function CompositeItem(
313
+ props: CompositeItemProps & HTMLProps<HTMLElement> & { ref?: CompositeRef },
314
+ ): OctaneNode {
217
315
  const render = props.render;
218
316
  const forwardedRef = props.ref;
219
317
  const { render: _r, ref: _ref, ...domProps } = props;
220
- const renderElementProps = render && typeof render !== 'function' ? render.props : {};
318
+ const renderElementProps: any = render && typeof render !== 'function' ? render.props : {};
221
319
  const { activeIndex, onNavigate } = useContext(CompositeContext);
222
320
  const { ref, index } = useListItem(S('CompositeItem:listItem'));
223
321
  const mergedRef = useMergeRefs(
@@ -231,8 +329,8 @@ export function CompositeItem(props: any): any {
231
329
  ref: mergedRef,
232
330
  tabIndex: isActive ? 0 : -1,
233
331
  'data-active': isActive ? '' : undefined,
234
- onFocus(e: any) {
235
- domProps.onFocus?.(e);
332
+ onFocus(e: FocusEvent) {
333
+ domProps.onFocus?.(e as FocusEvent & { currentTarget: HTMLElement & EventTarget });
236
334
  renderElementProps.onFocus?.(e);
237
335
  onNavigate(index);
238
336
  },
@@ -5,12 +5,69 @@
5
5
  // verbatim, so SVG presentation attributes are written kebab-case here.
6
6
  import { getComputedStyle } from '@floating-ui/utils/dom';
7
7
  import { createElement, useState } from 'octane';
8
+ import type { OctaneNode } from 'octane';
8
9
 
9
10
  import { S } from './internal';
10
11
  import { useId } from './useId';
11
- import { useModernLayoutEffect } from './utils';
12
+ import { useModernLayoutEffect, type CSSProperties } from './utils';
13
+ import type { FloatingContext, MutableRefObject, RefCallback } from './types';
12
14
 
13
- export function FloatingArrow(props: any): any {
15
+ // Upstream extends `React.ComponentPropsWithRef<'svg'>`. The port declares its
16
+ // OWN props strictly and leaves the remaining SVG attributes open (`unknown`
17
+ // index) so prop bags typed against other libraries' SVG surfaces (e.g.
18
+ // React's synthetic-event `SVGProps`) remain spreadable; the rest is forwarded
19
+ // verbatim to the `<svg>` element.
20
+ export interface FloatingArrowProps {
21
+ /**
22
+ * The floating context.
23
+ */
24
+ context: Omit<FloatingContext, 'refs'> & { refs: any };
25
+ /**
26
+ * Width of the arrow.
27
+ * @default 14
28
+ */
29
+ width?: number;
30
+ /**
31
+ * Height of the arrow.
32
+ * @default 7
33
+ */
34
+ height?: number;
35
+ /**
36
+ * The corner radius (rounding) of the arrow tip.
37
+ * @default 0 (sharp)
38
+ */
39
+ tipRadius?: number;
40
+ /**
41
+ * Forces a static offset over dynamic positioning under a certain condition.
42
+ * If the shift() middleware causes the popover to shift, this value will be
43
+ * ignored.
44
+ */
45
+ staticOffset?: string | number | null;
46
+ /**
47
+ * Custom path string.
48
+ */
49
+ d?: string;
50
+ /**
51
+ * Stroke (border) color of the arrow.
52
+ */
53
+ stroke?: string;
54
+ /**
55
+ * Stroke (border) width of the arrow.
56
+ */
57
+ strokeWidth?: number;
58
+ /** Object-form styles merged over the arrow's own positioning styles. */
59
+ style?: CSSProperties;
60
+ /** Ref-as-prop (octane has no forwardRef). */
61
+ ref?: MutableRefObject<SVGSVGElement | null> | RefCallback<SVGSVGElement> | null;
62
+ /** Remaining SVG attributes/handlers, forwarded to the `<svg>` element. */
63
+ [key: string]: unknown;
64
+ }
65
+
66
+ /**
67
+ * Renders a pointing arrow triangle.
68
+ * @see https://floating-ui.com/docs/FloatingArrow
69
+ */
70
+ export function FloatingArrow(props: FloatingArrowProps): OctaneNode {
14
71
  const ref = props.ref;
15
72
  const context = props.context;
16
73
  const placement = context.placement;
@@ -88,13 +145,13 @@ export function FloatingArrow(props: any): any {
88
145
  (' L' + (width - svgX) + ',' + (height - svgY)) +
89
146
  (' Q' + width / 2 + ',' + height + ' ' + svgX + ',' + (height - svgY)) +
90
147
  ' Z';
91
- const rotation: any = (
148
+ const rotation = (
92
149
  {
93
150
  top: isCustomShape ? 'rotate(180deg)' : '',
94
151
  left: isCustomShape ? 'rotate(90deg)' : 'rotate(-90deg)',
95
152
  bottom: isCustomShape ? '' : 'rotate(180deg)',
96
153
  right: isCustomShape ? 'rotate(-90deg)' : 'rotate(90deg)',
97
- } as any
154
+ } as Record<string, string>
98
155
  )[side];
99
156
 
100
157
  return createElement(
@@ -7,6 +7,7 @@
7
7
  import { getNodeName, isHTMLElement, isShadowRoot } from '@floating-ui/utils/dom';
8
8
  import { focusable, isTabbable, tabbable } from 'tabbable';
9
9
  import { createElement, useEffect, useMemo, useRef } from 'octane';
10
+ import type { OctaneNode } from 'octane';
10
11
 
11
12
  import { FocusGuard, usePortalContext } from './FloatingPortal';
12
13
  import { S } from './internal';
@@ -33,9 +34,11 @@ import {
33
34
  useEffectEvent,
34
35
  useLatestRef,
35
36
  useModernLayoutEffect,
37
+ type CSSProperties,
36
38
  } from './utils';
39
+ import type { FloatingRootContext, HTMLProps, MutableRefObject, RefCallback } from './types';
37
40
 
38
- const HIDDEN_STYLES: any = {
41
+ const HIDDEN_STYLES: CSSProperties = {
39
42
  border: 0,
40
43
  clip: 'rect(0 0 0 0)',
41
44
  height: '1px',
@@ -63,7 +66,7 @@ function getCounterMap(control: any) {
63
66
  let uncontrolledElementsSet = new WeakSet<any>();
64
67
  let markerMap: any = {};
65
68
  let lockCount = 0;
66
- export const supportsInert = () =>
69
+ export const supportsInert = (): boolean =>
67
70
  typeof HTMLElement !== 'undefined' && 'inert' in HTMLElement.prototype;
68
71
  function unwrapHost(node: any): any {
69
72
  if (!node) {
@@ -180,13 +183,13 @@ function markOthers(avoidElements: any[], ariaHidden = false, inert = false): ()
180
183
 
181
184
  // ── previously-focused element tracking (for return focus) ───────────────────
182
185
  const LIST_LIMIT = 20;
183
- let previouslyFocusedElements: any[] = [];
186
+ let previouslyFocusedElements: Array<WeakRef<Element>> = [];
184
187
  function clearDisconnectedPreviouslyFocusedElements() {
185
188
  previouslyFocusedElements = previouslyFocusedElements.filter(
186
189
  (elementRef) => elementRef.deref()?.isConnected,
187
190
  );
188
191
  }
189
- function addPreviouslyFocusedElement(element: any) {
192
+ function addPreviouslyFocusedElement(element: Element | null | undefined) {
190
193
  clearDisconnectedPreviouslyFocusedElements();
191
194
  if (element && getNodeName(element) !== 'body') {
192
195
  previouslyFocusedElements.push(new WeakRef(element));
@@ -195,18 +198,21 @@ function addPreviouslyFocusedElement(element: any) {
195
198
  }
196
199
  }
197
200
  }
198
- function getPreviouslyFocusedElement() {
201
+ function getPreviouslyFocusedElement(): Element | undefined {
199
202
  clearDisconnectedPreviouslyFocusedElements();
200
203
  return previouslyFocusedElements[previouslyFocusedElements.length - 1]?.deref();
201
204
  }
202
- function getFirstTabbableElement(container: any) {
205
+ function getFirstTabbableElement(container: Element) {
203
206
  const tabbableOptions = getTabbableOptions();
204
207
  if (isTabbable(container, tabbableOptions)) {
205
208
  return container;
206
209
  }
207
210
  return tabbable(container, tabbableOptions)[0] || container;
208
211
  }
209
- function handleTabIndex(floatingFocusElement: any, orderRef: any) {
212
+ function handleTabIndex(
213
+ floatingFocusElement: HTMLElement,
214
+ orderRef: MutableRefObject<Array<'reference' | 'floating' | 'content'>>,
215
+ ) {
210
216
  if (
211
217
  !orderRef.current.includes('floating') &&
212
218
  !floatingFocusElement.getAttribute('role')?.includes('dialog')
@@ -215,7 +221,7 @@ function handleTabIndex(floatingFocusElement: any, orderRef: any) {
215
221
  }
216
222
  const options = getTabbableOptions();
217
223
  const focusableElements = focusable(floatingFocusElement, options);
218
- const tabbableContent = focusableElements.filter((element: any) => {
224
+ const tabbableContent = focusableElements.filter((element) => {
219
225
  const dataTabIndex = element.getAttribute('data-tabindex') || '';
220
226
  return (
221
227
  isTabbable(element, options) ||
@@ -237,10 +243,13 @@ function handleTabIndex(floatingFocusElement: any, orderRef: any) {
237
243
  }
238
244
  }
239
245
 
240
- function useLiteMergeRefs(refs: any[], slot: symbol): any {
246
+ function useLiteMergeRefs<T>(
247
+ refs: Array<MutableRefObject<T | null> | undefined>,
248
+ slot: symbol,
249
+ ): (value: T | null) => void {
241
250
  return useMemo(
242
251
  () => {
243
- return (value: any) => {
252
+ return (value: T | null) => {
244
253
  refs.forEach((ref) => {
245
254
  if (ref) {
246
255
  ref.current = value;
@@ -253,7 +262,11 @@ function useLiteMergeRefs(refs: any[], slot: symbol): any {
253
262
  );
254
263
  }
255
264
 
256
- export function VisuallyHiddenDismiss(props: any): any {
265
+ export function VisuallyHiddenDismiss(
266
+ props: HTMLProps<HTMLButtonElement> & {
267
+ ref?: MutableRefObject<HTMLButtonElement | null> | RefCallback<HTMLButtonElement> | null;
268
+ },
269
+ ): OctaneNode {
257
270
  return createElement('button', {
258
271
  ...props,
259
272
  type: 'button',
@@ -263,7 +276,95 @@ export function VisuallyHiddenDismiss(props: any): any {
263
276
  });
264
277
  }
265
278
 
266
- export function FloatingFocusManager(props: any): any {
279
+ export interface FloatingFocusManagerProps {
280
+ children: OctaneNode;
281
+ /**
282
+ * The floating context returned from `useFloatingRootContext` (a full
283
+ * `FloatingContext` from `useFloating` is structurally compatible).
284
+ */
285
+ context: FloatingRootContext;
286
+ /**
287
+ * Whether or not the focus manager should be disabled. Useful to delay focus
288
+ * management until after a transition completes or some other conditional
289
+ * state.
290
+ * @default false
291
+ */
292
+ disabled?: boolean;
293
+ /**
294
+ * The order in which focus cycles.
295
+ * @default ['content']
296
+ */
297
+ order?: Array<'reference' | 'floating' | 'content'>;
298
+ /**
299
+ * Which element to initially focus. Can be either a number (tabbable index as
300
+ * specified by the `order`) or a ref.
301
+ * @default 0
302
+ */
303
+ initialFocus?: number | MutableRefObject<HTMLElement | null>;
304
+ /**
305
+ * Determines if the focus guards are rendered. If not, focus can escape into
306
+ * the address bar/console/browser UI, like in native dialogs.
307
+ * @default true
308
+ */
309
+ guards?: boolean;
310
+ /**
311
+ * Determines if focus should be returned to the reference element once the
312
+ * floating element closes/unmounts (or if that is not available, the
313
+ * previously focused element). This prop is ignored if the floating element
314
+ * lost focus.
315
+ * It can be also set to a ref to explicitly control the element to return focus to.
316
+ * @default true
317
+ */
318
+ returnFocus?: boolean | MutableRefObject<HTMLElement | null>;
319
+ /**
320
+ * Determines if focus should be restored to the nearest tabbable element if
321
+ * focus inside the floating element is lost (such as due to the removal of
322
+ * the currently focused element from the DOM).
323
+ * @default false
324
+ */
325
+ restoreFocus?: boolean;
326
+ /**
327
+ * Determines if focus is “modal”, meaning focus is fully trapped inside the
328
+ * floating element and outside content cannot be accessed. This includes
329
+ * screen reader virtual cursors.
330
+ * @default true
331
+ */
332
+ modal?: boolean;
333
+ /**
334
+ * If your focus management is modal and there is no explicit close button
335
+ * available, you can use this prop to render a visually-hidden dismiss
336
+ * button at the start and end of the floating element. This allows
337
+ * touch-based screen readers to escape the floating element due to lack of
338
+ * an `esc` key.
339
+ * @default undefined
340
+ */
341
+ visuallyHiddenDismiss?: boolean | string;
342
+ /**
343
+ * Determines whether `focusout` event listeners that control whether the
344
+ * floating element should be closed if the focus moves outside of it are
345
+ * attached to the reference and floating elements. This affects non-modal
346
+ * focus management.
347
+ * @default true
348
+ */
349
+ closeOnFocusOut?: boolean;
350
+ /**
351
+ * Determines whether outside elements are `inert` when `modal` is enabled.
352
+ * This enables pointer modality without a backdrop.
353
+ * @default false
354
+ */
355
+ outsideElementsInert?: boolean;
356
+ /**
357
+ * Returns a list of elements that should be considered part of the
358
+ * floating element.
359
+ */
360
+ getInsideElements?: () => Element[];
361
+ }
362
+
363
+ /**
364
+ * Provides focus management for the floating element.
365
+ * @see https://floating-ui.com/docs/FloatingFocusManager
366
+ */
367
+ export function FloatingFocusManager(props: FloatingFocusManagerProps): OctaneNode {
267
368
  const context = props.context;
268
369
  const children = props.children;
269
370
  const disabled = props.disabled ?? false;
@@ -301,8 +402,8 @@ export function FloatingFocusManager(props: any): any {
301
402
  const returnFocusRef = useLatestRef(returnFocus, S('FFM:return'));
302
403
  const tree = useFloatingTree();
303
404
  const portalContext = usePortalContext();
304
- const startDismissButtonRef = useRef<any>(null, S('FFM:startDismiss'));
305
- const endDismissButtonRef = useRef<any>(null, S('FFM:endDismiss'));
405
+ const startDismissButtonRef = useRef<HTMLButtonElement | null>(null, S('FFM:startDismiss'));
406
+ const endDismissButtonRef = useRef<HTMLButtonElement | null>(null, S('FFM:endDismiss'));
306
407
  const preventReturnFocusRef = useRef(false, S('FFM:preventReturn'));
307
408
  const isPointerDownRef = useRef(false, S('FFM:pointerDown'));
308
409
  const tabbableIndexRef = useRef(-1, S('FFM:tabbableIndex'));
@@ -313,10 +414,10 @@ export function FloatingFocusManager(props: any): any {
313
414
  const getTabbableContent = useEffectEvent((container = floatingFocusElement) => {
314
415
  return container ? tabbable(container, getTabbableOptions()) : [];
315
416
  }, S('FFM:getContent'));
316
- const getTabbableElements = useEffectEvent((container?: any) => {
417
+ const getTabbableElements = useEffectEvent((container?: HTMLElement) => {
317
418
  const content = getTabbableContent(container);
318
419
  return orderRef.current
319
- .map((type: string) => {
420
+ .map((type) => {
320
421
  if (domReference && type === 'reference') {
321
422
  return domReference;
322
423
  }
@@ -333,7 +434,7 @@ export function FloatingFocusManager(props: any): any {
333
434
  () => {
334
435
  if (disabled) return;
335
436
  if (!modal) return;
336
- function onKeyDown(event: any) {
437
+ function onKeyDown(event: KeyboardEvent) {
337
438
  if (event.key === 'Tab') {
338
439
  if (
339
440
  contains(
@@ -388,7 +489,7 @@ export function FloatingFocusManager(props: any): any {
388
489
  () => {
389
490
  if (disabled) return;
390
491
  if (!floating) return;
391
- function handleFocusIn(event: any) {
492
+ function handleFocusIn(event: FocusEvent) {
392
493
  const target = getTarget(event);
393
494
  const tabbableContent = getTabbableContent();
394
495
  const tabbableIndex = tabbableContent.indexOf(target as any);
@@ -415,8 +516,8 @@ export function FloatingFocusManager(props: any): any {
415
516
  isPointerDownRef.current = false;
416
517
  });
417
518
  }
418
- function handleFocusOutside(event: any) {
419
- const relatedTarget = event.relatedTarget;
519
+ function handleFocusOutside(event: FocusEvent) {
520
+ const relatedTarget = event.relatedTarget as Element | null;
420
521
  const currentTarget = event.currentTarget;
421
522
  const target = getTarget(event);
422
523
  queueMicrotask(() => {
@@ -526,8 +627,8 @@ export function FloatingFocusManager(props: any): any {
526
627
  S('FFM:e:focusout'),
527
628
  );
528
629
 
529
- const beforeGuardRef = useRef<any>(null, S('FFM:beforeGuard'));
530
- const afterGuardRef = useRef<any>(null, S('FFM:afterGuard'));
630
+ const beforeGuardRef = useRef<HTMLSpanElement | null>(null, S('FFM:beforeGuard'));
631
+ const afterGuardRef = useRef<HTMLSpanElement | null>(null, S('FFM:afterGuard'));
531
632
  const mergedBeforeGuardRef = useLiteMergeRefs(
532
633
  [beforeGuardRef, portalContext?.beforeInsideRef],
533
634
  S('FFM:mergeBefore'),
@@ -743,13 +844,13 @@ export function FloatingFocusManager(props: any): any {
743
844
  S('FFM:e:tabIndex'),
744
845
  );
745
846
 
746
- function renderDismissButton(location: string) {
847
+ function renderDismissButton(location: 'start' | 'end') {
747
848
  if (disabled || !visuallyHiddenDismiss || !modal) {
748
849
  return null;
749
850
  }
750
851
  return createElement(VisuallyHiddenDismiss, {
751
852
  ref: location === 'start' ? startDismissButtonRef : endDismissButtonRef,
752
- onClick: (event: any) => onOpenChange(false, event),
853
+ onClick: (event: MouseEvent) => onOpenChange(false, event),
753
854
  children: typeof visuallyHiddenDismiss === 'string' ? visuallyHiddenDismiss : 'Dismiss',
754
855
  });
755
856
  }
@@ -765,7 +866,7 @@ export function FloatingFocusManager(props: any): any {
765
866
  createElement(FocusGuard, {
766
867
  'data-type': 'inside',
767
868
  ref: mergedBeforeGuardRef,
768
- onFocus: (event: any) => {
869
+ onFocus: (event: FocusEvent) => {
769
870
  if (modal) {
770
871
  const els = getTabbableElements();
771
872
  enqueueFocus(order[0] === 'reference' ? els[0] : els[els.length - 1]);
@@ -790,7 +891,7 @@ export function FloatingFocusManager(props: any): any {
790
891
  createElement(FocusGuard, {
791
892
  'data-type': 'inside',
792
893
  ref: mergedAfterGuardRef,
793
- onFocus: (event: any) => {
894
+ onFocus: (event: FocusEvent) => {
794
895
  if (modal) {
795
896
  enqueueFocus(getTabbableElements()[0]);
796
897
  } else if (