@bsky.app/sift 0.2.0 → 0.2.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 CHANGED
@@ -1,5 +1,19 @@
1
1
  # @bsky.app/sift
2
2
 
3
+ ## 0.2.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [`5436104`](https://github.com/bluesky-social/toolbox/commit/54361043995ce4cbdefd06d40f0bf8b4ed6f3c4f) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Stop event propogation for handled keyboard events
8
+
9
+ ## 0.2.1
10
+
11
+ ### Patch Changes
12
+
13
+ - [`27b4748`](https://github.com/bluesky-social/toolbox/commit/27b4748ea3c9b2ed45b7979f0431b6eed2b908d3) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Misc fixes
14
+
15
+ - [`58e33f1`](https://github.com/bluesky-social/toolbox/commit/58e33f149c70db7583b65c8edb1bebc3c3075c63) Thanks [@estrattonbailey](https://github.com/estrattonbailey)! - Fall back to input ref if anchor ref is not set
16
+
3
17
  ## 0.2.0
4
18
 
5
19
  ### Minor Changes
@@ -8,5 +8,5 @@ export declare function computeStyles({ anchor, input, popover, }: {
8
8
  offset: number;
9
9
  placement: Placement;
10
10
  dynamicWidth?: boolean;
11
- }): Promise<ViewStyle>;
11
+ }): Promise<ViewStyle | null>;
12
12
  //# sourceMappingURL=computeStyles.d.ts.map
@@ -1 +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"}
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,GAAG,IAAI,CAAC,CA6C3B"}
@@ -9,6 +9,12 @@ export async function computeStyles({ anchor, input, popover, }, options) {
9
9
  const anchorRect = await measureInWindow(anchor);
10
10
  const inputRect = await measureInWindow(input);
11
11
  const popoverRect = popover ? await measureInWindow(popover) : null;
12
+ // If any measurement failed (view not in hierarchy yet), return null
13
+ // so the caller keeps the previous styles.
14
+ if (!anchorRect.width || !inputRect.width)
15
+ return null;
16
+ if (popoverRect && !popoverRect.width && !popoverRect.height)
17
+ return null;
12
18
  const popoverWidth = popoverRect?.width ?? 0;
13
19
  const popoverHeight = popoverRect?.height ?? 0;
14
20
  const [side, align] = options.placement.split('-');
@@ -1 +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"]}
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;IAEnE,qEAAqE;IACrE,2CAA2C;IAC3C,IAAI,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC,SAAS,CAAC,KAAK;QAAE,OAAO,IAAI,CAAA;IACtD,IAAI,WAAW,IAAI,CAAC,WAAW,CAAC,KAAK,IAAI,CAAC,WAAW,CAAC,MAAM;QAAE,OAAO,IAAI,CAAA;IAEzE,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 | null> {\n const anchorRect = await measureInWindow(anchor)\n const inputRect = await measureInWindow(input)\n const popoverRect = popover ? await measureInWindow(popover) : null\n\n // If any measurement failed (view not in hierarchy yet), return null\n // so the caller keeps the previous styles.\n if (!anchorRect.width || !inputRect.width) return null\n if (popoverRect && !popoverRect.width && !popoverRect.height) return null\n\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"]}
@@ -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,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"}
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,QAyDA"}
@@ -9,33 +9,38 @@ export function useKeyboardHandling(props) {
9
9
  function onKeyDown(e) {
10
10
  if (!callbacksRef.current.sift.isActive())
11
11
  return;
12
+ let handled = false;
12
13
  switch (e.key) {
13
14
  case 'ArrowDown':
14
- e.preventDefault();
15
+ handled = true;
15
16
  callbacksRef.current.onArrowDown();
16
17
  break;
17
18
  case 'ArrowUp':
18
- e.preventDefault();
19
+ handled = true;
19
20
  callbacksRef.current.onArrowUp();
20
21
  break;
21
22
  case 'Enter':
22
23
  case 'Tab':
23
- e.preventDefault();
24
+ handled = true;
24
25
  callbacksRef.current.onSelect();
25
26
  break;
26
27
  case 'Home':
27
- e.preventDefault();
28
+ handled = true;
28
29
  callbacksRef.current.onHome();
29
30
  break;
30
31
  case 'End':
31
- e.preventDefault();
32
+ handled = true;
32
33
  callbacksRef.current.onEnd();
33
34
  break;
34
35
  case 'Escape':
35
- e.preventDefault();
36
+ handled = true;
36
37
  callbacksRef.current.onDismiss?.();
37
38
  break;
38
39
  }
40
+ if (handled) {
41
+ e.stopPropagation();
42
+ e.preventDefault();
43
+ }
39
44
  }
40
45
  if ('addEventListener' in input) {
41
46
  input.addEventListener('keydown', onKeyDown);
@@ -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,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
+ {"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,IAAI,OAAO,GAAG,KAAK,CAAA;YAEnB,QAAQ,CAAC,CAAC,GAAG,EAAE,CAAC;gBACd,KAAK,WAAW;oBACd,OAAO,GAAG,IAAI,CAAA;oBACd,YAAY,CAAC,OAAO,CAAC,WAAW,EAAE,CAAA;oBAClC,MAAK;gBACP,KAAK,SAAS;oBACZ,OAAO,GAAG,IAAI,CAAA;oBACd,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,CAAA;oBAChC,MAAK;gBACP,KAAK,OAAO,CAAC;gBACb,KAAK,KAAK;oBACR,OAAO,GAAG,IAAI,CAAA;oBACd,YAAY,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAA;oBAC/B,MAAK;gBACP,KAAK,MAAM;oBACT,OAAO,GAAG,IAAI,CAAA;oBACd,YAAY,CAAC,OAAO,CAAC,MAAM,EAAE,CAAA;oBAC7B,MAAK;gBACP,KAAK,KAAK;oBACR,OAAO,GAAG,IAAI,CAAA;oBACd,YAAY,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;oBAC5B,MAAK;gBACP,KAAK,QAAQ;oBACX,OAAO,GAAG,IAAI,CAAA;oBACd,YAAY,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAA;oBAClC,MAAK;YACT,CAAC;YAED,IAAI,OAAO,EAAE,CAAC;gBACZ,CAAC,CAAC,eAAe,EAAE,CAAA;gBACnB,CAAC,CAAC,cAAc,EAAE,CAAA;YACpB,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 let handled = false\n\n switch (e.key) {\n case 'ArrowDown':\n handled = true\n callbacksRef.current.onArrowDown()\n break\n case 'ArrowUp':\n handled = true\n callbacksRef.current.onArrowUp()\n break\n case 'Enter':\n case 'Tab':\n handled = true\n callbacksRef.current.onSelect()\n break\n case 'Home':\n handled = true\n callbacksRef.current.onHome()\n break\n case 'End':\n handled = true\n callbacksRef.current.onEnd()\n break\n case 'Escape':\n handled = true\n callbacksRef.current.onDismiss?.()\n break\n }\n\n if (handled) {\n e.stopPropagation()\n e.preventDefault()\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 +1 @@
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;0BAaH,GAAG;;;;;;;;;;oBAnB8B,GAAG;;;;;;EAoD9C"}
1
+ {"version":3,"file":"useSift.d.ts","sourceRoot":"","sources":["../src/useSift.ts"],"names":[],"mappings":"AACA,OAAO,EAAC,KAAK,SAAS,EAAC,MAAM,cAAc,CAAA;AAK3C,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;;;2BAiDK,GAAG;0BAaH,GAAG;;;;;;;;;;oBAnB8B,GAAG;;;;;;EAmD9C"}
package/build/useSift.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { useCallback, useId, useRef, useState } from 'react';
2
2
  import { computeStyles } from './computeStyles';
3
+ const DEFAULT_POPOVER_STYLES = { position: 'absolute' };
3
4
  export function useSift({ offset: offsetValue = 0, placement = 'bottom', dynamicWidth = false, } = {}) {
4
5
  const id = useId();
5
6
  /*
@@ -7,7 +8,7 @@ export function useSift({ offset: offsetValue = 0, placement = 'bottom', dynamic
7
8
  */
8
9
  const [input, setInput] = useState(null);
9
10
  const [popover, setPopover] = useState(null);
10
- const [popoverStyles, setPopoverStyles] = useState({});
11
+ const [popoverStyles, setPopoverStyles] = useState(DEFAULT_POPOVER_STYLES);
11
12
  /*
12
13
  * These are non-reactive values that we want to persist across renders
13
14
  * without causing re-renders when they change, so we store them in refs.
@@ -25,15 +26,16 @@ export function useSift({ offset: offsetValue = 0, placement = 'bottom', dynamic
25
26
  placement,
26
27
  dynamicWidth,
27
28
  };
28
- const update = useCallback(async (anchorNode) => {
29
- if (!anchorNode || !inputRef.current)
29
+ const update = useCallback(async () => {
30
+ if (!inputRef.current || !popoverRef.current)
30
31
  return;
31
32
  const styles = await computeStyles({
32
- anchor: anchorNode,
33
+ anchor: anchorRef.current || inputRef.current,
33
34
  input: inputRef.current,
34
35
  popover: popoverRef.current,
35
36
  }, options.current);
36
- setPopoverStyles(styles);
37
+ if (styles)
38
+ setPopoverStyles(styles);
37
39
  }, []);
38
40
  const handleSetInput = useCallback((node) => {
39
41
  inputRef.current = node;
@@ -43,17 +45,16 @@ export function useSift({ offset: offsetValue = 0, placement = 'bottom', dynamic
43
45
  popoverRef.current = node;
44
46
  setPopover(node);
45
47
  if (node) {
46
- update(anchorRef.current);
48
+ update();
47
49
  }
48
50
  else {
49
- setPopoverStyles({});
51
+ setPopoverStyles(DEFAULT_POPOVER_STYLES);
50
52
  }
51
53
  }, [update]);
52
54
  const handleSetAnchor = useCallback((node) => {
53
- if (!node || node === anchorRef.current)
54
- return;
55
55
  anchorRef.current = node;
56
- update(node);
56
+ if (node)
57
+ update();
57
58
  }, [update]);
58
59
  return {
59
60
  id,
@@ -70,7 +71,7 @@ export function useSift({ offset: offsetValue = 0, placement = 'bottom', dynamic
70
71
  },
71
72
  popoverStyles,
72
73
  updatePosition() {
73
- update(anchorRef.current);
74
+ update();
74
75
  },
75
76
  targetProps: {
76
77
  ref: handleSetInput,
@@ -1 +1 @@
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 if (node) {\n update(anchorRef.current)\n } else {\n setPopoverStyles({})\n }\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"]}
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\nconst DEFAULT_POPOVER_STYLES: ViewStyle = {position: 'absolute'}\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 DEFAULT_POPOVER_STYLES,\n )\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 () => {\n if (!inputRef.current || !popoverRef.current) return\n const styles = await computeStyles(\n {\n anchor: anchorRef.current || inputRef.current,\n input: inputRef.current,\n popover: popoverRef.current,\n },\n options.current,\n )\n if (styles) 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 if (node) {\n update()\n } else {\n setPopoverStyles(DEFAULT_POPOVER_STYLES)\n }\n },\n [update],\n )\n\n const handleSetAnchor = useCallback(\n (node: any) => {\n anchorRef.current = node\n if (node) update()\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()\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,6 +1,6 @@
1
1
  {
2
2
  "name": "@bsky.app/sift",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "license": "MIT",
5
5
  "description": "A little React Native library for building autocompletes.",
6
6
  "repository": "https://github.com/bluesky-social/toolbox",
@@ -28,10 +28,16 @@ export async function computeStyles(
28
28
  placement: Placement
29
29
  dynamicWidth?: boolean
30
30
  },
31
- ): Promise<ViewStyle> {
31
+ ): Promise<ViewStyle | null> {
32
32
  const anchorRect = await measureInWindow(anchor)
33
33
  const inputRect = await measureInWindow(input)
34
34
  const popoverRect = popover ? await measureInWindow(popover) : null
35
+
36
+ // If any measurement failed (view not in hierarchy yet), return null
37
+ // so the caller keeps the previous styles.
38
+ if (!anchorRect.width || !inputRect.width) return null
39
+ if (popoverRect && !popoverRect.width && !popoverRect.height) return null
40
+
35
41
  const popoverWidth = popoverRect?.width ?? 0
36
42
  const popoverHeight = popoverRect?.height ?? 0
37
43
  const [side, align] = options.placement.split('-') as [
@@ -22,33 +22,40 @@ export function useKeyboardHandling(props: {
22
22
  function onKeyDown(e: KeyboardEvent) {
23
23
  if (!callbacksRef.current.sift.isActive()) return
24
24
 
25
+ let handled = false
26
+
25
27
  switch (e.key) {
26
28
  case 'ArrowDown':
27
- e.preventDefault()
29
+ handled = true
28
30
  callbacksRef.current.onArrowDown()
29
31
  break
30
32
  case 'ArrowUp':
31
- e.preventDefault()
33
+ handled = true
32
34
  callbacksRef.current.onArrowUp()
33
35
  break
34
36
  case 'Enter':
35
37
  case 'Tab':
36
- e.preventDefault()
38
+ handled = true
37
39
  callbacksRef.current.onSelect()
38
40
  break
39
41
  case 'Home':
40
- e.preventDefault()
42
+ handled = true
41
43
  callbacksRef.current.onHome()
42
44
  break
43
45
  case 'End':
44
- e.preventDefault()
46
+ handled = true
45
47
  callbacksRef.current.onEnd()
46
48
  break
47
49
  case 'Escape':
48
- e.preventDefault()
50
+ handled = true
49
51
  callbacksRef.current.onDismiss?.()
50
52
  break
51
53
  }
54
+
55
+ if (handled) {
56
+ e.stopPropagation()
57
+ e.preventDefault()
58
+ }
52
59
  }
53
60
 
54
61
  if ('addEventListener' in input) {
package/src/useSift.ts CHANGED
@@ -2,6 +2,8 @@ import {useCallback, useId, useRef, useState} from 'react'
2
2
  import {type ViewStyle} from 'react-native'
3
3
  import {computeStyles} from './computeStyles'
4
4
 
5
+ const DEFAULT_POPOVER_STYLES: ViewStyle = {position: 'absolute'}
6
+
5
7
  export type Placement =
6
8
  | 'top'
7
9
  | 'top-start'
@@ -28,7 +30,9 @@ export function useSift({
28
30
  */
29
31
  const [input, setInput] = useState<any>(null)
30
32
  const [popover, setPopover] = useState<any>(null)
31
- const [popoverStyles, setPopoverStyles] = useState<ViewStyle>({})
33
+ const [popoverStyles, setPopoverStyles] = useState<ViewStyle>(
34
+ DEFAULT_POPOVER_STYLES,
35
+ )
32
36
 
33
37
  /*
34
38
  * These are non-reactive values that we want to persist across renders
@@ -48,17 +52,17 @@ export function useSift({
48
52
  dynamicWidth,
49
53
  }
50
54
 
51
- const update = useCallback(async (anchorNode: any) => {
52
- if (!anchorNode || !inputRef.current) return
55
+ const update = useCallback(async () => {
56
+ if (!inputRef.current || !popoverRef.current) return
53
57
  const styles = await computeStyles(
54
58
  {
55
- anchor: anchorNode,
59
+ anchor: anchorRef.current || inputRef.current,
56
60
  input: inputRef.current,
57
61
  popover: popoverRef.current,
58
62
  },
59
63
  options.current,
60
64
  )
61
- setPopoverStyles(styles)
65
+ if (styles) setPopoverStyles(styles)
62
66
  }, [])
63
67
 
64
68
  const handleSetInput = useCallback((node: any) => {
@@ -71,9 +75,9 @@ export function useSift({
71
75
  popoverRef.current = node
72
76
  setPopover(node)
73
77
  if (node) {
74
- update(anchorRef.current)
78
+ update()
75
79
  } else {
76
- setPopoverStyles({})
80
+ setPopoverStyles(DEFAULT_POPOVER_STYLES)
77
81
  }
78
82
  },
79
83
  [update],
@@ -81,9 +85,8 @@ export function useSift({
81
85
 
82
86
  const handleSetAnchor = useCallback(
83
87
  (node: any) => {
84
- if (!node || node === anchorRef.current) return
85
88
  anchorRef.current = node
86
- update(node)
89
+ if (node) update()
87
90
  },
88
91
  [update],
89
92
  )
@@ -103,7 +106,7 @@ export function useSift({
103
106
  },
104
107
  popoverStyles,
105
108
  updatePosition() {
106
- update(anchorRef.current)
109
+ update()
107
110
  },
108
111
  targetProps: {
109
112
  ref: handleSetInput,