@bsky.app/sift 0.1.0 → 0.1.2

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/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # @bsky.app/sift
2
+
3
+ ## 0.1.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`255ae92`](https://github.com/bluesky-social/toolbox/commit/255ae923d552c12b6ad36dedde37a9bf44d02866) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Bump sift/tapper for publish
8
+
9
+ ## 0.1.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [`06c8141`](https://github.com/bluesky-social/toolbox/commit/06c81411f8420240c8c794d41398df034c514fda) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Bump packages
package/build/Sift.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { type StyleProp, type ViewStyle } from 'react-native';
1
+ import { type StyleProp, type ViewStyle, type PressableProps } from 'react-native';
2
2
  import { type UseSiftReturn } from './useSift';
3
3
  export * from './useSift';
4
4
  export declare function Sift<Item extends {
@@ -8,6 +8,11 @@ export declare function Sift<Item extends {
8
8
  data: Item[];
9
9
  render: (props: {
10
10
  active: boolean;
11
+ props: {
12
+ role: string;
13
+ 'aria-selected': boolean;
14
+ onPress: () => void;
15
+ };
11
16
  item: Item;
12
17
  }) => React.ReactElement;
13
18
  style?: StyleProp<ViewStyle>;
@@ -15,4 +20,15 @@ export declare function Sift<Item extends {
15
20
  onDismiss?: () => void;
16
21
  inverted?: boolean;
17
22
  }): import("react").JSX.Element;
23
+ /**
24
+ * A Pressable wrapper for items rendered in Sift. It applies the necessary
25
+ * accessibility props for each item.
26
+ */
27
+ export declare function SiftItem({ children, role, style, ...props }: Omit<PressableProps, 'role' | 'style'> & {
28
+ role?: string;
29
+ style?: StyleProp<ViewStyle> | ((state: {
30
+ pressed: boolean;
31
+ hovered: boolean;
32
+ }) => StyleProp<ViewStyle>) | undefined;
33
+ }): import("react").JSX.Element;
18
34
  //# sourceMappingURL=Sift.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"Sift.d.ts","sourceRoot":"","sources":["../src/Sift.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACf,MAAM,cAAc,CAAA;AACrB,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,WAAW,CAAA;AAG5C,cAAc,WAAW,CAAA;AAEzB,wBAAgB,IAAI,CAAC,IAAI,SAAS;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,EAAE,EAC/C,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,IAAI,EAAE,CAAA;IACZ,MAAM,EAAE,CAAC,KAAK,EAAE;QAAC,MAAM,EAAE,OAAO,CAAC;QAAC,IAAI,EAAE,IAAI,CAAA;KAAC,KAAK,KAAK,CAAC,YAAY,CAAA;IACpE,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,+BAmEA"}
1
+ {"version":3,"file":"Sift.d.ts","sourceRoot":"","sources":["../src/Sift.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,cAAc,EAEpB,MAAM,cAAc,CAAA;AACrB,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,WAAW,CAAA;AAG5C,cAAc,WAAW,CAAA;AAEzB,wBAAgB,IAAI,CAAC,IAAI,SAAS;IAAC,GAAG,EAAE,MAAM,CAAA;CAAC,EAAE,EAC/C,IAAI,EACJ,IAAI,EACJ,MAAM,EACN,KAAK,EACL,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE;IACD,IAAI,EAAE,aAAa,CAAA;IACnB,IAAI,EAAE,IAAI,EAAE,CAAA;IACZ,MAAM,EAAE,CAAC,KAAK,EAAE;QACd,MAAM,EAAE,OAAO,CAAA;QACf,KAAK,EAAE;YACL,IAAI,EAAE,MAAM,CAAA;YACZ,eAAe,EAAE,OAAO,CAAA;YACxB,OAAO,EAAE,MAAM,IAAI,CAAA;SACpB,CAAA;QACD,IAAI,EAAE,IAAI,CAAA;KACX,KAAK,KAAK,CAAC,YAAY,CAAA;IACxB,KAAK,CAAC,EAAE,SAAS,CAAC,SAAS,CAAC,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,IAAI,KAAK,IAAI,CAAA;IAC/B,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;IACtB,QAAQ,CAAC,EAAE,OAAO,CAAA;CACnB,+BAmEA;AAED;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,EACvB,QAAQ,EACR,IAAI,EACJ,KAAK,EACL,GAAG,KAAK,EACT,EAAE,IAAI,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,GAAG;IAC1C,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,KAAK,CAAC,EACF,SAAS,CAAC,SAAS,CAAC,GACpB,CAAC,CAAC,KAAK,EAAE;QAAC,OAAO,EAAE,OAAO,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAC,KAAK,SAAS,CAAC,SAAS,CAAC,CAAC,GACvE,SAAS,CAAA;CACd,+BASA"}
package/build/Sift.js CHANGED
@@ -6,8 +6,8 @@ export function Sift({ sift, data, render, style, onSelect, onDismiss, inverted,
6
6
  const [activeIndex, setActiveIndex] = useState(0);
7
7
  const activeIndexRef = useRef(activeIndex);
8
8
  activeIndexRef.current = activeIndex;
9
- const updateRef = useRef(sift.internal.update);
10
- updateRef.current = sift.internal.update;
9
+ const updateRef = useRef(sift.updatePosition);
10
+ updateRef.current = sift.updatePosition;
11
11
  useEffect(() => {
12
12
  setActiveIndex(0);
13
13
  updateRef.current();
@@ -43,15 +43,27 @@ export function Sift({ sift, data, render, style, onSelect, onDismiss, inverted,
43
43
  },
44
44
  onDismiss,
45
45
  });
46
- return (<View collapsable={false} ref={sift.internal.refs.setFloating}
46
+ return (<View collapsable={false} ref={sift.refs.setPopover}
47
47
  // @ts-ignore
48
- role="listbox" id={sift.id} style={[sift.internal.floatingStyles, style]}>
49
- <FlatList data={data} inverted={inverted} keyExtractor={item => item.key} renderItem={items => (<Pressable role="option" aria-selected={items.index === activeIndex} onPress={() => onSelect?.(items.item)}>
50
- {render({
51
- active: items.index === activeIndex,
52
- item: items.item,
53
- })}
54
- </Pressable>)}/>
48
+ role="listbox" id={sift.id} style={[style, sift.popoverStyles]}>
49
+ <FlatList data={data} inverted={inverted} keyExtractor={item => item.key} renderItem={items => render({
50
+ active: items.index === activeIndex,
51
+ props: {
52
+ role: 'option',
53
+ 'aria-selected': items.index === activeIndex,
54
+ onPress: () => onSelect?.(items.item),
55
+ },
56
+ item: items.item,
57
+ })}/>
55
58
  </View>);
56
59
  }
60
+ /**
61
+ * A Pressable wrapper for items rendered in Sift. It applies the necessary
62
+ * accessibility props for each item.
63
+ */
64
+ export function SiftItem({ children, role, style, ...props }) {
65
+ return (<Pressable role={role} style={style} {...props}>
66
+ {children}
67
+ </Pressable>);
68
+ }
57
69
  //# sourceMappingURL=Sift.js.map
package/build/Sift.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"Sift.js","sourceRoot":"","sources":["../src/Sift.tsx"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useRef, useState} from 'react'\nimport {\n View,\n FlatList,\n Pressable,\n type StyleProp,\n type ViewStyle,\n} from 'react-native'\nimport {type UseSiftReturn} from './useSift'\nimport {useKeyboardHandling} from './useKeyboardHandling'\n\nexport * from './useSift'\n\nexport function Sift<Item extends {key: string}>({\n sift,\n data,\n render,\n style,\n onSelect,\n onDismiss,\n inverted,\n}: {\n sift: UseSiftReturn\n data: Item[]\n render: (props: {active: boolean; item: Item}) => React.ReactElement\n style?: StyleProp<ViewStyle>\n onSelect?: (item: Item) => void\n onDismiss?: () => void\n inverted?: boolean\n}) {\n const [activeIndex, setActiveIndex] = useState(0)\n const activeIndexRef = useRef(activeIndex)\n activeIndexRef.current = activeIndex\n const updateRef = useRef(sift.internal.update)\n updateRef.current = sift.internal.update\n\n useEffect(() => {\n setActiveIndex(0)\n updateRef.current()\n }, [data.length])\n\n const next = () => {\n if (data.length === 0) return\n setActiveIndex(i => (i + 1) % data.length)\n }\n const prev = () => {\n if (data.length === 0) return\n setActiveIndex(i => (i - 1 + data.length) % data.length)\n }\n const first = () => {\n if (data.length === 0) return\n setActiveIndex(0)\n }\n const last = () => {\n if (data.length === 0) return\n setActiveIndex(data.length - 1)\n }\n\n useKeyboardHandling({\n sift,\n onArrowDown: inverted ? prev : next,\n onArrowUp: inverted ? next : prev,\n onHome: inverted ? last : first,\n onEnd: inverted ? first : last,\n onSelect: () => {\n onSelect?.(data[activeIndexRef.current])\n },\n onDismiss,\n })\n\n return (\n <View\n collapsable={false}\n ref={sift.internal.refs.setFloating}\n // @ts-ignore\n role=\"listbox\"\n id={sift.id}\n style={[sift.internal.floatingStyles, style]}>\n <FlatList\n data={data}\n inverted={inverted}\n keyExtractor={item => item.key}\n renderItem={items => (\n <Pressable\n role=\"option\"\n aria-selected={items.index === activeIndex}\n onPress={() => onSelect?.(items.item)}>\n {render({\n active: items.index === activeIndex,\n item: items.item,\n })}\n </Pressable>\n )}\n />\n </View>\n )\n}\n"]}
1
+ {"version":3,"file":"Sift.js","sourceRoot":"","sources":["../src/Sift.tsx"],"names":[],"mappings":"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","sourcesContent":["import {useEffect, useRef, useState} from 'react'\nimport {\n View,\n FlatList,\n Pressable,\n type StyleProp,\n type ViewStyle,\n type PressableProps,\n type Role,\n} from 'react-native'\nimport {type UseSiftReturn} from './useSift'\nimport {useKeyboardHandling} from './useKeyboardHandling'\n\nexport * from './useSift'\n\nexport function Sift<Item extends {key: string}>({\n sift,\n data,\n render,\n style,\n onSelect,\n onDismiss,\n inverted,\n}: {\n sift: UseSiftReturn\n data: Item[]\n render: (props: {\n active: boolean\n props: {\n role: string\n 'aria-selected': boolean\n onPress: () => void\n }\n item: Item\n }) => React.ReactElement\n style?: StyleProp<ViewStyle>\n onSelect?: (item: Item) => void\n onDismiss?: () => void\n inverted?: boolean\n}) {\n const [activeIndex, setActiveIndex] = useState(0)\n const activeIndexRef = useRef(activeIndex)\n activeIndexRef.current = activeIndex\n const updateRef = useRef(sift.updatePosition)\n updateRef.current = sift.updatePosition\n\n useEffect(() => {\n setActiveIndex(0)\n updateRef.current()\n }, [data.length])\n\n const next = () => {\n if (data.length === 0) return\n setActiveIndex(i => (i + 1) % data.length)\n }\n const prev = () => {\n if (data.length === 0) return\n setActiveIndex(i => (i - 1 + data.length) % data.length)\n }\n const first = () => {\n if (data.length === 0) return\n setActiveIndex(0)\n }\n const last = () => {\n if (data.length === 0) return\n setActiveIndex(data.length - 1)\n }\n\n useKeyboardHandling({\n sift,\n onArrowDown: inverted ? prev : next,\n onArrowUp: inverted ? next : prev,\n onHome: inverted ? last : first,\n onEnd: inverted ? first : last,\n onSelect: () => {\n onSelect?.(data[activeIndexRef.current])\n },\n onDismiss,\n })\n\n return (\n <View\n collapsable={false}\n ref={sift.refs.setPopover}\n // @ts-ignore\n role=\"listbox\"\n id={sift.id}\n style={[style, sift.popoverStyles]}>\n <FlatList\n data={data}\n inverted={inverted}\n keyExtractor={item => item.key}\n renderItem={items =>\n render({\n active: items.index === activeIndex,\n props: {\n role: 'option',\n 'aria-selected': items.index === activeIndex,\n onPress: () => onSelect?.(items.item),\n },\n item: items.item,\n })\n }\n />\n </View>\n )\n}\n\n/**\n * A Pressable wrapper for items rendered in Sift. It applies the necessary\n * accessibility props for each item.\n */\nexport function SiftItem({\n children,\n role,\n style,\n ...props\n}: Omit<PressableProps, 'role' | 'style'> & {\n role?: string\n style?:\n | StyleProp<ViewStyle>\n | ((state: {pressed: boolean; hovered: boolean}) => StyleProp<ViewStyle>)\n | undefined\n}) {\n return (\n <Pressable\n role={role as Role}\n style={style as PressableProps['style']}\n {...props}>\n {children}\n </Pressable>\n )\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import { type ViewStyle } from 'react-native';
2
+ import { type Placement } from './useSift';
3
+ export declare function computeStyles({ anchor, input, popover, }: {
4
+ anchor: any;
5
+ input: any;
6
+ popover: any | null;
7
+ }, options: {
8
+ offset: number;
9
+ placement: Placement;
10
+ dynamicWidth?: boolean;
11
+ }): Promise<ViewStyle>;
12
+ //# sourceMappingURL=computeStyles.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computeStyles.d.ts","sourceRoot":"","sources":["../src/computeStyles.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,WAAW,CAAA;AAcxC,wBAAsB,aAAa,CACjC,EACE,MAAM,EACN,KAAK,EACL,OAAO,GACR,EAAE;IACD,MAAM,EAAE,GAAG,CAAA;IACX,KAAK,EAAE,GAAG,CAAA;IACV,OAAO,EAAE,GAAG,GAAG,IAAI,CAAA;CACpB,EACD,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,GACA,OAAO,CAAC,SAAS,CAAC,CAuCpB"}
@@ -0,0 +1,44 @@
1
+ function measureInWindow(node) {
2
+ return new Promise(resolve => {
3
+ node.measureInWindow((x, y, width, height) => {
4
+ resolve({ x, y, width, height });
5
+ });
6
+ });
7
+ }
8
+ export async function computeStyles({ anchor, input, popover, }, options) {
9
+ const anchorRect = await measureInWindow(anchor);
10
+ const inputRect = await measureInWindow(input);
11
+ const popoverRect = popover ? await measureInWindow(popover) : null;
12
+ const popoverWidth = popoverRect?.width ?? 0;
13
+ const popoverHeight = popoverRect?.height ?? 0;
14
+ const [side, align] = options.placement.split('-');
15
+ let top;
16
+ if (side === 'top') {
17
+ top = anchorRect.y - options.offset - popoverHeight;
18
+ }
19
+ else {
20
+ top = anchorRect.y + anchorRect.height + options.offset;
21
+ }
22
+ let left;
23
+ if (align === 'start') {
24
+ left = anchorRect.x;
25
+ }
26
+ else if (align === 'end') {
27
+ left = anchorRect.x + anchorRect.width - popoverWidth;
28
+ }
29
+ else {
30
+ left = anchorRect.x + (anchorRect.width - popoverWidth) / 2;
31
+ }
32
+ let maxWidth;
33
+ if (options.dynamicWidth === false) {
34
+ left = inputRect.x;
35
+ maxWidth = inputRect.width;
36
+ }
37
+ return {
38
+ position: 'absolute',
39
+ top,
40
+ left,
41
+ maxWidth,
42
+ };
43
+ }
44
+ //# sourceMappingURL=computeStyles.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computeStyles.js","sourceRoot":"","sources":["../src/computeStyles.ts"],"names":[],"mappings":"AAGA,SAAS,eAAe,CACtB,IAAS;IAET,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC,EAAE;QAC3B,IAAI,CAAC,eAAe,CAClB,CAAC,CAAS,EAAE,CAAS,EAAE,KAAa,EAAE,MAAc,EAAE,EAAE;YACtD,OAAO,CAAC,EAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAC,CAAC,CAAA;QAChC,CAAC,CACF,CAAA;IACH,CAAC,CAAC,CAAA;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,EACE,MAAM,EACN,KAAK,EACL,OAAO,GAKR,EACD,OAIC;IAED,MAAM,UAAU,GAAG,MAAM,eAAe,CAAC,MAAM,CAAC,CAAA;IAChD,MAAM,SAAS,GAAG,MAAM,eAAe,CAAC,KAAK,CAAC,CAAA;IAC9C,MAAM,WAAW,GAAG,OAAO,CAAC,CAAC,CAAC,MAAM,eAAe,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;IACnE,MAAM,YAAY,GAAG,WAAW,EAAE,KAAK,IAAI,CAAC,CAAA;IAC5C,MAAM,aAAa,GAAG,WAAW,EAAE,MAAM,IAAI,CAAC,CAAA;IAC9C,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAGhD,CAAA;IAED,IAAI,GAAW,CAAA;IACf,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,GAAG,GAAG,UAAU,CAAC,CAAC,GAAG,OAAO,CAAC,MAAM,GAAG,aAAa,CAAA;IACrD,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IACzD,CAAC;IAED,IAAI,IAAY,CAAA;IAChB,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,IAAI,GAAG,UAAU,CAAC,CAAC,CAAA;IACrB,CAAC;SAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3B,IAAI,GAAG,UAAU,CAAC,CAAC,GAAG,UAAU,CAAC,KAAK,GAAG,YAAY,CAAA;IACvD,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,UAAU,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAC7D,CAAC;IAED,IAAI,QAA4B,CAAA;IAChC,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;QACnC,IAAI,GAAG,SAAS,CAAC,CAAC,CAAA;QAClB,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAA;IAC5B,CAAC;IAED,OAAO;QACL,QAAQ,EAAE,UAAU;QACpB,GAAG;QACH,IAAI;QACJ,QAAQ;KACT,CAAA;AACH,CAAC","sourcesContent":["import {type ViewStyle} from 'react-native'\nimport {type Placement} from './useSift'\n\nfunction measureInWindow(\n node: any,\n): Promise<{x: number; y: number; width: number; height: number}> {\n return new Promise(resolve => {\n node.measureInWindow(\n (x: number, y: number, width: number, height: number) => {\n resolve({x, y, width, height})\n },\n )\n })\n}\n\nexport async function computeStyles(\n {\n anchor,\n input,\n popover,\n }: {\n anchor: any\n input: any\n popover: any | null\n },\n options: {\n offset: number\n placement: Placement\n dynamicWidth?: boolean\n },\n): Promise<ViewStyle> {\n const anchorRect = await measureInWindow(anchor)\n const inputRect = await measureInWindow(input)\n const popoverRect = popover ? await measureInWindow(popover) : null\n const popoverWidth = popoverRect?.width ?? 0\n const popoverHeight = popoverRect?.height ?? 0\n const [side, align] = options.placement.split('-') as [\n string,\n string | undefined,\n ]\n\n let top: number\n if (side === 'top') {\n top = anchorRect.y - options.offset - popoverHeight\n } else {\n top = anchorRect.y + anchorRect.height + options.offset\n }\n\n let left: number\n if (align === 'start') {\n left = anchorRect.x\n } else if (align === 'end') {\n left = anchorRect.x + anchorRect.width - popoverWidth\n } else {\n left = anchorRect.x + (anchorRect.width - popoverWidth) / 2\n }\n\n let maxWidth: number | undefined\n if (options.dynamicWidth === false) {\n left = inputRect.x\n maxWidth = inputRect.width\n }\n\n return {\n position: 'absolute',\n top,\n left,\n maxWidth,\n }\n}\n"]}
@@ -0,0 +1,12 @@
1
+ import { type ViewStyle } from 'react-native';
2
+ import { type Placement } from './useSift';
3
+ export declare function computeStyles({ anchor, input, popover, }: {
4
+ anchor: HTMLElement;
5
+ input: HTMLElement;
6
+ popover: HTMLElement | null;
7
+ }, options: {
8
+ offset: number;
9
+ placement: Placement;
10
+ dynamicWidth?: boolean;
11
+ }): ViewStyle;
12
+ //# sourceMappingURL=computeStyles.web.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computeStyles.web.d.ts","sourceRoot":"","sources":["../src/computeStyles.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAC3C,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,WAAW,CAAA;AAExC,wBAAgB,aAAa,CAC3B,EACE,MAAM,EACN,KAAK,EACL,OAAO,GACR,EAAE;IACD,MAAM,EAAE,WAAW,CAAA;IACnB,KAAK,EAAE,WAAW,CAAA;IAClB,OAAO,EAAE,WAAW,GAAG,IAAI,CAAA;CAC5B,EACD,OAAO,EAAE;IACP,MAAM,EAAE,MAAM,CAAA;IACd,SAAS,EAAE,SAAS,CAAA;IACpB,YAAY,CAAC,EAAE,OAAO,CAAA;CACvB,GACA,SAAS,CAyCX"}
@@ -0,0 +1,39 @@
1
+ export function computeStyles({ anchor, input, popover, }, options) {
2
+ const anchorRect = anchor.getBoundingClientRect();
3
+ const inputRect = input.getBoundingClientRect();
4
+ const popoverRect = popover?.getBoundingClientRect();
5
+ const popoverWidth = popoverRect?.width ?? 0;
6
+ const popoverHeight = popoverRect?.height ?? 0;
7
+ const [side, align] = options.placement.split('-');
8
+ let top;
9
+ if (side === 'top') {
10
+ top = anchorRect.top - options.offset - popoverHeight + window.scrollY;
11
+ }
12
+ else {
13
+ top = anchorRect.bottom + options.offset + window.scrollY;
14
+ }
15
+ let left;
16
+ if (align === 'start') {
17
+ left = anchorRect.left;
18
+ }
19
+ else if (align === 'end') {
20
+ left = anchorRect.right - popoverWidth;
21
+ }
22
+ else {
23
+ left = anchorRect.left + (anchorRect.width - popoverWidth) / 2;
24
+ }
25
+ left += window.scrollX;
26
+ let maxWidth;
27
+ if (options.dynamicWidth === false) {
28
+ left = inputRect.left + window.scrollX;
29
+ maxWidth = inputRect.width;
30
+ }
31
+ return {
32
+ // @ts-ignore
33
+ position: 'fixed',
34
+ top,
35
+ left,
36
+ maxWidth,
37
+ };
38
+ }
39
+ //# sourceMappingURL=computeStyles.web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"computeStyles.web.js","sourceRoot":"","sources":["../src/computeStyles.web.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,aAAa,CAC3B,EACE,MAAM,EACN,KAAK,EACL,OAAO,GAKR,EACD,OAIC;IAED,MAAM,UAAU,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAA;IACjD,MAAM,SAAS,GAAG,KAAK,CAAC,qBAAqB,EAAE,CAAA;IAC/C,MAAM,WAAW,GAAG,OAAO,EAAE,qBAAqB,EAAE,CAAA;IACpD,MAAM,YAAY,GAAG,WAAW,EAAE,KAAK,IAAI,CAAC,CAAA;IAC5C,MAAM,aAAa,GAAG,WAAW,EAAE,MAAM,IAAI,CAAC,CAAA;IAC9C,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAGhD,CAAA;IAED,IAAI,GAAW,CAAA;IACf,IAAI,IAAI,KAAK,KAAK,EAAE,CAAC;QACnB,GAAG,GAAG,UAAU,CAAC,GAAG,GAAG,OAAO,CAAC,MAAM,GAAG,aAAa,GAAG,MAAM,CAAC,OAAO,CAAA;IACxE,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,UAAU,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAA;IAC3D,CAAC;IAED,IAAI,IAAY,CAAA;IAChB,IAAI,KAAK,KAAK,OAAO,EAAE,CAAC;QACtB,IAAI,GAAG,UAAU,CAAC,IAAI,CAAA;IACxB,CAAC;SAAM,IAAI,KAAK,KAAK,KAAK,EAAE,CAAC;QAC3B,IAAI,GAAG,UAAU,CAAC,KAAK,GAAG,YAAY,CAAA;IACxC,CAAC;SAAM,CAAC;QACN,IAAI,GAAG,UAAU,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,CAAA;IAChE,CAAC;IACD,IAAI,IAAI,MAAM,CAAC,OAAO,CAAA;IAEtB,IAAI,QAA4B,CAAA;IAChC,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;QACnC,IAAI,GAAG,SAAS,CAAC,IAAI,GAAG,MAAM,CAAC,OAAO,CAAA;QACtC,QAAQ,GAAG,SAAS,CAAC,KAAK,CAAA;IAC5B,CAAC;IAED,OAAO;QACL,aAAa;QACb,QAAQ,EAAE,OAAO;QACjB,GAAG;QACH,IAAI;QACJ,QAAQ;KACT,CAAA;AACH,CAAC","sourcesContent":["import {type ViewStyle} from 'react-native'\nimport {type Placement} from './useSift'\n\nexport function computeStyles(\n {\n anchor,\n input,\n popover,\n }: {\n anchor: HTMLElement\n input: HTMLElement\n popover: HTMLElement | null\n },\n options: {\n offset: number\n placement: Placement\n dynamicWidth?: boolean\n },\n): ViewStyle {\n const anchorRect = anchor.getBoundingClientRect()\n const inputRect = input.getBoundingClientRect()\n const popoverRect = popover?.getBoundingClientRect()\n const popoverWidth = popoverRect?.width ?? 0\n const popoverHeight = popoverRect?.height ?? 0\n const [side, align] = options.placement.split('-') as [\n string,\n string | undefined,\n ]\n\n let top: number\n if (side === 'top') {\n top = anchorRect.top - options.offset - popoverHeight + window.scrollY\n } else {\n top = anchorRect.bottom + options.offset + window.scrollY\n }\n\n let left: number\n if (align === 'start') {\n left = anchorRect.left\n } else if (align === 'end') {\n left = anchorRect.right - popoverWidth\n } else {\n left = anchorRect.left + (anchorRect.width - popoverWidth) / 2\n }\n left += window.scrollX\n\n let maxWidth: number | undefined\n if (options.dynamicWidth === false) {\n left = inputRect.left + window.scrollX\n maxWidth = inputRect.width\n }\n\n return {\n // @ts-ignore\n position: 'fixed',\n top,\n left,\n maxWidth,\n }\n}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { type UseSiftReturn } from './useSift';
2
2
  export declare function useKeyboardHandling(_props: {
3
+ enabled?: boolean;
3
4
  sift: UseSiftReturn;
4
5
  onArrowDown: () => void;
5
6
  onArrowUp: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardHandling.d.ts","sourceRoot":"","sources":["../src/useKeyboardHandling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,WAAW,CAAA;AAE5C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,IAAI,EAAE,aAAa,CAAA;IACnB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB,QAAI"}
1
+ {"version":3,"file":"useKeyboardHandling.d.ts","sourceRoot":"","sources":["../src/useKeyboardHandling.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,WAAW,CAAA;AAE5C,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAC1C,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB,QAAI"}
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardHandling.js","sourceRoot":"","sources":["../src/useKeyboardHandling.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,MAQnC,IAAG,CAAC","sourcesContent":["import {type UseSiftReturn} from './useSift'\n\nexport function useKeyboardHandling(_props: {\n sift: UseSiftReturn\n onArrowDown: () => void\n onArrowUp: () => void\n onHome: () => void\n onEnd: () => void\n onSelect: () => void\n onDismiss?: () => void\n}) {}\n"]}
1
+ {"version":3,"file":"useKeyboardHandling.js","sourceRoot":"","sources":["../src/useKeyboardHandling.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,mBAAmB,CAAC,MASnC,IAAG,CAAC","sourcesContent":["import {type UseSiftReturn} from './useSift'\n\nexport function useKeyboardHandling(_props: {\n enabled?: boolean\n sift: UseSiftReturn\n onArrowDown: () => void\n onArrowUp: () => void\n onHome: () => void\n onEnd: () => void\n onSelect: () => void\n onDismiss?: () => void\n}) {}\n"]}
@@ -1,5 +1,6 @@
1
1
  import { type UseSiftReturn } from './useSift';
2
2
  export declare function useKeyboardHandling(props: {
3
+ enabled?: boolean;
3
4
  sift: UseSiftReturn;
4
5
  onArrowDown: () => void;
5
6
  onArrowUp: () => void;
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardHandling.web.d.ts","sourceRoot":"","sources":["../src/useKeyboardHandling.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,WAAW,CAAA;AAE5C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,IAAI,EAAE,aAAa,CAAA;IACnB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB,QA8CA"}
1
+ {"version":3,"file":"useKeyboardHandling.web.d.ts","sourceRoot":"","sources":["../src/useKeyboardHandling.web.ts"],"names":[],"mappings":"AAEA,OAAO,EAAC,KAAK,aAAa,EAAC,MAAM,WAAW,CAAA;AAE5C,wBAAgB,mBAAmB,CAAC,KAAK,EAAE;IACzC,OAAO,CAAC,EAAE,OAAO,CAAA;IACjB,IAAI,EAAE,aAAa,CAAA;IACnB,WAAW,EAAE,MAAM,IAAI,CAAA;IACvB,SAAS,EAAE,MAAM,IAAI,CAAA;IACrB,MAAM,EAAE,MAAM,IAAI,CAAA;IAClB,KAAK,EAAE,MAAM,IAAI,CAAA;IACjB,QAAQ,EAAE,MAAM,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,IAAI,CAAA;CACvB,QAkDA"}
@@ -3,11 +3,11 @@ export function useKeyboardHandling(props) {
3
3
  const callbacksRef = useRef(props);
4
4
  callbacksRef.current = props;
5
5
  useEffect(() => {
6
- const reference = props.sift.internal.elements.reference;
7
- if (!reference)
6
+ const input = props.sift.elements.input;
7
+ if (!input)
8
8
  return;
9
9
  function onKeyDown(e) {
10
- if (!callbacksRef.current.sift.internal.elements.floating)
10
+ if (!callbacksRef.current.sift.isActive())
11
11
  return;
12
12
  switch (e.key) {
13
13
  case 'ArrowDown':
@@ -37,10 +37,14 @@ export function useKeyboardHandling(props) {
37
37
  break;
38
38
  }
39
39
  }
40
- reference.addEventListener('keydown', onKeyDown);
40
+ if ('addEventListener' in input) {
41
+ input.addEventListener('keydown', onKeyDown);
42
+ }
41
43
  return () => {
42
- reference.removeEventListener('keydown', onKeyDown);
44
+ if ('removeEventListener' in input) {
45
+ input.removeEventListener('keydown', onKeyDown);
46
+ }
43
47
  };
44
- }, [props.sift.internal.elements.reference]);
48
+ }, [props.sift.elements.input]);
45
49
  }
46
50
  //# sourceMappingURL=useKeyboardHandling.web.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"useKeyboardHandling.web.js","sourceRoot":"","sources":["../src/useKeyboardHandling.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,MAAM,EAAC,MAAM,OAAO,CAAA;AAIvC,MAAM,UAAU,mBAAmB,CAAC,KAQnC;IACC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAClC,YAAY,CAAC,OAAO,GAAG,KAAK,CAAA;IAE5B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,SAAS,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAA;QACxD,IAAI,CAAC,SAAS;YAAE,OAAM;QAEtB,SAAS,SAAS,CAAC,CAAgB;YACjC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ;gBAAE,OAAM;YAEjE,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;gBACd,KAAK,WAAW;oBACd,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;oBAClC,MAAK;gBACP,KAAK,SAAS;oBACZ,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,CAAA;oBAChC,MAAK;gBACP,KAAK,OAAO,CAAC;gBACb,KAAK,KAAK;oBACR,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;oBAC/B,MAAK;gBACP,KAAK,MAAM;oBACT,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,CAAA;oBAC7B,MAAK;gBACP,KAAK,KAAK;oBACR,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;oBAC5B,MAAK;gBACP,KAAK,QAAQ;oBACX,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAA;oBAClC,MAAK;YACT,CAAC;QACH,CAAC;QAED,SAAS,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAEhD,OAAO,GAAG,EAAE;YACV,SAAS,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QACrD,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC,CAAA;AAC9C,CAAC","sourcesContent":["import {useEffect, useRef} from 'react'\n\nimport {type UseSiftReturn} from './useSift'\n\nexport function useKeyboardHandling(props: {\n sift: UseSiftReturn\n onArrowDown: () => void\n onArrowUp: () => void\n onHome: () => void\n onEnd: () => void\n onSelect: () => void\n onDismiss?: () => void\n}) {\n const callbacksRef = useRef(props)\n callbacksRef.current = props\n\n useEffect(() => {\n const reference = props.sift.internal.elements.reference\n if (!reference) return\n\n function onKeyDown(e: KeyboardEvent) {\n if (!callbacksRef.current.sift.internal.elements.floating) return\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n callbacksRef.current.onArrowDown()\n break\n case 'ArrowUp':\n e.preventDefault()\n callbacksRef.current.onArrowUp()\n break\n case 'Enter':\n case 'Tab':\n e.preventDefault()\n callbacksRef.current.onSelect()\n break\n case 'Home':\n e.preventDefault()\n callbacksRef.current.onHome()\n break\n case 'End':\n e.preventDefault()\n callbacksRef.current.onEnd()\n break\n case 'Escape':\n e.preventDefault()\n callbacksRef.current.onDismiss?.()\n break\n }\n }\n\n reference.addEventListener('keydown', onKeyDown)\n\n return () => {\n reference.removeEventListener('keydown', onKeyDown)\n }\n }, [props.sift.internal.elements.reference])\n}\n"]}
1
+ {"version":3,"file":"useKeyboardHandling.web.js","sourceRoot":"","sources":["../src/useKeyboardHandling.web.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,SAAS,EAAE,MAAM,EAAC,MAAM,OAAO,CAAA;AAIvC,MAAM,UAAU,mBAAmB,CAAC,KASnC;IACC,MAAM,YAAY,GAAG,MAAM,CAAC,KAAK,CAAC,CAAA;IAClC,YAAY,CAAC,OAAO,GAAG,KAAK,CAAA;IAE5B,SAAS,CAAC,GAAG,EAAE;QACb,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAA;QACvC,IAAI,CAAC,KAAK;YAAE,OAAM;QAElB,SAAS,SAAS,CAAC,CAAgB;YACjC,IAAI,CAAC,YAAY,CAAC,OAAO,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAAE,OAAM;YAEjD,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;gBACd,KAAK,WAAW;oBACd,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;oBAClC,MAAK;gBACP,KAAK,SAAS;oBACZ,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,CAAA;oBAChC,MAAK;gBACP,KAAK,OAAO,CAAC;gBACb,KAAK,KAAK;oBACR,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;oBAC/B,MAAK;gBACP,KAAK,MAAM;oBACT,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,CAAA;oBAC7B,MAAK;gBACP,KAAK,KAAK;oBACR,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;oBAC5B,MAAK;gBACP,KAAK,QAAQ;oBACX,CAAC,CAAC,cAAc,EAAE,CAAA;oBAClB,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAA;oBAClC,MAAK;YACT,CAAC;QACH,CAAC;QAED,IAAI,kBAAkB,IAAI,KAAK,EAAE,CAAC;YAChC,KAAK,CAAC,gBAAgB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;QAC9C,CAAC;QAED,OAAO,GAAG,EAAE;YACV,IAAI,qBAAqB,IAAI,KAAK,EAAE,CAAC;gBACnC,KAAK,CAAC,mBAAmB,CAAC,SAAS,EAAE,SAAS,CAAC,CAAA;YACjD,CAAC;QACH,CAAC,CAAA;IACH,CAAC,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAA;AACjC,CAAC","sourcesContent":["import {useEffect, useRef} from 'react'\n\nimport {type UseSiftReturn} from './useSift'\n\nexport function useKeyboardHandling(props: {\n enabled?: boolean\n sift: UseSiftReturn\n onArrowDown: () => void\n onArrowUp: () => void\n onHome: () => void\n onEnd: () => void\n onSelect: () => void\n onDismiss?: () => void\n}) {\n const callbacksRef = useRef(props)\n callbacksRef.current = props\n\n useEffect(() => {\n const input = props.sift.elements.input\n if (!input) return\n\n function onKeyDown(e: KeyboardEvent) {\n if (!callbacksRef.current.sift.isActive()) return\n\n switch (e.key) {\n case 'ArrowDown':\n e.preventDefault()\n callbacksRef.current.onArrowDown()\n break\n case 'ArrowUp':\n e.preventDefault()\n callbacksRef.current.onArrowUp()\n break\n case 'Enter':\n case 'Tab':\n e.preventDefault()\n callbacksRef.current.onSelect()\n break\n case 'Home':\n e.preventDefault()\n callbacksRef.current.onHome()\n break\n case 'End':\n e.preventDefault()\n callbacksRef.current.onEnd()\n break\n case 'Escape':\n e.preventDefault()\n callbacksRef.current.onDismiss?.()\n break\n }\n }\n\n if ('addEventListener' in input) {\n input.addEventListener('keydown', onKeyDown)\n }\n\n return () => {\n if ('removeEventListener' in input) {\n input.removeEventListener('keydown', onKeyDown)\n }\n }\n }, [props.sift.elements.input])\n}\n"]}
@@ -1,11 +1,23 @@
1
- import { type Placement } from '@floating-ui/react-native';
1
+ import { type ViewStyle } from 'react-native';
2
+ export type Placement = 'top' | 'top-start' | 'top-end' | 'bottom' | 'bottom-start' | 'bottom-end';
2
3
  export type UseSiftReturn = ReturnType<typeof useSift>;
3
- export declare function useSift({ offset: offsetValue, placement, }: {
4
+ export declare function useSift({ offset: offsetValue, placement, dynamicWidth, }?: {
4
5
  offset?: number;
5
6
  placement?: Placement;
7
+ dynamicWidth?: boolean;
6
8
  }): {
7
9
  id: string;
8
- internal: import("@floating-ui/react-native").UseFloatingReturn;
10
+ refs: {
11
+ setPopover: (node: any) => void;
12
+ setAnchor: (node: any) => void;
13
+ };
14
+ elements: {
15
+ input: any;
16
+ popover: any;
17
+ };
18
+ isActive(): boolean;
19
+ popoverStyles: ViewStyle;
20
+ updatePosition(): void;
9
21
  targetProps: {
10
22
  ref: (node: any) => void;
11
23
  role: "combobox";
@@ -1 +1 @@
1
- {"version":3,"file":"useSift.d.ts","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACf,MAAM,2BAA2B,CAAA;AAElC,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAEtD,wBAAgB,OAAO,CAAC,EACtB,MAAM,EAAE,WAAe,EACvB,SAAS,GACV,EAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,SAAS,CAAA;CACtB;;;;;;;;;;EAkBA"}
1
+ {"version":3,"file":"useSift.d.ts","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAG3C,MAAM,MAAM,SAAS,GACjB,KAAK,GACL,WAAW,GACX,SAAS,GACT,QAAQ,GACR,cAAc,GACd,YAAY,CAAA;AAEhB,MAAM,MAAM,aAAa,GAAG,UAAU,CAAC,OAAO,OAAO,CAAC,CAAA;AAEtD,wBAAgB,OAAO,CAAC,EACtB,MAAM,EAAE,WAAe,EACvB,SAAoB,EACpB,YAAoB,GACrB,GAAE;IACD,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,CAAC,EAAE,SAAS,CAAA;IACrB,YAAY,CAAC,EAAE,OAAO,CAAA;CAClB;;;2BA+CK,GAAG;0BASH,GAAG;;;;;;;;;;oBAf8B,GAAG;;;;;;EAgD9C"}
package/build/useSift.js CHANGED
@@ -1,19 +1,77 @@
1
- import { useId } from 'react';
2
- import { useFloating, shift, offset, } from '@floating-ui/react-native';
3
- export function useSift({ offset: offsetValue = 0, placement, }) {
1
+ import { useCallback, useId, useRef, useState } from 'react';
2
+ import { computeStyles } from './computeStyles';
3
+ export function useSift({ offset: offsetValue = 0, placement = 'bottom', dynamicWidth = false, } = {}) {
4
4
  const id = useId();
5
- const floating = useFloating({
6
- middleware: [shift(), offset(offsetValue)],
5
+ /*
6
+ * These are reactive values and need to remain in state
7
+ */
8
+ const [input, setInput] = useState(null);
9
+ const [popover, setPopover] = useState(null);
10
+ const [popoverStyles, setPopoverStyles] = useState({});
11
+ /*
12
+ * These are non-reactive values that we want to persist across renders
13
+ * without causing re-renders when they change, so we store them in refs.
14
+ */
15
+ const inputRef = useRef(null);
16
+ const popoverRef = useRef(null);
17
+ const anchorRef = useRef(null);
18
+ const options = useRef({
19
+ offset: offsetValue,
7
20
  placement,
21
+ dynamicWidth,
8
22
  });
23
+ options.current = {
24
+ offset: offsetValue,
25
+ placement,
26
+ dynamicWidth,
27
+ };
28
+ const update = useCallback(async (anchorNode) => {
29
+ if (!anchorNode || !inputRef.current)
30
+ return;
31
+ const styles = await computeStyles({
32
+ anchor: anchorNode,
33
+ input: inputRef.current,
34
+ popover: popoverRef.current,
35
+ }, options.current);
36
+ setPopoverStyles(styles);
37
+ }, []);
38
+ const handleSetInput = useCallback((node) => {
39
+ inputRef.current = node;
40
+ setInput(node);
41
+ }, []);
42
+ const handleSetPopover = useCallback((node) => {
43
+ popoverRef.current = node;
44
+ setPopover(node);
45
+ update(anchorRef.current);
46
+ }, [update]);
47
+ const handleSetAnchor = useCallback((node) => {
48
+ if (!node || node === anchorRef.current)
49
+ return;
50
+ anchorRef.current = node;
51
+ update(node);
52
+ }, [update]);
9
53
  return {
10
54
  id,
11
- internal: floating,
55
+ refs: {
56
+ setPopover: handleSetPopover,
57
+ setAnchor: handleSetAnchor,
58
+ },
59
+ elements: {
60
+ input,
61
+ popover,
62
+ },
63
+ isActive() {
64
+ return !!popover;
65
+ },
66
+ popoverStyles,
67
+ updatePosition() {
68
+ update(anchorRef.current);
69
+ },
12
70
  targetProps: {
13
- ref: floating.refs.setReference,
71
+ ref: handleSetInput,
14
72
  role: 'combobox',
15
73
  'aria-controls': id,
16
- 'aria-expanded': !!floating.elements.floating,
74
+ 'aria-expanded': !!popover,
17
75
  'aria-autocomplete': 'list',
18
76
  },
19
77
  };
@@ -1 +1 @@
1
- {"version":3,"file":"useSift.js","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,OAAO,CAAA;AAC3B,OAAO,EACL,WAAW,EACX,KAAK,EACL,MAAM,GAEP,MAAM,2BAA2B,CAAA;AAIlC,MAAM,UAAU,OAAO,CAAC,EACtB,MAAM,EAAE,WAAW,GAAG,CAAC,EACvB,SAAS,GAIV;IACC,MAAM,EAAE,GAAG,KAAK,EAAE,CAAA;IAClB,MAAM,QAAQ,GAAG,WAAW,CAAC;QAC3B,UAAU,EAAE,CAAC,KAAK,EAAE,EAAE,MAAM,CAAC,WAAW,CAAC,CAAC;QAC1C,SAAS;KACV,CAAC,CAAA;IAEF,OAAO;QACL,EAAE;QACF,QAAQ,EAAE,QAAQ;QAClB,WAAW,EAAE;YACX,GAAG,EAAE,QAAQ,CAAC,IAAI,CAAC,YAAY;YAC/B,IAAI,EAAE,UAAmB;YACzB,eAAe,EAAE,EAAE;YACnB,eAAe,EAAE,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ;YAC7C,mBAAmB,EAAE,MAAe;SACrC;KACF,CAAA;AACH,CAAC","sourcesContent":["import {useId} from 'react'\nimport {\n useFloating,\n shift,\n offset,\n type Placement,\n} from '@floating-ui/react-native'\n\nexport type UseSiftReturn = ReturnType<typeof useSift>\n\nexport function useSift({\n offset: offsetValue = 0,\n placement,\n}: {\n offset?: number\n placement?: Placement\n}) {\n const id = useId()\n const floating = useFloating({\n middleware: [shift(), offset(offsetValue)],\n placement,\n })\n\n return {\n id,\n internal: floating,\n targetProps: {\n ref: floating.refs.setReference,\n role: 'combobox' as const,\n 'aria-controls': id,\n 'aria-expanded': !!floating.elements.floating,\n 'aria-autocomplete': 'list' as const,\n },\n }\n}\n"]}
1
+ {"version":3,"file":"useSift.js","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"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","sourcesContent":["import {useCallback, useId, useRef, useState} from 'react'\nimport {type ViewStyle} from 'react-native'\nimport {computeStyles} from './computeStyles'\n\nexport type Placement =\n | 'top'\n | 'top-start'\n | 'top-end'\n | 'bottom'\n | 'bottom-start'\n | 'bottom-end'\n\nexport type UseSiftReturn = ReturnType<typeof useSift>\n\nexport function useSift({\n offset: offsetValue = 0,\n placement = 'bottom',\n dynamicWidth = false,\n}: {\n offset?: number\n placement?: Placement\n dynamicWidth?: boolean\n} = {}) {\n const id = useId()\n\n /*\n * These are reactive values and need to remain in state\n */\n const [input, setInput] = useState<any>(null)\n const [popover, setPopover] = useState<any>(null)\n const [popoverStyles, setPopoverStyles] = useState<ViewStyle>({})\n\n /*\n * These are non-reactive values that we want to persist across renders\n * without causing re-renders when they change, so we store them in refs.\n */\n const inputRef = useRef<any>(null)\n const popoverRef = useRef<any>(null)\n const anchorRef = useRef<any>(null)\n const options = useRef({\n offset: offsetValue,\n placement,\n dynamicWidth,\n })\n options.current = {\n offset: offsetValue,\n placement,\n dynamicWidth,\n }\n\n const update = useCallback(async (anchorNode: any) => {\n if (!anchorNode || !inputRef.current) return\n const styles = await computeStyles(\n {\n anchor: anchorNode,\n input: inputRef.current,\n popover: popoverRef.current,\n },\n options.current,\n )\n setPopoverStyles(styles)\n }, [])\n\n const handleSetInput = useCallback((node: any) => {\n inputRef.current = node\n setInput(node)\n }, [])\n\n const handleSetPopover = useCallback(\n (node: any) => {\n popoverRef.current = node\n setPopover(node)\n update(anchorRef.current)\n },\n [update],\n )\n\n const handleSetAnchor = useCallback(\n (node: any) => {\n if (!node || node === anchorRef.current) return\n anchorRef.current = node\n update(node)\n },\n [update],\n )\n\n return {\n id,\n refs: {\n setPopover: handleSetPopover,\n setAnchor: handleSetAnchor,\n },\n elements: {\n input,\n popover,\n },\n isActive() {\n return !!popover\n },\n popoverStyles,\n updatePosition() {\n update(anchorRef.current)\n },\n targetProps: {\n ref: handleSetInput,\n role: 'combobox' as const,\n 'aria-controls': id,\n 'aria-expanded': !!popover,\n 'aria-autocomplete': 'list' as const,\n },\n }\n}\n"]}
package/package.json CHANGED
@@ -1,36 +1,20 @@
1
1
  {
2
2
  "name": "@bsky.app/sift",
3
- "version": "0.1.0",
4
- "description": "My new module",
3
+ "version": "0.1.2",
4
+ "license": "MIT",
5
+ "description": "A little React Native library for building autocompletes.",
6
+ "repository": "https://github.com/bluesky-social/toolbox",
7
+ "author": "Eric Bailey <git@esb.lol> (https://github.com/estrattonbailey)",
8
+ "homepage": "https://github.com/bluesky-social/toolbox/tree/main/packages/sift",
5
9
  "main": "build/index.js",
6
10
  "types": "build/index.d.ts",
7
- "scripts": {
8
- "build": "expo-module build",
9
- "clean": "expo-module clean",
10
- "lint": "expo-module lint",
11
- "test": "vitest run",
12
- "prepare": "expo-module prepare",
13
- "prepublishOnly": "expo-module prepublishOnly",
14
- "expo-module": "expo-module",
15
- "open:ios": "xed example/ios",
16
- "open:android": "open -a \"Android Studio\" example/android"
17
- },
18
11
  "keywords": [
19
12
  "react-native",
20
13
  "expo",
21
14
  "sift",
22
15
  "Tapper"
23
16
  ],
24
- "repository": "https://github.com/estrattonbailey/sift",
25
- "bugs": {
26
- "url": "https://github.com/estrattonbailey/sift/issues"
27
- },
28
- "author": "Bluesky Social PBLLC <git@esb.lol> (https://github.com/estrattonbailey)",
29
- "license": "MIT",
30
- "homepage": "https://github.com/estrattonbailey/sift#readme",
31
- "dependencies": {
32
- "@floating-ui/react-native": "^0.10.9"
33
- },
17
+ "dependencies": {},
34
18
  "devDependencies": {
35
19
  "@types/react": "~19.1.1",
36
20
  "expo": "^55.0.8",
@@ -42,5 +26,14 @@
42
26
  "expo": "*",
43
27
  "react": "*",
44
28
  "react-native": "*"
29
+ },
30
+ "scripts": {
31
+ "build": "expo-module build",
32
+ "clean": "expo-module clean",
33
+ "lint": "expo-module lint",
34
+ "test": "vitest run",
35
+ "expo-module": "expo-module",
36
+ "open:ios": "xed example/ios",
37
+ "open:android": "open -a \"Android Studio\" example/android"
45
38
  }
46
- }
39
+ }
package/src/Sift.tsx CHANGED
@@ -5,6 +5,8 @@ import {
5
5
  Pressable,
6
6
  type StyleProp,
7
7
  type ViewStyle,
8
+ type PressableProps,
9
+ type Role,
8
10
  } from 'react-native'
9
11
  import {type UseSiftReturn} from './useSift'
10
12
  import {useKeyboardHandling} from './useKeyboardHandling'
@@ -22,7 +24,15 @@ export function Sift<Item extends {key: string}>({
22
24
  }: {
23
25
  sift: UseSiftReturn
24
26
  data: Item[]
25
- render: (props: {active: boolean; item: Item}) => React.ReactElement
27
+ render: (props: {
28
+ active: boolean
29
+ props: {
30
+ role: string
31
+ 'aria-selected': boolean
32
+ onPress: () => void
33
+ }
34
+ item: Item
35
+ }) => React.ReactElement
26
36
  style?: StyleProp<ViewStyle>
27
37
  onSelect?: (item: Item) => void
28
38
  onDismiss?: () => void
@@ -31,8 +41,8 @@ export function Sift<Item extends {key: string}>({
31
41
  const [activeIndex, setActiveIndex] = useState(0)
32
42
  const activeIndexRef = useRef(activeIndex)
33
43
  activeIndexRef.current = activeIndex
34
- const updateRef = useRef(sift.internal.update)
35
- updateRef.current = sift.internal.update
44
+ const updateRef = useRef(sift.updatePosition)
45
+ updateRef.current = sift.updatePosition
36
46
 
37
47
  useEffect(() => {
38
48
  setActiveIndex(0)
@@ -71,27 +81,53 @@ export function Sift<Item extends {key: string}>({
71
81
  return (
72
82
  <View
73
83
  collapsable={false}
74
- ref={sift.internal.refs.setFloating}
84
+ ref={sift.refs.setPopover}
75
85
  // @ts-ignore
76
86
  role="listbox"
77
87
  id={sift.id}
78
- style={[sift.internal.floatingStyles, style]}>
88
+ style={[style, sift.popoverStyles]}>
79
89
  <FlatList
80
90
  data={data}
81
91
  inverted={inverted}
82
92
  keyExtractor={item => item.key}
83
- renderItem={items => (
84
- <Pressable
85
- role="option"
86
- aria-selected={items.index === activeIndex}
87
- onPress={() => onSelect?.(items.item)}>
88
- {render({
89
- active: items.index === activeIndex,
90
- item: items.item,
91
- })}
92
- </Pressable>
93
- )}
93
+ renderItem={items =>
94
+ render({
95
+ active: items.index === activeIndex,
96
+ props: {
97
+ role: 'option',
98
+ 'aria-selected': items.index === activeIndex,
99
+ onPress: () => onSelect?.(items.item),
100
+ },
101
+ item: items.item,
102
+ })
103
+ }
94
104
  />
95
105
  </View>
96
106
  )
97
107
  }
108
+
109
+ /**
110
+ * A Pressable wrapper for items rendered in Sift. It applies the necessary
111
+ * accessibility props for each item.
112
+ */
113
+ export function SiftItem({
114
+ children,
115
+ role,
116
+ style,
117
+ ...props
118
+ }: Omit<PressableProps, 'role' | 'style'> & {
119
+ role?: string
120
+ style?:
121
+ | StyleProp<ViewStyle>
122
+ | ((state: {pressed: boolean; hovered: boolean}) => StyleProp<ViewStyle>)
123
+ | undefined
124
+ }) {
125
+ return (
126
+ <Pressable
127
+ role={role as Role}
128
+ style={style as PressableProps['style']}
129
+ {...props}>
130
+ {children}
131
+ </Pressable>
132
+ )
133
+ }
@@ -0,0 +1,70 @@
1
+ import {type ViewStyle} from 'react-native'
2
+ import {type Placement} from './useSift'
3
+
4
+ function measureInWindow(
5
+ node: any,
6
+ ): Promise<{x: number; y: number; width: number; height: number}> {
7
+ return new Promise(resolve => {
8
+ node.measureInWindow(
9
+ (x: number, y: number, width: number, height: number) => {
10
+ resolve({x, y, width, height})
11
+ },
12
+ )
13
+ })
14
+ }
15
+
16
+ export async function computeStyles(
17
+ {
18
+ anchor,
19
+ input,
20
+ popover,
21
+ }: {
22
+ anchor: any
23
+ input: any
24
+ popover: any | null
25
+ },
26
+ options: {
27
+ offset: number
28
+ placement: Placement
29
+ dynamicWidth?: boolean
30
+ },
31
+ ): Promise<ViewStyle> {
32
+ const anchorRect = await measureInWindow(anchor)
33
+ const inputRect = await measureInWindow(input)
34
+ const popoverRect = popover ? await measureInWindow(popover) : null
35
+ const popoverWidth = popoverRect?.width ?? 0
36
+ const popoverHeight = popoverRect?.height ?? 0
37
+ const [side, align] = options.placement.split('-') as [
38
+ string,
39
+ string | undefined,
40
+ ]
41
+
42
+ let top: number
43
+ if (side === 'top') {
44
+ top = anchorRect.y - options.offset - popoverHeight
45
+ } else {
46
+ top = anchorRect.y + anchorRect.height + options.offset
47
+ }
48
+
49
+ let left: number
50
+ if (align === 'start') {
51
+ left = anchorRect.x
52
+ } else if (align === 'end') {
53
+ left = anchorRect.x + anchorRect.width - popoverWidth
54
+ } else {
55
+ left = anchorRect.x + (anchorRect.width - popoverWidth) / 2
56
+ }
57
+
58
+ let maxWidth: number | undefined
59
+ if (options.dynamicWidth === false) {
60
+ left = inputRect.x
61
+ maxWidth = inputRect.width
62
+ }
63
+
64
+ return {
65
+ position: 'absolute',
66
+ top,
67
+ left,
68
+ maxWidth,
69
+ }
70
+ }
@@ -0,0 +1,60 @@
1
+ import {type ViewStyle} from 'react-native'
2
+ import {type Placement} from './useSift'
3
+
4
+ export function computeStyles(
5
+ {
6
+ anchor,
7
+ input,
8
+ popover,
9
+ }: {
10
+ anchor: HTMLElement
11
+ input: HTMLElement
12
+ popover: HTMLElement | null
13
+ },
14
+ options: {
15
+ offset: number
16
+ placement: Placement
17
+ dynamicWidth?: boolean
18
+ },
19
+ ): ViewStyle {
20
+ const anchorRect = anchor.getBoundingClientRect()
21
+ const inputRect = input.getBoundingClientRect()
22
+ const popoverRect = popover?.getBoundingClientRect()
23
+ const popoverWidth = popoverRect?.width ?? 0
24
+ const popoverHeight = popoverRect?.height ?? 0
25
+ const [side, align] = options.placement.split('-') as [
26
+ string,
27
+ string | undefined,
28
+ ]
29
+
30
+ let top: number
31
+ if (side === 'top') {
32
+ top = anchorRect.top - options.offset - popoverHeight + window.scrollY
33
+ } else {
34
+ top = anchorRect.bottom + options.offset + window.scrollY
35
+ }
36
+
37
+ let left: number
38
+ if (align === 'start') {
39
+ left = anchorRect.left
40
+ } else if (align === 'end') {
41
+ left = anchorRect.right - popoverWidth
42
+ } else {
43
+ left = anchorRect.left + (anchorRect.width - popoverWidth) / 2
44
+ }
45
+ left += window.scrollX
46
+
47
+ let maxWidth: number | undefined
48
+ if (options.dynamicWidth === false) {
49
+ left = inputRect.left + window.scrollX
50
+ maxWidth = inputRect.width
51
+ }
52
+
53
+ return {
54
+ // @ts-ignore
55
+ position: 'fixed',
56
+ top,
57
+ left,
58
+ maxWidth,
59
+ }
60
+ }
@@ -1,6 +1,7 @@
1
1
  import {type UseSiftReturn} from './useSift'
2
2
 
3
3
  export function useKeyboardHandling(_props: {
4
+ enabled?: boolean
4
5
  sift: UseSiftReturn
5
6
  onArrowDown: () => void
6
7
  onArrowUp: () => void
@@ -3,6 +3,7 @@ import {useEffect, useRef} from 'react'
3
3
  import {type UseSiftReturn} from './useSift'
4
4
 
5
5
  export function useKeyboardHandling(props: {
6
+ enabled?: boolean
6
7
  sift: UseSiftReturn
7
8
  onArrowDown: () => void
8
9
  onArrowUp: () => void
@@ -15,11 +16,11 @@ export function useKeyboardHandling(props: {
15
16
  callbacksRef.current = props
16
17
 
17
18
  useEffect(() => {
18
- const reference = props.sift.internal.elements.reference
19
- if (!reference) return
19
+ const input = props.sift.elements.input
20
+ if (!input) return
20
21
 
21
22
  function onKeyDown(e: KeyboardEvent) {
22
- if (!callbacksRef.current.sift.internal.elements.floating) return
23
+ if (!callbacksRef.current.sift.isActive()) return
23
24
 
24
25
  switch (e.key) {
25
26
  case 'ArrowDown':
@@ -50,10 +51,14 @@ export function useKeyboardHandling(props: {
50
51
  }
51
52
  }
52
53
 
53
- reference.addEventListener('keydown', onKeyDown)
54
+ if ('addEventListener' in input) {
55
+ input.addEventListener('keydown', onKeyDown)
56
+ }
54
57
 
55
58
  return () => {
56
- reference.removeEventListener('keydown', onKeyDown)
59
+ if ('removeEventListener' in input) {
60
+ input.removeEventListener('keydown', onKeyDown)
61
+ }
57
62
  }
58
- }, [props.sift.internal.elements.reference])
63
+ }, [props.sift.elements.input])
59
64
  }
package/src/useSift.ts CHANGED
@@ -1,34 +1,111 @@
1
- import {useId} from 'react'
2
- import {
3
- useFloating,
4
- shift,
5
- offset,
6
- type Placement,
7
- } from '@floating-ui/react-native'
1
+ import {useCallback, useId, useRef, useState} from 'react'
2
+ import {type ViewStyle} from 'react-native'
3
+ import {computeStyles} from './computeStyles'
4
+
5
+ export type Placement =
6
+ | 'top'
7
+ | 'top-start'
8
+ | 'top-end'
9
+ | 'bottom'
10
+ | 'bottom-start'
11
+ | 'bottom-end'
8
12
 
9
13
  export type UseSiftReturn = ReturnType<typeof useSift>
10
14
 
11
15
  export function useSift({
12
16
  offset: offsetValue = 0,
13
- placement,
17
+ placement = 'bottom',
18
+ dynamicWidth = false,
14
19
  }: {
15
20
  offset?: number
16
21
  placement?: Placement
17
- }) {
22
+ dynamicWidth?: boolean
23
+ } = {}) {
18
24
  const id = useId()
19
- const floating = useFloating({
20
- middleware: [shift(), offset(offsetValue)],
25
+
26
+ /*
27
+ * These are reactive values and need to remain in state
28
+ */
29
+ const [input, setInput] = useState<any>(null)
30
+ const [popover, setPopover] = useState<any>(null)
31
+ const [popoverStyles, setPopoverStyles] = useState<ViewStyle>({})
32
+
33
+ /*
34
+ * These are non-reactive values that we want to persist across renders
35
+ * without causing re-renders when they change, so we store them in refs.
36
+ */
37
+ const inputRef = useRef<any>(null)
38
+ const popoverRef = useRef<any>(null)
39
+ const anchorRef = useRef<any>(null)
40
+ const options = useRef({
41
+ offset: offsetValue,
21
42
  placement,
43
+ dynamicWidth,
22
44
  })
45
+ options.current = {
46
+ offset: offsetValue,
47
+ placement,
48
+ dynamicWidth,
49
+ }
50
+
51
+ const update = useCallback(async (anchorNode: any) => {
52
+ if (!anchorNode || !inputRef.current) return
53
+ const styles = await computeStyles(
54
+ {
55
+ anchor: anchorNode,
56
+ input: inputRef.current,
57
+ popover: popoverRef.current,
58
+ },
59
+ options.current,
60
+ )
61
+ setPopoverStyles(styles)
62
+ }, [])
63
+
64
+ const handleSetInput = useCallback((node: any) => {
65
+ inputRef.current = node
66
+ setInput(node)
67
+ }, [])
68
+
69
+ const handleSetPopover = useCallback(
70
+ (node: any) => {
71
+ popoverRef.current = node
72
+ setPopover(node)
73
+ update(anchorRef.current)
74
+ },
75
+ [update],
76
+ )
77
+
78
+ const handleSetAnchor = useCallback(
79
+ (node: any) => {
80
+ if (!node || node === anchorRef.current) return
81
+ anchorRef.current = node
82
+ update(node)
83
+ },
84
+ [update],
85
+ )
23
86
 
24
87
  return {
25
88
  id,
26
- internal: floating,
89
+ refs: {
90
+ setPopover: handleSetPopover,
91
+ setAnchor: handleSetAnchor,
92
+ },
93
+ elements: {
94
+ input,
95
+ popover,
96
+ },
97
+ isActive() {
98
+ return !!popover
99
+ },
100
+ popoverStyles,
101
+ updatePosition() {
102
+ update(anchorRef.current)
103
+ },
27
104
  targetProps: {
28
- ref: floating.refs.setReference,
105
+ ref: handleSetInput,
29
106
  role: 'combobox' as const,
30
107
  'aria-controls': id,
31
- 'aria-expanded': !!floating.elements.floating,
108
+ 'aria-expanded': !!popover,
32
109
  'aria-autocomplete': 'list' as const,
33
110
  },
34
111
  }