react-native-molecules 0.5.0-beta.22 → 0.5.0-beta.23

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.
@@ -1,149 +1,117 @@
1
- import type { ComponentProps, ComponentType, ReactNode } from 'react';
1
+ import type { ReactNode, RefObject } from 'react';
2
2
  import {
3
+ type AccessibilityRole,
3
4
  type GestureResponderEvent,
4
- ScrollView,
5
- type TextInputProps,
6
- type ViewProps,
5
+ type ScrollViewProps,
6
+ type StyleProp,
7
+ type ViewStyle,
7
8
  } from 'react-native';
8
9
 
9
- import type { InternalListItemProps as ListItemProps } from './List';
10
+ import type { TouchableRippleProps } from '../TouchableRipple';
11
+
12
+ export type ListItemId = string | number;
10
13
 
11
14
  export type DefaultListItemT = {
12
- id: string | number;
15
+ id?: ListItemId;
13
16
  label?: string;
14
17
  selectable?: boolean;
15
- [key: string]: any;
18
+ [key: string]: unknown;
16
19
  };
17
20
 
18
- export type ListValue<
19
- Option extends DefaultListItemT,
20
- Multiple extends boolean,
21
- > = Multiple extends true ? Option['id'][] : Option['id'] | null;
21
+ export type ListValue<Multiple extends boolean = false> = Multiple extends true
22
+ ? ListItemId[]
23
+ : ListItemId | null;
24
+
25
+ export type ListEmptyStateRender = (ctx: {
26
+ /** True when `items` (the raw input) has at least one entry. */
27
+ hasItems: boolean;
28
+ }) => ReactNode;
22
29
 
23
- export type ListContextValue<Option extends DefaultListItemT = DefaultListItemT> = {
24
- value: Option['id'] | Option['id'][] | null;
30
+ export type ListContextValue<Option extends object = DefaultListItemT> = {
31
+ value: ListItemId | ListItemId[] | null;
25
32
  multiple: boolean;
26
33
  onAdd: (item: Option) => void;
27
34
  onRemove: (item: Option) => void;
35
+ isSelectedId: (id: ListItemId) => boolean;
28
36
  disabled?: boolean;
29
37
  error: boolean;
30
- items: Option[];
31
- searchQuery: string;
32
- setSearchQuery: (query: string) => void;
33
- filteredItems: Option[];
38
+ allowDeselect: boolean;
34
39
  };
35
40
 
36
- type ListPropsBase<Option extends DefaultListItemT = DefaultListItemT> = {
41
+ type ListPropsBase = {
37
42
  children: ReactNode;
38
- items: Option[];
39
43
  disabled?: boolean;
40
44
  error?: boolean;
41
- searchKey?: string;
42
- onSearchChange?: (query: string) => void;
43
- hideSelected?: boolean;
45
+ /**
46
+ * Whether re-clicking the currently-selected row should remove it. Defaults
47
+ * to `true` for multiple, `false` for single (re-clicking the picked row
48
+ * in a "pick one and close" flow shouldn't clear the value).
49
+ */
50
+ allowDeselect?: boolean;
44
51
  };
45
52
 
46
- type SingleListProps<Option extends DefaultListItemT = DefaultListItemT> = {
53
+ type SingleListProps<Option extends object = DefaultListItemT> = {
47
54
  multiple?: false | undefined;
48
- value?: ListValue<Option, false>;
49
- defaultValue?: ListValue<Option, false>;
50
- onChange?: (
51
- value: ListValue<Option, false>,
52
- item: Option,
53
- event?: GestureResponderEvent,
54
- ) => void;
55
+ value?: ListValue<false>;
56
+ defaultValue?: ListValue<false>;
57
+ onChange?: (value: ListValue<false>, item: Option, event?: GestureResponderEvent) => void;
55
58
  };
56
59
 
57
- type MultipleListProps<Option extends DefaultListItemT = DefaultListItemT> = {
60
+ type MultipleListProps<Option extends object = DefaultListItemT> = {
58
61
  multiple: true;
59
- value?: ListValue<Option, true>;
60
- defaultValue?: ListValue<Option, true>;
61
- onChange?: (
62
- value: ListValue<Option, true>,
63
- item: Option,
64
- event?: GestureResponderEvent,
65
- ) => void;
62
+ value?: ListValue<true>;
63
+ defaultValue?: ListValue<true>;
64
+ onChange?: (value: ListValue<true>, item: Option, event?: GestureResponderEvent) => void;
66
65
  };
67
66
 
68
- export type ListProps<Option extends DefaultListItemT = DefaultListItemT> = ListPropsBase<Option> &
67
+ export type ListProps<Option extends object = DefaultListItemT> = ListPropsBase &
69
68
  (SingleListProps<Option> | MultipleListProps<Option>);
70
69
 
70
+ export type ListContentProps = Omit<ScrollViewProps, 'children'> & {
71
+ children?: ReactNode;
72
+ };
73
+
71
74
  /**
72
- * Arguments passed to the `processProps` callback on `<List.Content>`.
75
+ * Props for `<List.Item>`. When `value` is provided, the item participates in the
76
+ * surrounding `<List>` context — it derives its `selected` state from the context's
77
+ * value and toggles selection on press (unless `shouldToggleOnPress` is false).
73
78
  *
74
- * Use these to adapt `List` context state into the prop contract required by
75
- * a custom container (for example `FlatList`'s `data`/`renderItem` or
76
- * `SectionList`'s `sections`/`renderItem`).
79
+ * Without `value`, the item is a plain styled row (use it for menu-style entries
80
+ * that don't represent a selectable option).
81
+ *
82
+ * Note: when `value` is set, both `onPress` and the selection toggle fire on press,
83
+ * in that order. For most cases that's fine — pass `onPress` for side effects
84
+ * (e.g. closing a menu) and let the toggle drive `onChange`. Pass
85
+ * `onBeforeToggle` for side effects that should only run when the built-in
86
+ * toggle will happen. Set `shouldToggleOnPress={false}` to suppress the toggle entirely.
87
+ *
88
+ * Deselection: by default, single-select rows do **not** deselect on re-click
89
+ * (use `<List allowDeselect>` to opt in or out at the list level).
77
90
  */
78
- export type ListContentProcessPropsArgs<
79
- Option extends DefaultListItemT = DefaultListItemT,
80
- ContainerProps extends Record<string, any> = Record<string, any>,
81
- > = {
82
- /** The user-provided props on `<List.Content>` minus `children`/`ref`. */
83
- props: ContainerProps;
84
- /** The current `filteredItems` from the List context. */
85
- items: Option[];
86
- /** True when there are no items to render after filtering. */
87
- isEmpty: boolean;
88
- /** Resolved empty state node (caller-provided or the default). */
89
- emptyState: ReactNode;
90
- /** Returns whether the given item is currently selected. */
91
- isSelected: (item: Option) => boolean;
92
- };
93
-
94
- type ListContentPropsShared<C extends ComponentType<any> = typeof ScrollView> = Partial<
95
- Omit<ComponentProps<C>, 'children' | 'ref'>
96
- > & {
97
- /**
98
- * The component used to render the scrollable container. Defaults to ScrollView.
99
- * The rest of the props on `<List.Content>` are inferred from this component's props.
100
- *
101
- * Required props (e.g. `FlatList`'s `data`/`renderItem`) can be supplied
102
- * either directly or via `processProps`.
103
- */
104
- ContainerComponent?: C;
105
- emptyState?: ReactNode;
106
- };
107
-
108
- export type ListContentProps<
109
- Option extends DefaultListItemT = DefaultListItemT,
110
- C extends ComponentType<any> = typeof ScrollView,
111
- > = ListContentPropsShared<C> &
112
- (
113
- | {
114
- /**
115
- * Optional when `processProps` renders rows/items itself (e.g. chunked grid rows).
116
- */
117
- processProps: (
118
- args: ListContentProcessPropsArgs<
119
- Option,
120
- Omit<ComponentProps<C>, 'children' | 'ref'>
121
- >,
122
- ) => ComponentProps<C>;
123
- children?: (item: Option, isSelected: boolean) => ReactNode;
124
- }
125
- | {
126
- processProps?: undefined;
127
- children: (item: Option, isSelected: boolean) => ReactNode;
128
- }
129
- );
130
-
131
- export type ListGroupProps = ViewProps & {
132
- children: ReactNode;
133
- label?: string;
134
- };
135
-
136
- export type ListItemOptionProps<Option extends DefaultListItemT = DefaultListItemT> = Omit<
137
- ListItemProps,
138
- 'children' | 'selected' | 'disabled' | 'onPress'
91
+ export type ListItemProps<Option extends object = DefaultListItemT> = Omit<
92
+ TouchableRippleProps,
93
+ 'children' | 'onPress'
139
94
  > & {
140
- value: Option['id'];
141
- children: ReactNode;
142
- onPress?: (item: Option, event: GestureResponderEvent) => void;
95
+ ref?: RefObject<unknown>;
96
+ children?: ReactNode;
97
+ value?: ListItemId;
98
+ style?: StyleProp<ViewStyle>;
99
+ variant?: 'default' | 'menuItem';
100
+ selected?: boolean;
143
101
  disabled?: boolean;
102
+ hovered?: boolean;
103
+ hoverable?: boolean;
144
104
  shouldToggleOnPress?: boolean;
145
- accessibilityRole?: any;
105
+ /** Runs after `onPress`, before the built-in selection toggle. */
106
+ onBeforeToggle?: (event: GestureResponderEvent) => void;
107
+ onPress?: (event: GestureResponderEvent) => void;
108
+ accessibilityRole?: AccessibilityRole;
146
109
  accessibilityState?: Record<string, unknown>;
110
+ /** Reserved for generic item shape; not consumed directly. */
111
+ __optionType?: Option;
147
112
  };
148
113
 
149
- export type ListSearchInputProps = Omit<TextInputProps, 'value' | 'onChangeText'>;
114
+ export type ListItemElementProps = {
115
+ children?: ReactNode;
116
+ style?: StyleProp<ViewStyle>;
117
+ };
@@ -3,19 +3,6 @@ import { StyleSheet } from 'react-native-unistyles';
3
3
  import { getRegisteredComponentStylesWithFallback } from '../../core';
4
4
 
5
5
  const defaultStyles = StyleSheet.create(theme => ({
6
- groupLabel: {
7
- paddingHorizontal: theme.spacings['4'],
8
- paddingVertical: theme.spacings['2'],
9
- fontWeight: '600',
10
- color: theme.colors.onSurface,
11
- },
12
- searchInput: {
13
- marginHorizontal: theme.spacings['2'],
14
- marginVertical: theme.spacings['3'],
15
- },
16
- searchInputInput: {
17
- height: 42,
18
- },
19
6
  emptyState: {
20
7
  paddingHorizontal: theme.spacings['4'],
21
8
  paddingVertical: theme.spacings['6'],
@@ -33,6 +20,9 @@ export const listStyles = getRegisteredComponentStylesWithFallback('List', defau
33
20
  const listItemStylesDefault = StyleSheet.create(theme => ({
34
21
  root: {
35
22
  backgroundColor: theme.colors.surface,
23
+ flexDirection: 'row',
24
+ alignItems: 'center',
25
+ gap: theme.spacings['4'],
36
26
 
37
27
  _web: {
38
28
  outlineStyle: 'none',
@@ -58,37 +48,14 @@ const listItemStylesDefault = StyleSheet.create(theme => ({
58
48
  paddingLeft: theme.spacings['4'],
59
49
  paddingRight: theme.spacings['6'],
60
50
  minHeight: 56,
61
- justifyContent: 'center',
62
51
  },
63
52
  menuItem: {
64
53
  paddingHorizontal: theme.spacings['3'],
65
- minHeight: 48,
66
- justifyContent: 'center',
54
+ minHeight: 40,
67
55
  },
68
56
  },
69
57
  },
70
58
  },
71
-
72
- innerContainer: {
73
- flexDirection: 'row',
74
- alignItems: 'center',
75
- minHeight: 40,
76
- },
77
-
78
- content: {
79
- flex: 1,
80
- justifyContent: 'center',
81
- minHeight: 40,
82
- },
83
-
84
- leftElement: {
85
- marginRight: theme.spacings['4'],
86
- marginLeft: theme.spacings._1,
87
- },
88
- rightElement: {
89
- marginRight: theme.spacings._1,
90
- marginLeft: theme.spacings['4'],
91
- },
92
59
  stateLayer: {
93
60
  variants: {
94
61
  state: {
@@ -29,20 +29,16 @@ type MenuBaseProps = Omit<
29
29
  style?: ViewStyle;
30
30
  closeOnSelect?: boolean;
31
31
  children: ReactElement | ReactElement[];
32
- backdropStyles?: ViewStyle;
33
- items?: DefaultListItemT[];
34
32
  disabled?: boolean;
35
- searchKey?: string;
36
- onSearchChange?: (query: string) => void;
37
- hideSelected?: boolean;
33
+ allowDeselect?: boolean;
38
34
  };
39
35
 
40
36
  type SingleMenuProps = {
41
37
  multiple?: false | undefined;
42
- value?: ListValue<DefaultListItemT, false>;
43
- defaultValue?: ListValue<DefaultListItemT, false>;
38
+ value?: ListValue<false>;
39
+ defaultValue?: ListValue<false>;
44
40
  onChange?: (
45
- value: ListValue<DefaultListItemT, false>,
41
+ value: ListValue<false>,
46
42
  item: DefaultListItemT,
47
43
  event?: GestureResponderEvent,
48
44
  ) => void;
@@ -50,10 +46,10 @@ type SingleMenuProps = {
50
46
 
51
47
  type MultipleMenuProps = {
52
48
  multiple: true;
53
- value?: ListValue<DefaultListItemT, true>;
54
- defaultValue?: ListValue<DefaultListItemT, true>;
49
+ value?: ListValue<true>;
50
+ defaultValue?: ListValue<true>;
55
51
  onChange?: (
56
- value: ListValue<DefaultListItemT, true>,
52
+ value: ListValue<true>,
57
53
  item: DefaultListItemT,
58
54
  event?: GestureResponderEvent,
59
55
  ) => void;
@@ -61,34 +57,25 @@ type MultipleMenuProps = {
61
57
 
62
58
  export type Props = MenuBaseProps & (SingleMenuProps | MultipleMenuProps);
63
59
 
64
- const emptyObj = {} as ViewStyle;
65
-
66
- const emptyArr = [] as DefaultListItemT[];
67
-
68
60
  const Menu = ({
69
61
  children,
70
62
  style: styleProp,
71
- backdropStyles = emptyObj,
72
63
  closeOnSelect = true,
73
- items,
74
64
  value,
75
65
  defaultValue,
76
66
  onChange,
77
67
  multiple,
78
68
  disabled,
79
- searchKey,
80
- onSearchChange,
81
- hideSelected,
69
+ allowDeselect,
82
70
  ...rest
83
71
  }: Props) => {
84
72
  const { isOpen, onClose, triggerRef } = useContext(MenuRootContext);
85
73
 
86
- const { backdropStyle, style } = useMemo(() => {
74
+ const { style } = useMemo(() => {
87
75
  return {
88
- backdropStyle: [menuStyles.backdrop, backdropStyles] as unknown as ViewStyle,
89
76
  style: [menuStyles.root, styleProp] as unknown as ViewStyle,
90
77
  };
91
- }, [backdropStyles, styleProp]);
78
+ }, [styleProp]);
92
79
 
93
80
  const contextValue = useMemo(
94
81
  () => ({
@@ -99,20 +86,16 @@ const Menu = ({
99
86
  );
100
87
 
101
88
  const listProps = {
102
- items: items ?? emptyArr,
103
89
  multiple,
104
90
  value,
105
91
  defaultValue,
106
92
  onChange,
107
93
  disabled,
108
- searchKey,
109
- onSearchChange,
110
- hideSelected,
94
+ allowDeselect,
111
95
  } as ListProps<DefaultListItemT>;
112
96
 
113
97
  return (
114
98
  <Popover isOpen={isOpen} onClose={onClose} style={style} triggerRef={triggerRef} {...rest}>
115
- <Popover.Overlay style={backdropStyle} />
116
99
  <List {...listProps}>
117
100
  <MenuContext.Provider value={contextValue}>{children}</MenuContext.Provider>
118
101
  </List>
@@ -187,9 +170,9 @@ export const MenuItem = memo(({ onPress, children, ...rest }: MenuItemProps) =>
187
170
  );
188
171
 
189
172
  return (
190
- <List.Row {...rest} variant="menuItem" onPress={handlePress}>
173
+ <List.Item variant="menuItem" {...rest} onPress={handlePress}>
191
174
  {children}
192
- </List.Row>
175
+ </List.Item>
193
176
  );
194
177
  });
195
178
 
@@ -8,8 +8,6 @@ export const MenuDefault = Object.assign(MenuComponent, {
8
8
  Trigger: MenuTrigger,
9
9
  Item: MenuItem,
10
10
  Content: List.Content,
11
- Group: List.Group,
12
- SearchInput: List.SearchInput,
13
11
  RootContext: MenuRootContext,
14
12
  });
15
13
 
@@ -27,9 +27,7 @@ import { createPopoverRoot } from './PopoverRoot';
27
27
  import { usePlatformMeasure } from './usePlatformMeasure';
28
28
  import { popoverStyles } from './utils';
29
29
 
30
- type PopoverPanelProps = PopoverProps & {
31
- overlay?: ReactNode;
32
- };
30
+ type PopoverPanelProps = PopoverProps & { backdrop?: ReactNode };
33
31
 
34
32
  const PopoverPanel = ({
35
33
  triggerRef,
@@ -45,7 +43,7 @@ const PopoverPanel = ({
45
43
  offset = 8,
46
44
  horizontalOffset = 0,
47
45
  triggerDimensions,
48
- overlay,
46
+ backdrop,
49
47
  ...rest
50
48
  }: PopoverPanelProps) => {
51
49
  const {
@@ -58,11 +56,13 @@ const PopoverPanel = ({
58
56
  } = usePopover({ isOpen, position, align, offset, horizontalOffset });
59
57
 
60
58
  const popoverRef = useRef<View>(null);
59
+ const hasBackdrop = !!backdrop;
61
60
 
62
61
  const { popoverStyle } = usePlatformMeasure({
63
62
  triggerRef,
64
63
  isOpen,
65
64
  onClose,
65
+ dismissOnClickOutside: !hasBackdrop,
66
66
  calculatedPosition,
67
67
  calculateAndSetPosition,
68
68
  targetLayoutRef,
@@ -93,8 +93,8 @@ const PopoverPanel = ({
93
93
  return (
94
94
  <Portal>
95
95
  <PopoverPanelContext value={panelContextValue}>
96
+ {backdrop}
96
97
  <Wrapper {...(WrapperProps as any)}>
97
- {overlay}
98
98
  <View
99
99
  onLayout={handlePopoverLayout}
100
100
  style={[popoverStyles.popoverContainer, style, popoverStyle]}
@@ -127,10 +127,7 @@ export const PopoverTrigger = memo(
127
127
  );
128
128
  PopoverTrigger.displayName = 'Popover_Trigger';
129
129
 
130
- export const PopoverContent = memo(({ children }: { children?: ReactNode }) => <>{children}</>);
131
- PopoverContent.displayName = 'Popover_Content';
132
-
133
- export const PopoverOverlay = memo(({ style, onPress, ...rest }: PressableProps) => {
130
+ export const PopoverBackdrop = memo(({ style, onPress, ...rest }: PressableProps) => {
134
131
  const { isOpen, onClose } = useContext(PopoverContext);
135
132
  if (!isOpen) return null;
136
133
  return (
@@ -141,7 +138,7 @@ export const PopoverOverlay = memo(({ style, onPress, ...rest }: PressableProps)
141
138
  />
142
139
  );
143
140
  });
144
- PopoverOverlay.displayName = 'Popover_Overlay';
141
+ PopoverBackdrop.displayName = 'Popover_Backdrop';
145
142
 
146
143
  type PopoverArrowProps = {
147
144
  size?: number;
@@ -1,17 +1,10 @@
1
- import {
2
- type ComponentType,
3
- memo,
4
- type ReactElement,
5
- type ReactNode,
6
- useMemo,
7
- useRef,
8
- } from 'react';
1
+ import { type ComponentType, memo, type ReactNode, useMemo, useRef } from 'react';
9
2
  import { type View } from 'react-native';
10
3
 
11
4
  import { extractSubcomponents } from '../../utils/extractSubcomponents';
12
5
  import { PopoverContext, type PopoverProps } from './common';
13
6
 
14
- type PopoverPanelInternalProps = PopoverProps & { overlay?: ReactNode };
7
+ type PopoverPanelInternalProps = PopoverProps & { backdrop?: ReactNode };
15
8
 
16
9
  export const createPopoverRoot = (PopoverPanel: ComponentType<PopoverPanelInternalProps>) => {
17
10
  const PopoverRoot = ({
@@ -25,15 +18,13 @@ export const createPopoverRoot = (PopoverPanel: ComponentType<PopoverPanelIntern
25
18
 
26
19
  const {
27
20
  Popover_Trigger,
28
- Popover_Content,
29
- Popover_Overlay,
21
+ Popover_Backdrop,
30
22
  rest: restChildren,
31
23
  } = extractSubcomponents({
32
24
  children,
33
25
  allowedChildren: [
34
26
  { name: 'Popover_Trigger', allowMultiple: false },
35
- { name: 'Popover_Content', allowMultiple: false },
36
- { name: 'Popover_Overlay', allowMultiple: false },
27
+ { name: 'Popover_Backdrop', allowMultiple: false },
37
28
  ] as const,
38
29
  includeRest: true,
39
30
  });
@@ -41,11 +32,6 @@ export const createPopoverRoot = (PopoverPanel: ComponentType<PopoverPanelIntern
41
32
  const hasTrigger = Popover_Trigger.length > 0;
42
33
  const resolvedTriggerRef = triggerRefProp ?? (hasTrigger ? internalTriggerRef : undefined);
43
34
 
44
- const panelContent =
45
- Popover_Content.length > 0
46
- ? (Popover_Content[0] as ReactElement<{ children?: ReactNode }>).props.children
47
- : restChildren;
48
-
49
35
  const contextValue = useMemo(
50
36
  () => ({
51
37
  triggerRef: resolvedTriggerRef ?? internalTriggerRef,
@@ -62,9 +48,9 @@ export const createPopoverRoot = (PopoverPanel: ComponentType<PopoverPanelIntern
62
48
  triggerRef={resolvedTriggerRef}
63
49
  isOpen={isOpen}
64
50
  onClose={onClose}
65
- overlay={Popover_Overlay[0]}
51
+ backdrop={Popover_Backdrop[0]}
66
52
  {...rest}>
67
- {panelContent}
53
+ {restChildren}
68
54
  </PopoverPanel>
69
55
  </PopoverContext>
70
56
  );
@@ -4,6 +4,8 @@ import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
4
4
  import type { LayoutRectangle, StyleProp, View, ViewStyle } from 'react-native';
5
5
  import { Dimensions, StyleSheet } from 'react-native';
6
6
 
7
+ import { registerPortalContext } from '../Portal';
8
+
7
9
  export type Position = 'top' | 'left' | 'right' | 'bottom';
8
10
  export type Align = 'start' | 'center' | 'end';
9
11
 
@@ -27,6 +29,8 @@ export const PopoverContext = createContext<PopoverContextValue>({
27
29
  triggerRef: { current: null },
28
30
  });
29
31
 
32
+ registerPortalContext(PopoverContext);
33
+
30
34
  export type PopoverPanelContextValue = {
31
35
  calculatedPosition: ViewStyle | null;
32
36
  targetLayoutRef: RefObject<LayoutRectangle | null>;
@@ -1,18 +1,12 @@
1
1
  import { getRegisteredComponentWithFallback } from '../../core';
2
- import PopoverDefault, {
3
- PopoverArrow,
4
- PopoverContent,
5
- PopoverOverlay,
6
- PopoverTrigger,
7
- } from './Popover';
2
+ import PopoverDefault, { PopoverArrow, PopoverBackdrop, PopoverTrigger } from './Popover';
8
3
 
9
4
  const PopoverBase = getRegisteredComponentWithFallback('Popover', PopoverDefault);
10
5
 
11
6
  export const Popover = Object.assign(PopoverBase, {
12
7
  Trigger: PopoverTrigger,
13
- Content: PopoverContent,
14
8
  Arrow: PopoverArrow,
15
- Overlay: PopoverOverlay,
9
+ Backdrop: PopoverBackdrop,
16
10
  });
17
11
 
18
12
  export type { Align, PopoverProps, Position } from './common';
@@ -7,6 +7,7 @@ export type UsePlatformMeasureArgs = {
7
7
  triggerRef: RefObject<View | any> | undefined;
8
8
  isOpen: boolean;
9
9
  onClose?: () => void;
10
+ dismissOnClickOutside?: boolean;
10
11
  calculatedPosition: ViewStyle | null;
11
12
  calculateAndSetPosition: () => void;
12
13
  targetLayoutRef: RefObject<LayoutRectangle | null>;
@@ -23,6 +24,7 @@ export const usePlatformMeasure = ({
23
24
  triggerRef,
24
25
  isOpen,
25
26
  onClose,
27
+ dismissOnClickOutside = true,
26
28
  calculatedPosition,
27
29
  calculateAndSetPosition,
28
30
  targetLayoutRef,
@@ -82,7 +84,7 @@ export const usePlatformMeasure = ({
82
84
  }, [isOpen, measureTarget, triggerRef]);
83
85
 
84
86
  useEffect(() => {
85
- if (!isOpen || !onClose) return;
87
+ if (!isOpen || !onClose || !dismissOnClickOutside) return;
86
88
  const handleClickOutside = (event: MouseEvent) => {
87
89
  const popoverElement = popoverRef.current as any as HTMLElement;
88
90
  const targetElement = triggerRef?.current as any as HTMLElement;
@@ -99,7 +101,7 @@ export const usePlatformMeasure = ({
99
101
  return () => {
100
102
  document.removeEventListener('mousedown', handleClickOutside, { capture: true });
101
103
  };
102
- }, [isOpen, onClose, popoverRef, triggerRef]);
104
+ }, [dismissOnClickOutside, isOpen, onClose, popoverRef, triggerRef]);
103
105
 
104
106
  const popoverStyle = useMemo(() => {
105
107
  if (!calculatedPosition) return popoverDefaultStyles;