@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 +13 -0
- package/build/Sift.d.ts +17 -1
- package/build/Sift.d.ts.map +1 -1
- package/build/Sift.js +22 -10
- package/build/Sift.js.map +1 -1
- package/build/computeStyles.d.ts +12 -0
- package/build/computeStyles.d.ts.map +1 -0
- package/build/computeStyles.js +44 -0
- package/build/computeStyles.js.map +1 -0
- package/build/computeStyles.web.d.ts +12 -0
- package/build/computeStyles.web.d.ts.map +1 -0
- package/build/computeStyles.web.js +39 -0
- package/build/computeStyles.web.js.map +1 -0
- package/build/useKeyboardHandling.d.ts +1 -0
- package/build/useKeyboardHandling.d.ts.map +1 -1
- package/build/useKeyboardHandling.js.map +1 -1
- package/build/useKeyboardHandling.web.d.ts +1 -0
- package/build/useKeyboardHandling.web.d.ts.map +1 -1
- package/build/useKeyboardHandling.web.js +10 -6
- package/build/useKeyboardHandling.web.js.map +1 -1
- package/build/useSift.d.ts +15 -3
- package/build/useSift.d.ts.map +1 -1
- package/build/useSift.js +66 -8
- package/build/useSift.js.map +1 -1
- package/package.json +17 -24
- package/src/Sift.tsx +52 -16
- package/src/computeStyles.ts +70 -0
- package/src/computeStyles.web.ts +60 -0
- package/src/useKeyboardHandling.ts +1 -0
- package/src/useKeyboardHandling.web.ts +11 -6
- package/src/useSift.ts +91 -14
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
|
package/build/Sift.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Sift.d.ts","sourceRoot":"","sources":["../src/Sift.tsx"],"names":[],"mappings":"AACA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,SAAS,
|
|
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.
|
|
10
|
-
updateRef.current = sift.
|
|
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.
|
|
46
|
+
return (<View collapsable={false} ref={sift.refs.setPopover}
|
|
47
47
|
// @ts-ignore
|
|
48
|
-
role="listbox" id={sift.id} style={[sift.
|
|
49
|
-
<FlatList data={data} inverted={inverted} keyExtractor={item => item.key} renderItem={items => (
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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":"AAAA,OAAO,EAAC,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAC,MAAM,OAAO,CAAA;AACjD,OAAO,EACL,IAAI,EACJ,QAAQ,EACR,SAAS,
|
|
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 +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,
|
|
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 +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,
|
|
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
|
|
7
|
-
if (!
|
|
6
|
+
const input = props.sift.elements.input;
|
|
7
|
+
if (!input)
|
|
8
8
|
return;
|
|
9
9
|
function onKeyDown(e) {
|
|
10
|
-
if (!callbacksRef.current.sift.
|
|
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
|
-
|
|
40
|
+
if ('addEventListener' in input) {
|
|
41
|
+
input.addEventListener('keydown', onKeyDown);
|
|
42
|
+
}
|
|
41
43
|
return () => {
|
|
42
|
-
|
|
44
|
+
if ('removeEventListener' in input) {
|
|
45
|
+
input.removeEventListener('keydown', onKeyDown);
|
|
46
|
+
}
|
|
43
47
|
};
|
|
44
|
-
}, [props.sift.
|
|
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,
|
|
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"]}
|
package/build/useSift.d.ts
CHANGED
|
@@ -1,11 +1,23 @@
|
|
|
1
|
-
import { type
|
|
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
|
-
|
|
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";
|
package/build/useSift.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSift.d.ts","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"AACA,OAAO,
|
|
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 {
|
|
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
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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:
|
|
71
|
+
ref: handleSetInput,
|
|
14
72
|
role: 'combobox',
|
|
15
73
|
'aria-controls': id,
|
|
16
|
-
'aria-expanded': !!
|
|
74
|
+
'aria-expanded': !!popover,
|
|
17
75
|
'aria-autocomplete': 'list',
|
|
18
76
|
},
|
|
19
77
|
};
|
package/build/useSift.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useSift.js","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,OAAO,CAAA;
|
|
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.
|
|
4
|
-
"
|
|
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
|
-
"
|
|
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: {
|
|
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.
|
|
35
|
-
updateRef.current = sift.
|
|
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.
|
|
84
|
+
ref={sift.refs.setPopover}
|
|
75
85
|
// @ts-ignore
|
|
76
86
|
role="listbox"
|
|
77
87
|
id={sift.id}
|
|
78
|
-
style={[sift.
|
|
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
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
+
}
|
|
@@ -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
|
|
19
|
-
if (!
|
|
19
|
+
const input = props.sift.elements.input
|
|
20
|
+
if (!input) return
|
|
20
21
|
|
|
21
22
|
function onKeyDown(e: KeyboardEvent) {
|
|
22
|
-
if (!callbacksRef.current.sift.
|
|
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
|
-
|
|
54
|
+
if ('addEventListener' in input) {
|
|
55
|
+
input.addEventListener('keydown', onKeyDown)
|
|
56
|
+
}
|
|
54
57
|
|
|
55
58
|
return () => {
|
|
56
|
-
|
|
59
|
+
if ('removeEventListener' in input) {
|
|
60
|
+
input.removeEventListener('keydown', onKeyDown)
|
|
61
|
+
}
|
|
57
62
|
}
|
|
58
|
-
}, [props.sift.
|
|
63
|
+
}, [props.sift.elements.input])
|
|
59
64
|
}
|
package/src/useSift.ts
CHANGED
|
@@ -1,34 +1,111 @@
|
|
|
1
|
-
import {useId} from 'react'
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
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
|
-
|
|
20
|
-
|
|
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
|
-
|
|
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:
|
|
105
|
+
ref: handleSetInput,
|
|
29
106
|
role: 'combobox' as const,
|
|
30
107
|
'aria-controls': id,
|
|
31
|
-
'aria-expanded': !!
|
|
108
|
+
'aria-expanded': !!popover,
|
|
32
109
|
'aria-autocomplete': 'list' as const,
|
|
33
110
|
},
|
|
34
111
|
}
|