@fluentui-react-native/menu 0.9.3 → 0.10.0

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.
Files changed (88) hide show
  1. package/CHANGELOG.json +72 -1
  2. package/CHANGELOG.md +22 -2
  3. package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts +4 -2
  4. package/lib/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -1
  5. package/lib/MenuItemCheckbox/MenuItemCheckbox.js +11 -8
  6. package/lib/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -1
  7. package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts +10 -0
  8. package/lib/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -1
  9. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js +28 -14
  10. package/lib/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
  11. package/lib/MenuItemRadio/MenuItemRadio.d.ts +4 -0
  12. package/lib/MenuItemRadio/MenuItemRadio.d.ts.map +1 -0
  13. package/lib/MenuItemRadio/MenuItemRadio.js +12 -0
  14. package/lib/MenuItemRadio/MenuItemRadio.js.map +1 -0
  15. package/lib/MenuItemRadio/useMenuItemRadio.d.ts +3 -0
  16. package/lib/MenuItemRadio/useMenuItemRadio.d.ts.map +1 -0
  17. package/lib/MenuItemRadio/useMenuItemRadio.js +15 -0
  18. package/lib/MenuItemRadio/useMenuItemRadio.js.map +1 -0
  19. package/lib/MenuList/MenuList.types.d.ts +1 -0
  20. package/lib/MenuList/MenuList.types.d.ts.map +1 -1
  21. package/lib/MenuList/useMenuList.d.ts.map +1 -1
  22. package/lib/MenuList/useMenuList.js +13 -3
  23. package/lib/MenuList/useMenuList.js.map +1 -1
  24. package/lib/MenuPopover/MenuPopover.d.ts.map +1 -1
  25. package/lib/MenuPopover/MenuPopover.js +1 -1
  26. package/lib/MenuPopover/MenuPopover.js.map +1 -1
  27. package/lib/MenuPopover/MenuPopover.types.d.ts +2 -0
  28. package/lib/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  29. package/lib/MenuPopover/useMenuPopover.d.ts.map +1 -1
  30. package/lib/MenuPopover/useMenuPopover.js +6 -1
  31. package/lib/MenuPopover/useMenuPopover.js.map +1 -1
  32. package/lib/MenuTrigger/useMenuTrigger.d.ts +3 -0
  33. package/lib/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  34. package/lib/MenuTrigger/useMenuTrigger.js +11 -1
  35. package/lib/MenuTrigger/useMenuTrigger.js.map +1 -1
  36. package/lib/index.d.ts +1 -0
  37. package/lib/index.d.ts.map +1 -1
  38. package/lib/index.js +1 -0
  39. package/lib/index.js.map +1 -1
  40. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts +4 -2
  41. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.d.ts.map +1 -1
  42. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js +12 -8
  43. package/lib-commonjs/MenuItemCheckbox/MenuItemCheckbox.js.map +1 -1
  44. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts +10 -0
  45. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.d.ts.map +1 -1
  46. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js +31 -16
  47. package/lib-commonjs/MenuItemCheckbox/useMenuItemCheckbox.js.map +1 -1
  48. package/lib-commonjs/MenuItemRadio/MenuItemRadio.d.ts +4 -0
  49. package/lib-commonjs/MenuItemRadio/MenuItemRadio.d.ts.map +1 -0
  50. package/lib-commonjs/MenuItemRadio/MenuItemRadio.js +15 -0
  51. package/lib-commonjs/MenuItemRadio/MenuItemRadio.js.map +1 -0
  52. package/lib-commonjs/MenuItemRadio/useMenuItemRadio.d.ts +3 -0
  53. package/lib-commonjs/MenuItemRadio/useMenuItemRadio.d.ts.map +1 -0
  54. package/lib-commonjs/MenuItemRadio/useMenuItemRadio.js +20 -0
  55. package/lib-commonjs/MenuItemRadio/useMenuItemRadio.js.map +1 -0
  56. package/lib-commonjs/MenuList/MenuList.types.d.ts +1 -0
  57. package/lib-commonjs/MenuList/MenuList.types.d.ts.map +1 -1
  58. package/lib-commonjs/MenuList/useMenuList.d.ts.map +1 -1
  59. package/lib-commonjs/MenuList/useMenuList.js +13 -3
  60. package/lib-commonjs/MenuList/useMenuList.js.map +1 -1
  61. package/lib-commonjs/MenuPopover/MenuPopover.d.ts.map +1 -1
  62. package/lib-commonjs/MenuPopover/MenuPopover.js +1 -1
  63. package/lib-commonjs/MenuPopover/MenuPopover.js.map +1 -1
  64. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts +2 -0
  65. package/lib-commonjs/MenuPopover/MenuPopover.types.d.ts.map +1 -1
  66. package/lib-commonjs/MenuPopover/useMenuPopover.d.ts.map +1 -1
  67. package/lib-commonjs/MenuPopover/useMenuPopover.js +6 -1
  68. package/lib-commonjs/MenuPopover/useMenuPopover.js.map +1 -1
  69. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts +3 -0
  70. package/lib-commonjs/MenuTrigger/useMenuTrigger.d.ts.map +1 -1
  71. package/lib-commonjs/MenuTrigger/useMenuTrigger.js +11 -1
  72. package/lib-commonjs/MenuTrigger/useMenuTrigger.js.map +1 -1
  73. package/lib-commonjs/index.d.ts +1 -0
  74. package/lib-commonjs/index.d.ts.map +1 -1
  75. package/lib-commonjs/index.js +3 -1
  76. package/lib-commonjs/index.js.map +1 -1
  77. package/package.json +7 -7
  78. package/src/MenuItemCheckbox/MenuItemCheckbox.tsx +29 -16
  79. package/src/MenuItemCheckbox/useMenuItemCheckbox.ts +39 -20
  80. package/src/MenuItemRadio/MenuItemRadio.tsx +16 -0
  81. package/src/MenuItemRadio/useMenuItemRadio.ts +21 -0
  82. package/src/MenuList/MenuList.types.ts +1 -0
  83. package/src/MenuList/useMenuList.ts +22 -3
  84. package/src/MenuPopover/MenuPopover.tsx +2 -0
  85. package/src/MenuPopover/MenuPopover.types.ts +2 -0
  86. package/src/MenuPopover/useMenuPopover.ts +7 -1
  87. package/src/MenuTrigger/useMenuTrigger.ts +13 -1
  88. package/src/index.ts +1 -0
@@ -14,7 +14,41 @@ import { useMenuListContext } from '../context/menuListContext';
14
14
  const defaultAccessibilityActions = [{ name: 'Toggle' }];
15
15
 
16
16
  export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheckboxState => {
17
- // attach the pressable state handlers
17
+ const { name } = props;
18
+ const context = useMenuListContext();
19
+ const checked = context.checked?.[name];
20
+ const onCheckedChange = context.onCheckedChange;
21
+
22
+ const toggleChecked = React.useCallback(
23
+ (e: InteractionEvent) => {
24
+ onCheckedChange(e, name, !checked);
25
+ },
26
+ [checked, name, onCheckedChange],
27
+ );
28
+
29
+ return useMenuCheckboxInteraction(props, toggleChecked);
30
+ };
31
+
32
+ const getAccessibilityState = memoize(getAccessibilityStateWorker);
33
+ function getAccessibilityStateWorker(disabled: boolean, checked: boolean, accessibilityState?: AccessibilityState) {
34
+ if (accessibilityState) {
35
+ return { disabled, checked, ...accessibilityState };
36
+ }
37
+ return { disabled, checked };
38
+ }
39
+
40
+ /**
41
+ * Create interactivity and accessibility props to be passed into the inner render.
42
+ * This logic is shared between Checkbox and Radio versions of MenuItem.
43
+ *
44
+ * @param props Props passed into the outer compoennt
45
+ * @param toggleCallback Function to be called when item is toggled
46
+ * @returns Props and additional state needed to render the component
47
+ */
48
+ export const useMenuCheckboxInteraction = (
49
+ props: MenuItemCheckboxProps,
50
+ toggleCallback: (e: InteractionEvent) => void,
51
+ ): MenuItemCheckboxState => {
18
52
  const defaultComponentRef = React.useRef(null);
19
53
  const {
20
54
  accessibilityActions,
@@ -27,32 +61,25 @@ export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheck
27
61
  } = props;
28
62
  const context = useMenuListContext();
29
63
  const checked = context.checked?.[name];
30
- const onCheckedChange = context.onCheckedChange;
31
64
 
32
- const toggleChecked = React.useCallback(
33
- (e: InteractionEvent) => {
34
- onCheckedChange(e, name, !checked);
35
- },
36
- [checked, name, onCheckedChange],
37
- );
38
65
  // Ensure focus is placed on checkbox after click
39
- const toggleCheckedWithFocus = useOnPressWithFocus(componentRef, toggleChecked);
66
+ const toggleCheckedWithFocus = useOnPressWithFocus(componentRef, toggleCallback);
40
67
 
41
68
  const pressable = useAsPressable({ onPress: toggleCheckedWithFocus, ...rest });
42
69
  const buttonRef = useViewCommandFocus(componentRef);
43
70
 
44
- const onKeyProps = useKeyProps(toggleChecked, ' ');
71
+ const onKeyProps = useKeyProps(toggleCallback, ' ');
45
72
  const accessibilityActionsProp = accessibilityActions
46
73
  ? [...defaultAccessibilityActions, ...accessibilityActions]
47
74
  : defaultAccessibilityActions;
48
75
  const onAccessibilityActionProp = React.useCallback(
49
76
  (event: AccessibilityActionEvent) => {
50
77
  if (event.nativeEvent.actionName === 'Toggle') {
51
- toggleChecked(event);
78
+ toggleCallback(event);
52
79
  }
53
80
  onAccessibilityAction && onAccessibilityAction(event);
54
81
  },
55
- [toggleChecked, onAccessibilityAction],
82
+ [toggleCallback, onAccessibilityAction],
56
83
  );
57
84
 
58
85
  const state = {
@@ -78,11 +105,3 @@ export const useMenuItemCheckbox = (props: MenuItemCheckboxProps): MenuItemCheck
78
105
  state: state,
79
106
  };
80
107
  };
81
-
82
- const getAccessibilityState = memoize(getAccessibilityStateWorker);
83
- function getAccessibilityStateWorker(disabled: boolean, checked: boolean, accessibilityState?: AccessibilityState) {
84
- if (accessibilityState) {
85
- return { disabled, checked, ...accessibilityState };
86
- }
87
- return { disabled, checked };
88
- }
@@ -0,0 +1,16 @@
1
+ import { UseSlots } from '@fluentui-react-native/framework';
2
+ import { useMenuItemRadio } from './useMenuItemRadio';
3
+ import { MenuItemCheckbox, menuItemFinalRender } from '../MenuItemCheckbox/MenuItemCheckbox';
4
+ import { MenuItemCheckboxProps, MenuItemCheckboxType } from '../MenuItemCheckbox/MenuItemCheckbox.types';
5
+
6
+ export const menuItemRadioName = 'MenuItemRadio';
7
+
8
+ export const MenuItemRadio = MenuItemCheckbox.compose({
9
+ displayName: menuItemRadioName,
10
+ useRender: (userProps: MenuItemCheckboxProps, useSlots: UseSlots<MenuItemCheckboxType>) => {
11
+ const menuItem = useMenuItemRadio(userProps);
12
+ const Slots = useSlots(userProps, (layer): boolean => menuItem.state[layer]);
13
+
14
+ return menuItemFinalRender(menuItem, Slots);
15
+ },
16
+ });
@@ -0,0 +1,21 @@
1
+ import * as React from 'react';
2
+ import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
3
+ import { useMenuListContext } from '../context/menuListContext';
4
+ import { MenuItemCheckboxProps, MenuItemCheckboxState } from '../MenuItemCheckbox/MenuItemCheckbox.types';
5
+ import { useMenuCheckboxInteraction } from '../MenuItemCheckbox/useMenuItemCheckbox';
6
+
7
+ export const useMenuItemRadio = (props: MenuItemCheckboxProps): MenuItemCheckboxState => {
8
+ const { name } = props;
9
+ const context = useMenuListContext();
10
+ const checked = context.checked?.[name];
11
+ const selectRadio = context.selectRadio;
12
+
13
+ const toggleChecked = React.useCallback(
14
+ (e: InteractionEvent) => {
15
+ selectRadio(e, name, !checked);
16
+ },
17
+ [checked, name, selectRadio],
18
+ );
19
+
20
+ return useMenuCheckboxInteraction(props, toggleChecked);
21
+ };
@@ -17,6 +17,7 @@ export interface MenuListProps extends Omit<IViewProps, 'onPress'> {
17
17
 
18
18
  export interface MenuListState extends MenuListProps {
19
19
  isCheckedControlled: boolean;
20
+ selectRadio?: (e: InteractionEvent, name: string, isChecked: boolean) => void;
20
21
  }
21
22
 
22
23
  export interface MenuListSlotProps {
@@ -9,20 +9,25 @@ export const useMenuList = (_props: MenuListProps): MenuListState => {
9
9
  // MenuList v2 needs to be able to be standalone, but this is not in scope for v1
10
10
  // Assuming that checked information will come from parent Menu
11
11
  const isCheckedControlled = typeof context.checked !== 'undefined';
12
- const [checked, onCheckedChange] = useMenuCheckedState(isCheckedControlled, context);
12
+ const [checked, onCheckedChange, selectRadio] = useMenuCheckedState(isCheckedControlled, context);
13
13
 
14
14
  return {
15
15
  ...context,
16
16
  isCheckedControlled,
17
17
  checked,
18
18
  onCheckedChange,
19
+ selectRadio,
19
20
  };
20
21
  };
21
22
 
22
23
  const useMenuCheckedState = (
23
24
  isControlled: boolean,
24
25
  props: MenuListProps,
25
- ): [Record<string, boolean>, (e: InteractionEvent, name: string, isChecked: boolean) => void] => {
26
+ ): [
27
+ Record<string, boolean>,
28
+ (e: InteractionEvent, name: string, isChecked: boolean) => void,
29
+ (e: InteractionEvent, name: string, isChecked: boolean) => void,
30
+ ] => {
26
31
  const { defaultChecked, onCheckedChange, checked } = props;
27
32
  const initialState = defaultChecked ?? checked ?? {};
28
33
  const [checkedInternal, setCheckedInternal] = React.useState<Record<string, boolean>>(initialState);
@@ -45,5 +50,19 @@ const useMenuCheckedState = (
45
50
  [isControlled, state, onCheckedChange, setCheckedInternal],
46
51
  );
47
52
 
48
- return [state, setChecked];
53
+ const selectRadio = React.useCallback(
54
+ (e: InteractionEvent, name: string, isChecked: boolean) => {
55
+ if (!isControlled) {
56
+ const updatedChecked = { [name]: true };
57
+ setCheckedInternal(updatedChecked);
58
+ }
59
+
60
+ if (onCheckedChange) {
61
+ onCheckedChange(e, name, isChecked);
62
+ }
63
+ },
64
+ [isControlled, onCheckedChange, setCheckedInternal],
65
+ );
66
+
67
+ return [state, setChecked, selectRadio];
49
68
  };
@@ -16,6 +16,8 @@ export const MenuPopover = stagedComponent((props: MenuPopoverProps) => {
16
16
  target={state.triggerRef}
17
17
  onDismiss={state.onDismiss}
18
18
  dismissBehaviors={state.dismissBehaviors}
19
+ setInitialFocus={state.setInitialFocus}
20
+ doNotTakePointerCapture={state.doNotTakePointerCapture}
19
21
  >
20
22
  {children}
21
23
  </Callout>
@@ -7,6 +7,8 @@ export interface MenuPopoverProps extends Omit<IViewProps, 'onPress'> {}
7
7
 
8
8
  export interface MenuPopoverState {
9
9
  dismissBehaviors: DismissBehaviors[];
10
+ doNotTakePointerCapture: boolean;
10
11
  onDismiss: () => void;
12
+ setInitialFocus: boolean;
11
13
  triggerRef: React.RefObject<React.Component>;
12
14
  }
@@ -1,4 +1,5 @@
1
1
  import React from 'react';
2
+ import { Platform } from 'react-native';
2
3
  import { DismissBehaviors } from '@fluentui-react-native/callout';
3
4
  import { useMenuContext } from '../context/menuContext';
4
5
  import { MenuPopoverProps, MenuPopoverState } from './MenuPopover.types';
@@ -13,5 +14,10 @@ export const useMenuPopover = (_props: MenuPopoverProps): MenuPopoverState => {
13
14
  ? (['preventDismissOnKeyDown', 'preventDismissOnClickOutside'] as DismissBehaviors[])
14
15
  : undefined;
15
16
 
16
- return { triggerRef, onDismiss, dismissBehaviors };
17
+ // Initial focus behavior differs per platform, Windows platforms move focus
18
+ // automatically onto first element of Callout
19
+ const setInitialFocus = Platform.OS === ('win32' as any) || Platform.OS === 'windows';
20
+ const doNotTakePointerCapture = context.openOnHover;
21
+
22
+ return { triggerRef, onDismiss, dismissBehaviors, doNotTakePointerCapture, setInitialFocus };
17
23
  };
@@ -1,6 +1,7 @@
1
1
  import { useMenuContext } from '../context/menuContext';
2
2
  import { InteractionEvent } from '@fluentui-react-native/interactive-hooks';
3
3
  import { MenuTriggerProps } from './MenuTrigger.types';
4
+ import { Platform } from 'react-native';
4
5
 
5
6
  export const useMenuTrigger = (_props: MenuTriggerProps) => {
6
7
  const context = useMenuContext();
@@ -10,15 +11,26 @@ export const useMenuTrigger = (_props: MenuTriggerProps) => {
10
11
  const openOnHover = context.openOnHover;
11
12
  const triggerRef = context.triggerRef;
12
13
 
14
+ const delayHover = Platform.select({
15
+ macos: 100,
16
+ default: 500, // win32
17
+ });
18
+
13
19
  const onHoverIn = (e: InteractionEvent) => {
14
20
  if (openOnHover) {
15
21
  setOpen(e, true /* isOpen */);
16
22
  }
17
23
  };
18
24
 
25
+ const onHoverOut = (e: InteractionEvent) => {
26
+ if (openOnHover) {
27
+ setOpen(e, false /* isOpen */);
28
+ }
29
+ };
30
+
19
31
  const onClick = (e: InteractionEvent) => {
20
32
  setOpen(e, !open);
21
33
  };
22
34
 
23
- return { onClick, onHoverIn, componentRef: triggerRef };
35
+ return { onClick, onHoverIn, onHoverOut, componentRef: triggerRef, delayHoverIn: delayHover, delayHoverOut: delayHover };
24
36
  };
package/src/index.ts CHANGED
@@ -3,5 +3,6 @@ export { MenuTrigger } from './MenuTrigger/MenuTrigger';
3
3
  export { MenuPopover } from './MenuPopover/MenuPopover';
4
4
  export { MenuItem } from './MenuItem/MenuItem';
5
5
  export { MenuItemCheckbox } from './MenuItemCheckbox/MenuItemCheckbox';
6
+ export { MenuItemRadio } from './MenuItemRadio/MenuItemRadio';
6
7
  export { MenuList } from './MenuList/MenuList';
7
8
  export { MenuDivider } from './MenuDivider/MenuDivider';