@octanejs/floating-ui 0.1.9 → 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/src/types.ts ADDED
@@ -0,0 +1,337 @@
1
+ // Shared public types for @octanejs/floating-ui — the octane adaptation of
2
+ // @floating-ui/react's (0.27.19) type surface. Names and shapes mirror upstream;
3
+ // React idioms map to the port's actual contract:
4
+ //
5
+ // - `React.MutableRefObject<T>` → octane's plain mutable ref object
6
+ // (`MutableRefObject<T>` below — `{ current: T }`, what octane's useRef
7
+ // returns). Ref callbacks are `(node: T | null) => void` with an optional
8
+ // React-19-style cleanup return.
9
+ // - `React.SetStateAction` / `Dispatch` → octane's useState setter shape
10
+ // (`(value: T | ((prev: T) => T)) => void`).
11
+ // - `React.HTMLProps<T>` prop bags → `HTMLProps<T>`: octane's JSX attribute
12
+ // surface (`Octane.HTMLAttributes` — NATIVE event handlers, no synthetic
13
+ // layer) plus an open string index, because the interaction prop getters
14
+ // merge arbitrary keys (aria-*, data-*, ref).
15
+ // - `whileElementsMounted` may return `void` as well as a cleanup — the port
16
+ // forwards its result straight to an octane effect, which accepts both.
17
+ import type { Octane } from 'octane/jsx-runtime';
18
+ import type {
19
+ ComputePositionConfig,
20
+ ComputePositionReturn,
21
+ Padding,
22
+ Strategy,
23
+ VirtualElement,
24
+ } from '@floating-ui/dom';
25
+
26
+ type Prettify<T> = {
27
+ [K in keyof T]: T[K];
28
+ } & {};
29
+
30
+ export type ReferenceType = Element | VirtualElement;
31
+
32
+ export type NarrowedElement<T> = T extends Element ? T : Element;
33
+
34
+ /** Octane ref-object shape (what octane's `useRef` returns). */
35
+ export interface MutableRefObject<T> {
36
+ current: T;
37
+ }
38
+
39
+ /** Octane ref callback (React-19 style: may return a cleanup). */
40
+ export type RefCallback<T> = (node: T | null) => void | (() => void);
41
+
42
+ export type OpenChangeReason =
43
+ | 'outside-press'
44
+ | 'escape-key'
45
+ | 'ancestor-scroll'
46
+ | 'reference-press'
47
+ | 'click'
48
+ | 'hover'
49
+ | 'focus'
50
+ | 'focus-out'
51
+ | 'list-navigation'
52
+ | 'safe-polygon';
53
+
54
+ export interface FloatingEvents {
55
+ emit<T extends string>(event: T, data?: any): void;
56
+ on(event: string, handler: (data: any) => void): void;
57
+ off(event: string, handler: (data: any) => void): void;
58
+ }
59
+
60
+ export interface ContextData {
61
+ openEvent?: Event;
62
+ floatingContext?: FloatingContext;
63
+ /** @deprecated use `onTypingChange` prop in `useTypeahead` */
64
+ typing?: boolean;
65
+ [key: string]: any;
66
+ }
67
+
68
+ /**
69
+ * The pre-configured positioning styles for the floating element. Upstream
70
+ * types this as `React.CSSProperties`; the port returns exactly this object
71
+ * shape, which is assignable to octane's `style` prop.
72
+ */
73
+ export interface FloatingStyles {
74
+ position: Strategy;
75
+ top: number;
76
+ left: number;
77
+ transform?: string;
78
+ willChange?: string;
79
+ }
80
+
81
+ // ─── Positioning core (the @floating-ui/react-dom `useFloating` surface) ─────
82
+
83
+ export type UsePositionFloatingOptions<RT extends ReferenceType = ReferenceType> = Prettify<
84
+ Partial<ComputePositionConfig> & {
85
+ /**
86
+ * A callback invoked when both the reference and floating elements are
87
+ * mounted, and cleaned up when either is unmounted. This is useful for
88
+ * setting up event listeners (e.g. pass `autoUpdate`).
89
+ */
90
+ whileElementsMounted?: (
91
+ reference: RT,
92
+ floating: HTMLElement,
93
+ update: () => void,
94
+ ) => void | (() => void);
95
+ /**
96
+ * Object containing the reference and floating elements.
97
+ */
98
+ elements?: {
99
+ reference?: RT | null;
100
+ floating?: HTMLElement | null;
101
+ };
102
+ /**
103
+ * The `open` state of the floating element to synchronize with the
104
+ * `isPositioned` value.
105
+ * @default false
106
+ */
107
+ open?: boolean;
108
+ /**
109
+ * Whether to use `transform` for positioning instead of `top` and `left`
110
+ * (layout) in the `floatingStyles` object.
111
+ * @default true
112
+ */
113
+ transform?: boolean;
114
+ }
115
+ >;
116
+
117
+ export type UsePositionFloatingData = Prettify<ComputePositionReturn & { isPositioned: boolean }>;
118
+
119
+ export type UsePositionFloatingReturn<RT extends ReferenceType = ReferenceType> = Prettify<
120
+ UsePositionFloatingData & {
121
+ /**
122
+ * Update the position of the floating element, re-rendering the component
123
+ * if required.
124
+ */
125
+ update: () => void;
126
+ /**
127
+ * Pre-configured positioning styles to apply to the floating element.
128
+ */
129
+ floatingStyles: FloatingStyles;
130
+ /**
131
+ * Object containing the reference and floating refs and reactive setters.
132
+ */
133
+ refs: {
134
+ reference: MutableRefObject<RT | null>;
135
+ floating: MutableRefObject<HTMLElement | null>;
136
+ setReference: (node: RT | null) => void;
137
+ setFloating: (node: HTMLElement | null) => void;
138
+ };
139
+ /**
140
+ * Object containing the reference and floating elements.
141
+ */
142
+ elements: {
143
+ reference: RT | null;
144
+ floating: HTMLElement | null;
145
+ };
146
+ }
147
+ >;
148
+
149
+ /** Options for the ref-aware `arrow` middleware (accepts an octane ref or an element). */
150
+ export interface ArrowOptions {
151
+ /**
152
+ * The arrow element to be positioned.
153
+ * @default undefined
154
+ */
155
+ element: MutableRefObject<Element | null> | Element | null;
156
+ /**
157
+ * The padding between the arrow element and the floating element edges.
158
+ * Useful when the floating element has rounded corners.
159
+ * @default 0
160
+ */
161
+ padding?: Padding;
162
+ }
163
+
164
+ // ─── Interaction context (the @floating-ui/react `useFloating` surface) ──────
165
+
166
+ export interface ExtendedRefs<RT> {
167
+ reference: MutableRefObject<ReferenceType | null>;
168
+ floating: MutableRefObject<HTMLElement | null>;
169
+ domReference: MutableRefObject<NarrowedElement<RT> | null>;
170
+ setReference(node: RT | null): void;
171
+ setFloating(node: HTMLElement | null): void;
172
+ setPositionReference(node: ReferenceType | null): void;
173
+ }
174
+
175
+ export interface ExtendedElements<RT> {
176
+ reference: ReferenceType | null;
177
+ floating: HTMLElement | null;
178
+ domReference: NarrowedElement<RT> | null;
179
+ }
180
+
181
+ export type FloatingContext<RT extends ReferenceType = ReferenceType> = Omit<
182
+ UsePositionFloatingReturn<RT>,
183
+ 'refs' | 'elements'
184
+ > & {
185
+ open: boolean;
186
+ onOpenChange(open: boolean, event?: Event, reason?: OpenChangeReason): void;
187
+ events: FloatingEvents;
188
+ dataRef: MutableRefObject<ContextData>;
189
+ nodeId: string | undefined;
190
+ floatingId: string;
191
+ refs: ExtendedRefs<RT>;
192
+ elements: ExtendedElements<RT>;
193
+ };
194
+
195
+ export interface FloatingRootContext<RT extends ReferenceType = ReferenceType> {
196
+ dataRef: MutableRefObject<ContextData>;
197
+ open: boolean;
198
+ onOpenChange: (open: boolean, event?: Event, reason?: OpenChangeReason) => void;
199
+ elements: {
200
+ domReference: Element | null;
201
+ reference: RT | null;
202
+ floating: HTMLElement | null;
203
+ };
204
+ events: FloatingEvents;
205
+ floatingId: string;
206
+ refs: {
207
+ setPositionReference(node: ReferenceType | null): void;
208
+ };
209
+ }
210
+
211
+ export interface UseFloatingRootContextOptions {
212
+ open?: boolean;
213
+ onOpenChange?: (open: boolean, event?: Event, reason?: OpenChangeReason) => void;
214
+ elements: {
215
+ reference: Element | null;
216
+ floating: HTMLElement | null;
217
+ };
218
+ }
219
+
220
+ export interface UseFloatingOptions<RT extends ReferenceType = ReferenceType> extends Omit<
221
+ UsePositionFloatingOptions<RT>,
222
+ 'elements'
223
+ > {
224
+ rootContext?: FloatingRootContext<RT>;
225
+ /**
226
+ * Object of external elements as an alternative to the `refs` object setters.
227
+ */
228
+ elements?: {
229
+ /**
230
+ * Externally passed reference element. Store in state.
231
+ */
232
+ reference?: Element | null;
233
+ /**
234
+ * Externally passed floating element. Store in state.
235
+ */
236
+ floating?: HTMLElement | null;
237
+ };
238
+ /**
239
+ * An event callback that is invoked when the floating element is opened or
240
+ * closed.
241
+ */
242
+ onOpenChange?(open: boolean, event?: Event, reason?: OpenChangeReason): void;
243
+ /**
244
+ * Unique node id when using `FloatingTree`.
245
+ */
246
+ nodeId?: string;
247
+ }
248
+
249
+ export type UseFloatingReturn<RT extends ReferenceType = ReferenceType> = Prettify<
250
+ Omit<UsePositionFloatingReturn<RT>, 'refs' | 'elements'> & {
251
+ /**
252
+ * `FloatingContext`
253
+ */
254
+ context: Prettify<FloatingContext<RT>>;
255
+ /**
256
+ * Object containing the reference and floating refs and reactive setters.
257
+ */
258
+ refs: ExtendedRefs<RT>;
259
+ elements: ExtendedElements<RT>;
260
+ }
261
+ >;
262
+
263
+ export type UseFloatingData = Prettify<UseFloatingReturn>;
264
+
265
+ // ─── Interaction prop getters (useInteractions) ──────────────────────────────
266
+
267
+ /**
268
+ * An octane HTML prop bag: octane's JSX attribute surface (NATIVE event
269
+ * handlers — octane has no synthetic events) plus an open string index, since
270
+ * the interaction hooks and prop getters merge arbitrary keys (aria-*, data-*,
271
+ * ref). The octane analog of upstream's `React.HTMLProps<T>`.
272
+ */
273
+ export type HTMLProps<T extends EventTarget = HTMLElement> = Octane.HTMLAttributes<T> & {
274
+ [key: string]: unknown;
275
+ };
276
+
277
+ export interface ExtendedUserProps {
278
+ active?: boolean;
279
+ selected?: boolean;
280
+ }
281
+
282
+ export interface ElementProps {
283
+ reference?: HTMLProps<Element>;
284
+ floating?: HTMLProps<HTMLElement>;
285
+ item?: HTMLProps<HTMLElement> | ((props: ExtendedUserProps) => HTMLProps<HTMLElement>);
286
+ }
287
+
288
+ export interface UseInteractionsReturn {
289
+ // The `| object` widening keeps prop bags typed against other libraries'
290
+ // HTML prop shapes (e.g. React's) callable — the getters merge any object.
291
+ getReferenceProps: (userProps?: HTMLProps<Element> | object) => Record<string, unknown>;
292
+ getFloatingProps: (userProps?: HTMLProps<HTMLElement> | object) => Record<string, unknown>;
293
+ getItemProps: (
294
+ userProps?: (Omit<HTMLProps<HTMLElement>, 'selected' | 'active'> & ExtendedUserProps) | object,
295
+ ) => Record<string, unknown>;
296
+ }
297
+
298
+ // ─── Tree ────────────────────────────────────────────────────────────────────
299
+
300
+ export interface FloatingNodeType<RT extends ReferenceType = ReferenceType> {
301
+ id: string | undefined;
302
+ parentId: string | null;
303
+ context?: FloatingContext<RT>;
304
+ }
305
+
306
+ export interface FloatingTreeType<RT extends ReferenceType = ReferenceType> {
307
+ nodesRef: MutableRefObject<Array<FloatingNodeType<RT>>>;
308
+ events: FloatingEvents;
309
+ addNode(node: FloatingNodeType): void;
310
+ removeNode(node: FloatingNodeType): void;
311
+ }
312
+
313
+ // ─── Shared interaction-hook types ───────────────────────────────────────────
314
+
315
+ export type Delay =
316
+ | number
317
+ | Partial<{
318
+ open: number;
319
+ close: number;
320
+ }>;
321
+
322
+ export interface SafePolygonOptions {
323
+ buffer?: number;
324
+ blockPointerEvents?: boolean;
325
+ requireIntent?: boolean;
326
+ }
327
+
328
+ export interface HandleCloseContext extends FloatingContext {
329
+ onClose: () => void;
330
+ tree?: FloatingTreeType | null;
331
+ leave?: boolean;
332
+ }
333
+
334
+ export interface HandleClose {
335
+ (context: HandleCloseContext): (event: MouseEvent) => void;
336
+ __options?: SafePolygonOptions;
337
+ }
package/src/useClick.ts CHANGED
@@ -5,21 +5,71 @@ import { useMemo, useRef } from 'octane';
5
5
 
6
6
  import { splitSlot, subSlot } from './internal';
7
7
  import { isMouseLikePointerType, isTypeableElement } from './utils';
8
+ import type { ElementProps, FloatingRootContext } from './types';
8
9
 
9
- function isButtonTarget(event: any): boolean {
10
+ export interface UseClickProps {
11
+ /**
12
+ * Whether the Hook is enabled, including all internal Effects and event
13
+ * handlers.
14
+ * @default true
15
+ */
16
+ enabled?: boolean;
17
+ /**
18
+ * The type of event to use to determine a “click” with mouse input.
19
+ * Keyboard clicks work as normal.
20
+ * @default 'click'
21
+ */
22
+ event?: 'click' | 'mousedown';
23
+ /**
24
+ * Whether to toggle the open state with repeated clicks.
25
+ * @default true
26
+ */
27
+ toggle?: boolean;
28
+ /**
29
+ * Whether to ignore the logic for mouse input (for example, if `useHover()`
30
+ * is also being used).
31
+ * @default false
32
+ */
33
+ ignoreMouse?: boolean;
34
+ /**
35
+ * Whether to add keyboard handlers (Enter and Space key functionality) for
36
+ * non-button elements (to open/close the floating element via keyboard
37
+ * “click”).
38
+ * @default true
39
+ */
40
+ keyboardHandlers?: boolean;
41
+ /**
42
+ * If already open from another event such as the `useHover()` Hook,
43
+ * determines whether to keep the floating element open when clicking the
44
+ * reference element for the first time.
45
+ * @default true
46
+ */
47
+ stickIfOpen?: boolean;
48
+ }
49
+
50
+ function isButtonTarget(event: Event): boolean {
10
51
  return isHTMLElement(event.target) && event.target.tagName === 'BUTTON';
11
52
  }
12
- function isAnchorTarget(event: any): boolean {
53
+ function isAnchorTarget(event: Event): boolean {
13
54
  return isHTMLElement(event.target) && event.target.tagName === 'A';
14
55
  }
15
- function isSpaceIgnored(element: any): boolean {
56
+ function isSpaceIgnored(element: Element | null): boolean {
16
57
  return isTypeableElement(element);
17
58
  }
18
59
 
19
- export function useClick(...args: any[]): any {
60
+ /**
61
+ * Opens or closes the floating element when clicking the reference element.
62
+ * @see https://floating-ui.com/docs/useClick
63
+ */
64
+ export function useClick(
65
+ context: FloatingRootContext,
66
+ props?: UseClickProps,
67
+ slot?: symbol,
68
+ ): ElementProps;
69
+ export function useClick(...args: any[]): ElementProps {
20
70
  const [user, slot] = splitSlot(args);
21
- const context = user[0];
22
- const props = (user[1] as any) ?? {};
71
+ const context = user[0] as FloatingRootContext;
72
+ const props = (user[1] as UseClickProps) ?? {};
23
73
 
24
74
  const open = context.open;
25
75
  const onOpenChange = context.onOpenChange;
@@ -33,15 +83,15 @@ export function useClick(...args: any[]): any {
33
83
  const keyboardHandlers = props.keyboardHandlers ?? true;
34
84
  const stickIfOpen = props.stickIfOpen ?? true;
35
85
 
36
- const pointerTypeRef = useRef<any>(undefined, subSlot(slot, 'ptype'));
86
+ const pointerTypeRef = useRef<string | undefined>(undefined, subSlot(slot, 'ptype'));
37
87
  const didKeyDownRef = useRef(false, subSlot(slot, 'kd'));
38
88
 
39
89
  const reference = useMemo(
40
90
  () => ({
41
- onPointerDown(event: any) {
91
+ onPointerDown(event: PointerEvent) {
42
92
  pointerTypeRef.current = event.pointerType;
43
93
  },
44
- onMouseDown(event: any) {
94
+ onMouseDown(event: MouseEvent) {
45
95
  const pointerType = pointerTypeRef.current;
46
96
  if (event.button !== 0) return;
47
97
  if (eventOption === 'click') return;
@@ -59,7 +109,7 @@ export function useClick(...args: any[]): any {
59
109
  onOpenChange(true, event, 'click');
60
110
  }
61
111
  },
62
- onClick(event: any) {
112
+ onClick(event: MouseEvent) {
63
113
  const pointerType = pointerTypeRef.current;
64
114
  if (eventOption === 'mousedown' && pointerTypeRef.current) {
65
115
  pointerTypeRef.current = undefined;
@@ -78,7 +128,7 @@ export function useClick(...args: any[]): any {
78
128
  onOpenChange(true, event, 'click');
79
129
  }
80
130
  },
81
- onKeyDown(event: any) {
131
+ onKeyDown(event: KeyboardEvent) {
82
132
  pointerTypeRef.current = undefined;
83
133
  if (event.defaultPrevented || !keyboardHandlers || isButtonTarget(event)) {
84
134
  return;
@@ -98,7 +148,7 @@ export function useClick(...args: any[]): any {
98
148
  }
99
149
  }
100
150
  },
101
- onKeyUp(event: any) {
151
+ onKeyUp(event: KeyboardEvent) {
102
152
  if (
103
153
  event.defaultPrevented ||
104
154
  !keyboardHandlers ||
@@ -131,7 +181,7 @@ export function useClick(...args: any[]): any {
131
181
  subSlot(slot, 'm:ref'),
132
182
  );
133
183
 
134
- return useMemo(
184
+ return useMemo<ElementProps>(
135
185
  () => (enabled ? { reference } : {}),
136
186
  [enabled, reference],
137
187
  subSlot(slot, 'm:ret'),
@@ -11,6 +11,33 @@ import {
11
11
  useEffectEvent,
12
12
  useModernLayoutEffect,
13
13
  } from './utils';
14
+ import type { ElementProps, FloatingRootContext } from './types';
15
+
16
+ export interface UseClientPointProps {
17
+ /**
18
+ * Whether the Hook is enabled, including all internal Effects and event
19
+ * handlers.
20
+ * @default true
21
+ */
22
+ enabled?: boolean;
23
+ /**
24
+ * Whether to restrict the client point to an axis and use the reference
25
+ * element (if it exists) as the other axis. This can be useful if the
26
+ * floating element is also interactive.
27
+ * @default 'both'
28
+ */
29
+ axis?: 'x' | 'y' | 'both';
30
+ /**
31
+ * An explicitly defined `x` client coordinate.
32
+ * @default null
33
+ */
34
+ x?: number | null;
35
+ /**
36
+ * An explicitly defined `y` client coordinate.
37
+ * @default null
38
+ */
39
+ y?: number | null;
40
+ }
14
41
 
15
42
  function createVirtualElement(domElement: any, data: any): any {
16
43
  let offsetX: number | null = null;
@@ -57,10 +84,20 @@ function isMouseBasedEvent(event: any): boolean {
57
84
  return event != null && event.clientX != null;
58
85
  }
59
86
 
60
- export function useClientPoint(...args: any[]): any {
87
+ /**
88
+ * Positions the floating element relative to a client point (in the viewport),
89
+ * such as the mouse position. By default, it follows the mouse cursor.
90
+ * @see https://floating-ui.com/docs/useClientPoint
91
+ */
92
+ export function useClientPoint(
93
+ context: FloatingRootContext,
94
+ props?: UseClientPointProps,
95
+ slot?: symbol,
96
+ ): ElementProps;
97
+ export function useClientPoint(...args: any[]): ElementProps {
61
98
  const [user, slot] = splitSlot(args);
62
- const context = user[0];
63
- const props = (user[1] as any) ?? {};
99
+ const context = user[0] as FloatingRootContext;
100
+ const props = (user[1] as UseClientPointProps) ?? {};
64
101
 
65
102
  const open = context.open;
66
103
  const dataRef = context.dataRef;
@@ -74,12 +111,15 @@ export function useClientPoint(...args: any[]): any {
74
111
  const y = props.y ?? null;
75
112
 
76
113
  const initialRef = useRef(false, subSlot(slot, 'init'));
77
- const cleanupListenerRef = useRef<any>(null, subSlot(slot, 'cleanup'));
78
- const [pointerType, setPointerType] = useState<any>(undefined, subSlot(slot, 'ptype'));
114
+ const cleanupListenerRef = useRef<(() => void) | null>(null, subSlot(slot, 'cleanup'));
115
+ const [pointerType, setPointerType] = useState<string | undefined>(
116
+ undefined,
117
+ subSlot(slot, 'ptype'),
118
+ );
79
119
  const [reactive, setReactive] = useState<any[]>([], subSlot(slot, 'reactive'));
80
120
 
81
121
  const setReference = useEffectEvent(
82
- (px: number, py: number) => {
122
+ (px: number | null, py: number | null) => {
83
123
  if (initialRef.current) return;
84
124
  if (dataRef.current.openEvent && !isMouseBasedEvent(dataRef.current.openEvent)) {
85
125
  return;
@@ -92,7 +132,7 @@ export function useClientPoint(...args: any[]): any {
92
132
  );
93
133
 
94
134
  const handleReferenceEnterOrMove = useEffectEvent(
95
- (event: any) => {
135
+ (event: MouseEvent) => {
96
136
  if (x != null || y != null) return;
97
137
  if (!open) {
98
138
  setReference(event.clientX, event.clientY);
@@ -108,7 +148,7 @@ export function useClientPoint(...args: any[]): any {
108
148
  () => {
109
149
  if (!openCheck || !enabled || x != null || y != null) return;
110
150
  const win = getWindow(floating);
111
- function handleMouseMove(event: any) {
151
+ function handleMouseMove(event: MouseEvent) {
112
152
  const target = getTarget(event);
113
153
  if (!contains(floating, target as any)) {
114
154
  setReference(event.clientX, event.clientY);
@@ -170,7 +210,7 @@ export function useClientPoint(...args: any[]): any {
170
210
 
171
211
  const reference = useMemo(
172
212
  () => {
173
- function setPointerTypeRef(_ref: any) {
213
+ function setPointerTypeRef(_ref: PointerEvent) {
174
214
  const { pointerType: pt } = _ref;
175
215
  setPointerType(pt);
176
216
  }
@@ -185,7 +225,7 @@ export function useClientPoint(...args: any[]): any {
185
225
  subSlot(slot, 'm:ref'),
186
226
  );
187
227
 
188
- return useMemo(
228
+ return useMemo<ElementProps>(
189
229
  () => (enabled ? { reference } : {}),
190
230
  [enabled, reference],
191
231
  subSlot(slot, 'm:ret'),