@ankhorage/surface 4.4.0 → 5.0.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 (107) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/dist/features/bottom-sheet/BottomSheetProvider.js +8 -6
  3. package/dist/features/bottom-sheet/BottomSheetProvider.js.map +1 -1
  4. package/dist/features/bottom-sheet/public.d.ts +2 -1
  5. package/dist/features/bottom-sheet/public.d.ts.map +1 -1
  6. package/dist/features/bottom-sheet/public.js +1 -0
  7. package/dist/features/bottom-sheet/public.js.map +1 -1
  8. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts +5 -0
  9. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.d.ts.map +1 -0
  10. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js +85 -0
  11. package/dist/features/popover-menu/adapters/inbound/PopoverMenu.js.map +1 -0
  12. package/dist/features/popover-menu/composition/usePopoverMenuController.d.ts +19 -0
  13. package/dist/features/popover-menu/composition/usePopoverMenuController.d.ts.map +1 -0
  14. package/dist/features/popover-menu/composition/usePopoverMenuController.js +35 -0
  15. package/dist/features/popover-menu/composition/usePopoverMenuController.js.map +1 -0
  16. package/dist/features/popover-menu/public.d.ts +3 -0
  17. package/dist/features/popover-menu/public.d.ts.map +1 -0
  18. package/dist/features/popover-menu/public.js +2 -0
  19. package/dist/features/popover-menu/public.js.map +1 -0
  20. package/dist/features/popover-menu/utils/resolveNextMenuIndex.d.ts +5 -0
  21. package/dist/features/popover-menu/utils/resolveNextMenuIndex.d.ts.map +1 -0
  22. package/dist/features/popover-menu/utils/resolveNextMenuIndex.js +18 -0
  23. package/dist/features/popover-menu/utils/resolveNextMenuIndex.js.map +1 -0
  24. package/dist/features/tooltip/adapters/inbound/Tooltip.d.ts +5 -0
  25. package/dist/features/tooltip/adapters/inbound/Tooltip.d.ts.map +1 -0
  26. package/dist/features/tooltip/adapters/inbound/Tooltip.js +25 -0
  27. package/dist/features/tooltip/adapters/inbound/Tooltip.js.map +1 -0
  28. package/dist/features/tooltip/composition/useTooltipVisibility.d.ts +9 -0
  29. package/dist/features/tooltip/composition/useTooltipVisibility.d.ts.map +1 -0
  30. package/dist/features/tooltip/composition/useTooltipVisibility.js +25 -0
  31. package/dist/features/tooltip/composition/useTooltipVisibility.js.map +1 -0
  32. package/dist/features/tooltip/public.d.ts +3 -0
  33. package/dist/features/tooltip/public.d.ts.map +1 -0
  34. package/dist/features/tooltip/public.js +2 -0
  35. package/dist/features/tooltip/public.js.map +1 -0
  36. package/dist/index.d.ts +4 -4
  37. package/dist/index.d.ts.map +1 -1
  38. package/dist/index.js +2 -2
  39. package/dist/index.js.map +1 -1
  40. package/dist/types/bottomSheet.d.ts +3 -0
  41. package/dist/types/bottomSheet.d.ts.map +1 -1
  42. package/dist/types/bottomSheet.js.map +1 -1
  43. package/dist/types/popoverMenu.d.ts +24 -0
  44. package/dist/types/popoverMenu.d.ts.map +1 -0
  45. package/dist/types/popoverMenu.js +2 -0
  46. package/dist/types/popoverMenu.js.map +1 -0
  47. package/dist/{components/tooltip/types.d.ts → types/tooltip.d.ts} +2 -2
  48. package/dist/types/tooltip.d.ts.map +1 -0
  49. package/dist/types/tooltip.js +2 -0
  50. package/dist/types/tooltip.js.map +1 -0
  51. package/package.json +1 -1
  52. package/src/features/bottom-sheet/BottomSheetProvider.tsx +13 -8
  53. package/src/features/bottom-sheet/public.test.ts +23 -0
  54. package/src/features/bottom-sheet/public.ts +12 -1
  55. package/src/features/popover-menu/adapters/inbound/PopoverMenu.tsx +201 -0
  56. package/src/features/popover-menu/composition/usePopoverMenuController.ts +55 -0
  57. package/src/features/popover-menu/public.test.ts +13 -0
  58. package/src/features/popover-menu/public.ts +6 -0
  59. package/src/{components/menu/navigation.test.ts → features/popover-menu/utils/resolveNextMenuIndex.test.ts} +4 -4
  60. package/src/features/popover-menu/utils/resolveNextMenuIndex.ts +21 -0
  61. package/src/features/tooltip/adapters/inbound/Tooltip.tsx +56 -0
  62. package/src/features/tooltip/composition/useTooltipVisibility.ts +29 -0
  63. package/src/features/tooltip/public.test.ts +11 -0
  64. package/src/features/tooltip/public.ts +2 -0
  65. package/src/index.test.ts +8 -3
  66. package/src/index.ts +8 -4
  67. package/src/types/bottomSheet.ts +4 -0
  68. package/src/types/popoverMenu.ts +27 -0
  69. package/src/{components/tooltip/types.ts → types/tooltip.ts} +1 -1
  70. package/dist/components/menu/Menu.d.ts +0 -4
  71. package/dist/components/menu/Menu.d.ts.map +0 -1
  72. package/dist/components/menu/Menu.js +0 -152
  73. package/dist/components/menu/Menu.js.map +0 -1
  74. package/dist/components/menu/index.d.ts +0 -3
  75. package/dist/components/menu/index.d.ts.map +0 -1
  76. package/dist/components/menu/index.js +0 -2
  77. package/dist/components/menu/index.js.map +0 -1
  78. package/dist/components/menu/navigation.d.ts +0 -4
  79. package/dist/components/menu/navigation.d.ts.map +0 -1
  80. package/dist/components/menu/navigation.js +0 -25
  81. package/dist/components/menu/navigation.js.map +0 -1
  82. package/dist/components/menu/types.d.ts +0 -23
  83. package/dist/components/menu/types.d.ts.map +0 -1
  84. package/dist/components/menu/types.js +0 -2
  85. package/dist/components/menu/types.js.map +0 -1
  86. package/dist/components/tooltip/Tooltip.d.ts +0 -4
  87. package/dist/components/tooltip/Tooltip.d.ts.map +0 -1
  88. package/dist/components/tooltip/Tooltip.js +0 -75
  89. package/dist/components/tooltip/Tooltip.js.map +0 -1
  90. package/dist/components/tooltip/index.d.ts +0 -3
  91. package/dist/components/tooltip/index.d.ts.map +0 -1
  92. package/dist/components/tooltip/index.js +0 -2
  93. package/dist/components/tooltip/index.js.map +0 -1
  94. package/dist/components/tooltip/types.d.ts.map +0 -1
  95. package/dist/components/tooltip/types.js +0 -2
  96. package/dist/components/tooltip/types.js.map +0 -1
  97. package/dist/examples/DocsExamples.d.ts +0 -6
  98. package/dist/examples/DocsExamples.d.ts.map +0 -1
  99. package/dist/examples/DocsExamples.js +0 -97
  100. package/dist/examples/DocsExamples.js.map +0 -1
  101. package/src/components/menu/Menu.tsx +0 -228
  102. package/src/components/menu/index.ts +0 -2
  103. package/src/components/menu/navigation.ts +0 -34
  104. package/src/components/menu/types.ts +0 -26
  105. package/src/components/tooltip/Tooltip.tsx +0 -117
  106. package/src/components/tooltip/index.ts +0 -2
  107. package/src/examples/DocsExamples.tsx +0 -122
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEzC,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAE/C,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,cAAc,mBAAmB,CAAC;AAUlC,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAS9D,OAAO,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAKhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAQhF,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAMhF,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAQpD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEzE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAE7D,cAAc,UAAU,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOtD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export type { FieldProps } from './components/field';\nexport { Field } from './components/field';\nexport type { HelperTextProps } from './components/helper-text';\nexport { HelperText } from './components/helper-text';\nexport type { LabelProps } from './components/label';\nexport { Label } from './components/label';\nexport type { ListItemProps } from './components/list-item';\nexport { ListItem } from './components/list-item';\nexport type { MenuAction, MenuActionIntent, MenuItem, MenuProps } from './components/menu';\nexport { Menu } from './components/menu';\nexport type { ModalProps } from './components/modal';\nexport { Modal } from './components/modal';\nexport type { SwitchProps } from './components/switch';\nexport { Switch } from './components/switch';\nexport type { TabListProps, TabPanelProps, TabProps, TabsProps } from './components/tabs';\nexport { Tab, TabList, TabPanel, Tabs } from './components/tabs';\nexport type { TextareaProps } from './components/textarea';\nexport { Textarea } from './components/textarea';\nexport type { TooltipProps } from './components/tooltip';\nexport { Tooltip } from './components/tooltip';\nexport type { FontRuntime } from './context/FontContext';\nexport { FontProvider, useFontContext } from './context/FontContext';\nexport type { I18nInstance, TranslationRuntime, Translator } from './context/TranslationContext';\nexport { TranslationProvider, useTranslationContext } from './context/TranslationContext';\nexport * from './core/responsive';\nexport type {\n AccordionContentProps,\n AccordionItemProps,\n AccordionMode,\n AccordionMultipleProps,\n AccordionProps,\n AccordionSingleProps,\n AccordionTriggerProps,\n} from './features/accordion/public';\nexport {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from './features/accordion/public';\nexport type { BadgeProps } from './features/badge/public';\nexport { Badge } from './features/badge/public';\nexport type { ButtonIconSpec, ButtonProps, IconButtonProps } from './features/button/public';\nexport { Button, IconButton } from './features/button/public';\nexport type { CardProps } from './features/card/public';\nexport { Card } from './features/card/public';\nexport type { CheckboxProps } from './features/form/checkbox/public';\nexport { Checkbox } from './features/form/checkbox/public';\nexport type { RadioProps } from './features/form/radio/public';\nexport { Radio } from './features/form/radio/public';\nexport type { TextInputProps } from './features/form/text-input/public';\nexport { TextInput } from './features/form/text-input/public';\nexport type {\n FontIconSource,\n IconProps,\n IconProvider,\n IconSource,\n IconVariant,\n SvgIconSource,\n} from './features/icon/public';\nexport { Icon, SUPPORTED_ICON_PROVIDERS } from './features/icon/public';\nexport type { ImageFit, ImageProps, SurfaceImageSource } from './features/image/public';\nexport { Image } from './features/image/public';\nexport type {\n KeyboardAvoidingViewBehavior,\n KeyboardAvoidingViewProps,\n} from './features/keyboard-avoiding-view/public';\nexport { KeyboardAvoidingView } from './features/keyboard-avoiding-view/public';\nexport type {\n BoxProps,\n ContainerProps,\n DividerProps,\n GridProps,\n StackProps,\n} from './features/layout/public';\nexport { Box, Container, Divider, Grid, Stack } from './features/layout/public';\nexport type {\n PopoverAnchorRenderProps,\n PopoverPlacement,\n PopoverProps,\n} from './features/popover/public';\nexport { Popover } from './features/popover/public';\nexport type { SurfaceProps, SurfaceVariant } from './features/surface/public';\nexport { Surface } from './features/surface/public';\nexport type {\n ToastController,\n ToastOptions,\n ToastProps,\n ToastProviderProps,\n ToastStatus,\n} from './features/toast/public';\nexport { Toast, ToastProvider, useToast } from './features/toast/public';\nexport type { HeadingLevel, HeadingProps, TextProps } from './features/typography/public';\nexport { Heading, Text } from './features/typography/public';\nexport type { InteractionPolicy, InteractionPolicyProps } from './interactionPolicy';\nexport * from './layout';\nexport type { ButtonBaseProps } from './primitives/button-base';\nexport { ButtonBase } from './primitives/button-base';\nexport type {\n SurfaceColor,\n SurfaceEmphasis,\n SurfacePaletteColor,\n SurfaceStatusColor,\n} from './surfaceColor';\nexport {\n SURFACE_COLORS,\n SURFACE_EMPHASES,\n SURFACE_PALETTE_COLORS,\n SURFACE_STATUS_COLORS,\n} from './surfaceColor';\nexport * from './theme';\nexport { isDeepEqual } from './utils/deepEqual';\nexport { deepMerge } from './utils/deepMerge';\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAEtD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,QAAQ,EAAE,MAAM,wBAAwB,CAAC;AAElD,OAAO,EAAE,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAE3C,OAAO,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAE7C,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AAEjE,OAAO,EAAE,QAAQ,EAAE,MAAM,uBAAuB,CAAC;AAEjD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;AAErE,OAAO,EAAE,mBAAmB,EAAE,qBAAqB,EAAE,MAAM,8BAA8B,CAAC;AAC1F,cAAc,mBAAmB,CAAC;AAUlC,OAAO,EACL,SAAS,EACT,gBAAgB,EAChB,aAAa,EACb,gBAAgB,GACjB,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEhD,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAE9D,OAAO,EAAE,IAAI,EAAE,MAAM,wBAAwB,CAAC;AAE9C,OAAO,EAAE,QAAQ,EAAE,MAAM,iCAAiC,CAAC;AAE3D,OAAO,EAAE,KAAK,EAAE,MAAM,8BAA8B,CAAC;AAErD,OAAO,EAAE,SAAS,EAAE,MAAM,mCAAmC,CAAC;AAS9D,OAAO,EAAE,IAAI,EAAE,wBAAwB,EAAE,MAAM,wBAAwB,CAAC;AAExE,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAKhD,OAAO,EAAE,oBAAoB,EAAE,MAAM,0CAA0C,CAAC;AAQhF,OAAO,EAAE,GAAG,EAAE,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,0BAA0B,CAAC;AAMhF,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAMpD,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAE7D,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAQpD,OAAO,EAAE,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AAEzE,OAAO,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAEpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,8BAA8B,CAAC;AAE7D,cAAc,UAAU,CAAC;AAEzB,OAAO,EAAE,UAAU,EAAE,MAAM,0BAA0B,CAAC;AAOtD,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,GACtB,MAAM,gBAAgB,CAAC;AACxB,cAAc,SAAS,CAAC;AACxB,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,mBAAmB,CAAC","sourcesContent":["export type { FieldProps } from './components/field';\nexport { Field } from './components/field';\nexport type { HelperTextProps } from './components/helper-text';\nexport { HelperText } from './components/helper-text';\nexport type { LabelProps } from './components/label';\nexport { Label } from './components/label';\nexport type { ListItemProps } from './components/list-item';\nexport { ListItem } from './components/list-item';\nexport type { ModalProps } from './components/modal';\nexport { Modal } from './components/modal';\nexport type { SwitchProps } from './components/switch';\nexport { Switch } from './components/switch';\nexport type { TabListProps, TabPanelProps, TabProps, TabsProps } from './components/tabs';\nexport { Tab, TabList, TabPanel, Tabs } from './components/tabs';\nexport type { TextareaProps } from './components/textarea';\nexport { Textarea } from './components/textarea';\nexport type { FontRuntime } from './context/FontContext';\nexport { FontProvider, useFontContext } from './context/FontContext';\nexport type { I18nInstance, TranslationRuntime, Translator } from './context/TranslationContext';\nexport { TranslationProvider, useTranslationContext } from './context/TranslationContext';\nexport * from './core/responsive';\nexport type {\n AccordionContentProps,\n AccordionItemProps,\n AccordionMode,\n AccordionMultipleProps,\n AccordionProps,\n AccordionSingleProps,\n AccordionTriggerProps,\n} from './features/accordion/public';\nexport {\n Accordion,\n AccordionContent,\n AccordionItem,\n AccordionTrigger,\n} from './features/accordion/public';\nexport type { BadgeProps } from './features/badge/public';\nexport { Badge } from './features/badge/public';\nexport type { ButtonIconSpec, ButtonProps, IconButtonProps } from './features/button/public';\nexport { Button, IconButton } from './features/button/public';\nexport type { CardProps } from './features/card/public';\nexport { Card } from './features/card/public';\nexport type { CheckboxProps } from './features/form/checkbox/public';\nexport { Checkbox } from './features/form/checkbox/public';\nexport type { RadioProps } from './features/form/radio/public';\nexport { Radio } from './features/form/radio/public';\nexport type { TextInputProps } from './features/form/text-input/public';\nexport { TextInput } from './features/form/text-input/public';\nexport type {\n FontIconSource,\n IconProps,\n IconProvider,\n IconSource,\n IconVariant,\n SvgIconSource,\n} from './features/icon/public';\nexport { Icon, SUPPORTED_ICON_PROVIDERS } from './features/icon/public';\nexport type { ImageFit, ImageProps, SurfaceImageSource } from './features/image/public';\nexport { Image } from './features/image/public';\nexport type {\n KeyboardAvoidingViewBehavior,\n KeyboardAvoidingViewProps,\n} from './features/keyboard-avoiding-view/public';\nexport { KeyboardAvoidingView } from './features/keyboard-avoiding-view/public';\nexport type {\n BoxProps,\n ContainerProps,\n DividerProps,\n GridProps,\n StackProps,\n} from './features/layout/public';\nexport { Box, Container, Divider, Grid, Stack } from './features/layout/public';\nexport type {\n PopoverAnchorRenderProps,\n PopoverPlacement,\n PopoverProps,\n} from './features/popover/public';\nexport { Popover } from './features/popover/public';\nexport type {\n PopoverMenuAction,\n PopoverMenuActionIntent,\n PopoverMenuProps,\n} from './features/popover-menu/public';\nexport { PopoverMenu } from './features/popover-menu/public';\nexport type { SurfaceProps, SurfaceVariant } from './features/surface/public';\nexport { Surface } from './features/surface/public';\nexport type {\n ToastController,\n ToastOptions,\n ToastProps,\n ToastProviderProps,\n ToastStatus,\n} from './features/toast/public';\nexport { Toast, ToastProvider, useToast } from './features/toast/public';\nexport type { TooltipProps } from './features/tooltip/public';\nexport { Tooltip } from './features/tooltip/public';\nexport type { HeadingLevel, HeadingProps, TextProps } from './features/typography/public';\nexport { Heading, Text } from './features/typography/public';\nexport type { InteractionPolicy, InteractionPolicyProps } from './interactionPolicy';\nexport * from './layout';\nexport type { ButtonBaseProps } from './primitives/button-base';\nexport { ButtonBase } from './primitives/button-base';\nexport type {\n SurfaceColor,\n SurfaceEmphasis,\n SurfacePaletteColor,\n SurfaceStatusColor,\n} from './surfaceColor';\nexport {\n SURFACE_COLORS,\n SURFACE_EMPHASES,\n SURFACE_PALETTE_COLORS,\n SURFACE_STATUS_COLORS,\n} from './surfaceColor';\nexport * from './theme';\nexport { isDeepEqual } from './utils/deepEqual';\nexport { deepMerge } from './utils/deepMerge';\n"]}
@@ -1,9 +1,12 @@
1
1
  import type React from 'react';
2
+ export type BottomSheetContentMode = 'view' | 'direct';
2
3
  export interface BottomSheetPresentOptions {
3
4
  content: React.ReactNode;
5
+ contentMode?: BottomSheetContentMode;
4
6
  snapPoints?: readonly (number | string)[];
5
7
  initialIndex?: number;
6
8
  enableDynamicSizing?: boolean;
9
+ maxDynamicContentSize?: number;
7
10
  enablePanDownToClose?: boolean;
8
11
  dismissOnBackdropPress?: boolean;
9
12
  keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';
@@ -1 +1 @@
1
- {"version":3,"file":"bottomSheet.d.ts","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC;IAC3D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACtD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
1
+ {"version":3,"file":"bottomSheet.d.ts","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,MAAM,MAAM,sBAAsB,GAAG,MAAM,GAAG,QAAQ,CAAC;AAEvD,MAAM,WAAW,yBAAyB;IACxC,OAAO,EAAE,KAAK,CAAC,SAAS,CAAC;IACzB,WAAW,CAAC,EAAE,sBAAsB,CAAC;IACrC,UAAU,CAAC,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,CAAC;IAC1C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,oBAAoB,CAAC,EAAE,OAAO,CAAC;IAC/B,sBAAsB,CAAC,EAAE,OAAO,CAAC;IACjC,gBAAgB,CAAC,EAAE,QAAQ,GAAG,YAAY,GAAG,aAAa,CAAC;IAC3D,oBAAoB,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1C,aAAa,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACxB;AAED,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,CAAC;IACtD,OAAO,EAAE,MAAM,IAAI,CAAC;CACrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"bottomSheet.js","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nexport interface BottomSheetPresentOptions {\n content: React.ReactNode;\n snapPoints?: readonly (number | string)[];\n initialIndex?: number;\n enableDynamicSizing?: boolean;\n enablePanDownToClose?: boolean;\n dismissOnBackdropPress?: boolean;\n keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';\n keyboardBlurBehavior?: 'none' | 'restore';\n onIndexChange?: (index: number) => void;\n onDismiss?: () => void;\n}\n\nexport interface BottomSheetController {\n present: (options: BottomSheetPresentOptions) => void;\n dismiss: () => void;\n}\n"]}
1
+ {"version":3,"file":"bottomSheet.js","sourceRoot":"","sources":["../../src/types/bottomSheet.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nexport type BottomSheetContentMode = 'view' | 'direct';\n\nexport interface BottomSheetPresentOptions {\n content: React.ReactNode;\n contentMode?: BottomSheetContentMode;\n snapPoints?: readonly (number | string)[];\n initialIndex?: number;\n enableDynamicSizing?: boolean;\n maxDynamicContentSize?: number;\n enablePanDownToClose?: boolean;\n dismissOnBackdropPress?: boolean;\n keyboardBehavior?: 'extend' | 'fillParent' | 'interactive';\n keyboardBlurBehavior?: 'none' | 'restore';\n onIndexChange?: (index: number) => void;\n onDismiss?: () => void;\n}\n\nexport interface BottomSheetController {\n present: (options: BottomSheetPresentOptions) => void;\n dismiss: () => void;\n}\n"]}
@@ -0,0 +1,24 @@
1
+ import type React from 'react';
2
+ import type { InteractionPolicy } from '../interactionPolicy';
3
+ import type { PopoverAnchorRenderProps } from './popover';
4
+ export type PopoverMenuActionIntent = 'default' | 'danger';
5
+ export interface PopoverMenuAction {
6
+ id: string;
7
+ title: React.ReactNode;
8
+ description?: React.ReactNode;
9
+ leading?: React.ReactNode;
10
+ trailing?: React.ReactNode;
11
+ intent?: PopoverMenuActionIntent;
12
+ disabled?: boolean;
13
+ selected?: boolean;
14
+ activate?: () => void;
15
+ }
16
+ export interface PopoverMenuProps {
17
+ trigger: (controls: PopoverAnchorRenderProps) => React.ReactNode;
18
+ actions: readonly PopoverMenuAction[];
19
+ dismiss?: () => void;
20
+ closeOnSelect?: boolean;
21
+ interactionPolicy?: InteractionPolicy;
22
+ testID?: string;
23
+ }
24
+ //# sourceMappingURL=popoverMenu.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popoverMenu.d.ts","sourceRoot":"","sources":["../../src/types/popoverMenu.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,WAAW,CAAC;AAE1D,MAAM,MAAM,uBAAuB,GAAG,SAAS,GAAG,QAAQ,CAAC;AAE3D,MAAM,WAAW,iBAAiB;IAChC,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,KAAK,CAAC,SAAS,CAAC;IACvB,WAAW,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC9B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,MAAM,CAAC,EAAE,uBAAuB,CAAC;IACjC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,IAAI,CAAC;CACvB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,CAAC,QAAQ,EAAE,wBAAwB,KAAK,KAAK,CAAC,SAAS,CAAC;IACjE,OAAO,EAAE,SAAS,iBAAiB,EAAE,CAAC;IACtC,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IACrB,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=popoverMenu.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"popoverMenu.js","sourceRoot":"","sources":["../../src/types/popoverMenu.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from '../interactionPolicy';\nimport type { PopoverAnchorRenderProps } from './popover';\n\nexport type PopoverMenuActionIntent = 'default' | 'danger';\n\nexport interface PopoverMenuAction {\n id: string;\n title: React.ReactNode;\n description?: React.ReactNode;\n leading?: React.ReactNode;\n trailing?: React.ReactNode;\n intent?: PopoverMenuActionIntent;\n disabled?: boolean;\n selected?: boolean;\n activate?: () => void;\n}\n\nexport interface PopoverMenuProps {\n trigger: (controls: PopoverAnchorRenderProps) => React.ReactNode;\n actions: readonly PopoverMenuAction[];\n dismiss?: () => void;\n closeOnSelect?: boolean;\n interactionPolicy?: InteractionPolicy;\n testID?: string;\n}\n"]}
@@ -1,5 +1,5 @@
1
1
  import type React from 'react';
2
- import type { InteractionPolicy } from '../../interactionPolicy';
2
+ import type { InteractionPolicy } from '../interactionPolicy';
3
3
  export interface TooltipProps {
4
4
  children?: React.ReactNode;
5
5
  content?: React.ReactNode;
@@ -8,4 +8,4 @@ export interface TooltipProps {
8
8
  placement?: 'top' | 'bottom';
9
9
  testID?: string;
10
10
  }
11
- //# sourceMappingURL=types.d.ts.map
11
+ //# sourceMappingURL=tooltip.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.d.ts","sourceRoot":"","sources":["../../src/types/tooltip.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAC;AAE/B,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,sBAAsB,CAAC;AAE9D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC3B,OAAO,CAAC,EAAE,KAAK,CAAC,SAAS,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,SAAS,CAAC,EAAE,KAAK,GAAG,QAAQ,CAAC;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=tooltip.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tooltip.js","sourceRoot":"","sources":["../../src/types/tooltip.ts"],"names":[],"mappings":"","sourcesContent":["import type React from 'react';\n\nimport type { InteractionPolicy } from '../interactionPolicy';\n\nexport interface TooltipProps {\n children?: React.ReactNode;\n content?: React.ReactNode;\n delay?: number;\n interactionPolicy?: InteractionPolicy;\n placement?: 'top' | 'bottom';\n testID?: string;\n}\n"]}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@ankhorage/surface",
3
3
  "type": "module",
4
- "version": "4.4.0",
4
+ "version": "5.0.0",
5
5
  "description": "Stable UI foundation for React Native and React Native Web.",
6
6
  "homepage": "https://github.com/ankhorage/surface#readme",
7
7
  "bugs": {
@@ -77,10 +77,7 @@ function BottomSheetHost({
77
77
  presentedRef,
78
78
  }: BottomSheetHostProps) {
79
79
  React.useEffect(() => {
80
- if (!activeRequest || presentedRef.current) {
81
- return;
82
- }
83
-
80
+ if (!activeRequest || presentedRef.current) return;
84
81
  presentedRef.current = true;
85
82
  modalRef.current?.present();
86
83
  }, [activeRequest, modalRef, presentedRef]);
@@ -91,9 +88,7 @@ function BottomSheetHost({
91
88
  [activeRequest?.snapPoints],
92
89
  );
93
90
 
94
- if (!activeRequest) {
95
- return null;
96
- }
91
+ if (!activeRequest) return null;
97
92
 
98
93
  return (
99
94
  <BottomSheetModal
@@ -104,15 +99,25 @@ function BottomSheetHost({
104
99
  index={activeRequest.initialIndex ?? 0}
105
100
  keyboardBehavior={activeRequest.keyboardBehavior ?? 'interactive'}
106
101
  keyboardBlurBehavior={activeRequest.keyboardBlurBehavior ?? 'restore'}
102
+ maxDynamicContentSize={activeRequest.maxDynamicContentSize}
107
103
  onChange={activeRequest.onIndexChange}
108
104
  onDismiss={handleDismiss}
109
105
  snapPoints={snapPoints}
110
106
  >
111
- <BottomSheetView>{activeRequest.content}</BottomSheetView>
107
+ {renderBottomSheetContent(activeRequest)}
112
108
  </BottomSheetModal>
113
109
  );
114
110
  }
115
111
 
112
+ /*** Renders default content in BottomSheetView and direct content without an extra wrapper. */
113
+ function renderBottomSheetContent(activeRequest: BottomSheetPresentOptions): React.ReactNode {
114
+ return activeRequest.contentMode === 'direct' ? (
115
+ activeRequest.content
116
+ ) : (
117
+ <BottomSheetView>{activeRequest.content}</BottomSheetView>
118
+ );
119
+ }
120
+
116
121
  /*** Creates the backdrop renderer for the active bottom-sheet request. */
117
122
  function useBottomSheetBackdrop(dismissOnBackdropPress: boolean | undefined) {
118
123
  return React.useCallback(
@@ -0,0 +1,23 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ test('bottom-sheet boundary exposes Gorhom integrated scrollables and direct content mode', async () => {
4
+ const [publicSource, providerSource, typeSource] = await Promise.all([
5
+ Bun.file('src/features/bottom-sheet/public.ts').text(),
6
+ Bun.file('src/features/bottom-sheet/BottomSheetProvider.tsx').text(),
7
+ Bun.file('src/types/bottomSheet.ts').text(),
8
+ ]);
9
+
10
+ for (const name of [
11
+ 'BottomSheetFlatList',
12
+ 'BottomSheetScrollView',
13
+ 'BottomSheetSectionList',
14
+ 'BottomSheetView',
15
+ 'BottomSheetVirtualizedList',
16
+ ]) {
17
+ expect(publicSource).toContain(name);
18
+ }
19
+
20
+ expect(typeSource).toContain("export type BottomSheetContentMode = 'view' | 'direct';");
21
+ expect(providerSource).toContain("activeRequest.contentMode === 'direct'");
22
+ expect(providerSource).toContain('maxDynamicContentSize={activeRequest.maxDynamicContentSize}');
23
+ });
@@ -1,3 +1,14 @@
1
- export type { BottomSheetController, BottomSheetPresentOptions } from '../../types/bottomSheet';
1
+ export type {
2
+ BottomSheetContentMode,
3
+ BottomSheetController,
4
+ BottomSheetPresentOptions,
5
+ } from '../../types/bottomSheet';
2
6
  export { BottomSheetProvider } from './BottomSheetProvider';
3
7
  export { useBottomSheet } from './useBottomSheet';
8
+ export {
9
+ BottomSheetFlatList,
10
+ BottomSheetScrollView,
11
+ BottomSheetSectionList,
12
+ BottomSheetView,
13
+ BottomSheetVirtualizedList,
14
+ } from '@gorhom/bottom-sheet';
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { Pressable } from 'react-native';
3
+
4
+ import { FocusScope } from '../../../../internal/focus/FocusScope';
5
+ import { useFocusManager } from '../../../../internal/focus/useFocusManager';
6
+ import { Inline } from '../../../../layout';
7
+ import { useTheme } from '../../../../theme/ThemeContext';
8
+ import type { PopoverMenuAction, PopoverMenuProps } from '../../../../types/popoverMenu';
9
+ import { Box, Stack } from '../../../layout/public';
10
+ import { Popover } from '../../../popover/public';
11
+ import { Surface } from '../../../surface/public';
12
+ import { Text } from '../../../typography/public';
13
+ import { usePopoverMenuController } from '../../composition/usePopoverMenuController';
14
+ import { resolveNextMenuIndex } from '../../utils/resolveNextMenuIndex';
15
+
16
+ /*** Presents an anchored action menu using the shared Popover capability. */
17
+ export function PopoverMenu({
18
+ trigger,
19
+ actions,
20
+ dismiss,
21
+ closeOnSelect = true,
22
+ interactionPolicy = 'enabled',
23
+ testID,
24
+ }: PopoverMenuProps) {
25
+ const passive = interactionPolicy === 'passive';
26
+ const controller = usePopoverMenuController({ actions, closeOnSelect, dismiss, passive });
27
+
28
+ return (
29
+ <Popover
30
+ anchor={trigger}
31
+ closeOnOutsidePress
32
+ interactionPolicy={interactionPolicy}
33
+ onOpenChange={controller.handleOpenChange}
34
+ open={controller.open}
35
+ placement="bottom-start"
36
+ testID={testID}
37
+ >
38
+ <PopoverMenuContent
39
+ actions={actions}
40
+ activeIndex={controller.activeIndex}
41
+ activateAction={controller.activateAction}
42
+ close={controller.close}
43
+ passive={passive}
44
+ setActiveIndex={controller.setActiveIndex}
45
+ testID={testID}
46
+ />
47
+ </Popover>
48
+ );
49
+ }
50
+
51
+ interface PopoverMenuContentProps {
52
+ actions: readonly PopoverMenuAction[];
53
+ activeIndex: number;
54
+ activateAction: (action: PopoverMenuAction) => void;
55
+ close: () => void;
56
+ passive: boolean;
57
+ setActiveIndex: React.Dispatch<React.SetStateAction<number>>;
58
+ testID?: string;
59
+ }
60
+
61
+ /*** Renders menu semantics and keyboard navigation inside an open Popover. */
62
+ function PopoverMenuContent({
63
+ actions,
64
+ activeIndex,
65
+ activateAction,
66
+ close,
67
+ passive,
68
+ setActiveIndex,
69
+ testID,
70
+ }: PopoverMenuContentProps) {
71
+ usePopoverMenuKeyboard({ actions, activeIndex, activateAction, close, passive, setActiveIndex });
72
+
73
+ return (
74
+ <FocusScope active onEscape={passive ? undefined : close}>
75
+ <Surface accessibilityRole="menu" p="xs" testID={testID} variant="raised">
76
+ {actions.map((action, index) => (
77
+ <PopoverMenuItem
78
+ action={action}
79
+ active={index === activeIndex}
80
+ activateAction={activateAction}
81
+ key={action.id}
82
+ passive={passive}
83
+ testID={testID}
84
+ />
85
+ ))}
86
+ </Surface>
87
+ </FocusScope>
88
+ );
89
+ }
90
+
91
+ /*** Subscribes an open PopoverMenu to standard menu keyboard controls. */
92
+ function usePopoverMenuKeyboard({
93
+ actions,
94
+ activeIndex,
95
+ activateAction,
96
+ close,
97
+ passive,
98
+ setActiveIndex,
99
+ }: Omit<PopoverMenuContentProps, 'testID'>) {
100
+ const { bindKeydown } = useFocusManager();
101
+
102
+ React.useEffect(
103
+ () =>
104
+ bindKeydown((event) => {
105
+ if (passive) return;
106
+
107
+ const { key } = event;
108
+ if (key === undefined) return;
109
+
110
+ if (['ArrowDown', 'ArrowUp', 'Home', 'End'].includes(key)) {
111
+ event.preventDefault();
112
+ setActiveIndex((current) => resolveNextMenuIndex(actions, current, key));
113
+ return;
114
+ }
115
+ if (key === 'Enter') {
116
+ event.preventDefault();
117
+ const action = actions.at(activeIndex);
118
+ if (action) activateAction(action);
119
+ return;
120
+ }
121
+ if (key === 'Escape') {
122
+ event.preventDefault();
123
+ close();
124
+ }
125
+ }),
126
+ [actions, activeIndex, activateAction, bindKeydown, close, passive, setActiveIndex],
127
+ );
128
+ }
129
+
130
+ /*** Renders one accessible PopoverMenu action row. */
131
+ function PopoverMenuItem({
132
+ action,
133
+ active,
134
+ activateAction,
135
+ passive,
136
+ testID,
137
+ }: {
138
+ action: PopoverMenuAction;
139
+ active: boolean;
140
+ activateAction: (action: PopoverMenuAction) => void;
141
+ passive: boolean;
142
+ testID?: string;
143
+ }) {
144
+ const { theme } = useTheme();
145
+ const selected = action.selected ?? active;
146
+
147
+ return (
148
+ <Pressable
149
+ accessibilityRole="menuitem"
150
+ accessibilityState={{ disabled: action.disabled, selected }}
151
+ disabled={action.disabled}
152
+ onPress={passive ? undefined : () => activateAction(action)}
153
+ testID={testID ? `${testID}-item-${action.id}` : undefined}
154
+ >
155
+ <Box
156
+ bg={selected ? theme.semantics.action.neutral.softBg : 'transparent'}
157
+ opacity={action.disabled ? 0.56 : 1}
158
+ px="m"
159
+ py="s"
160
+ radius="s"
161
+ >
162
+ <PopoverMenuItemContent action={action} active={active} />
163
+ </Box>
164
+ </Pressable>
165
+ );
166
+ }
167
+
168
+ /*** Renders the leading, textual, and trailing content of one menu action. */
169
+ function PopoverMenuItemContent({
170
+ action,
171
+ active,
172
+ }: {
173
+ action: PopoverMenuAction;
174
+ active: boolean;
175
+ }) {
176
+ const titleColor =
177
+ action.intent === 'danger' ? 'danger' : active || action.selected ? 'neutral' : undefined;
178
+
179
+ return (
180
+ <Inline align="center" gap="s" justify="space-between" wrap="nowrap">
181
+ {action.leading ? <Box>{action.leading}</Box> : null}
182
+ <Box flex={1}>
183
+ <Stack gap="xxs">
184
+ <Text
185
+ color={titleColor}
186
+ variant="bodySmall"
187
+ weight={action.selected ? 'semiBold' : 'medium'}
188
+ >
189
+ {action.title}
190
+ </Text>
191
+ {action.description ? (
192
+ <Text emphasis="muted" variant="caption">
193
+ {action.description}
194
+ </Text>
195
+ ) : null}
196
+ </Stack>
197
+ </Box>
198
+ {action.trailing ? <Box>{action.trailing}</Box> : null}
199
+ </Inline>
200
+ );
201
+ }
@@ -0,0 +1,55 @@
1
+ import React from 'react';
2
+
3
+ import type { PopoverMenuAction } from '../../../types/popoverMenu';
4
+
5
+ interface PopoverMenuControllerOptions {
6
+ actions: readonly PopoverMenuAction[];
7
+ closeOnSelect: boolean;
8
+ dismiss?: () => void;
9
+ passive: boolean;
10
+ }
11
+
12
+ /*** Owns PopoverMenu visibility, active action state, and selection behavior. */
13
+ export function usePopoverMenuController({
14
+ actions,
15
+ closeOnSelect,
16
+ dismiss,
17
+ passive,
18
+ }: PopoverMenuControllerOptions) {
19
+ const [open, setOpen] = React.useState(false);
20
+ const [activeIndex, setActiveIndex] = React.useState(0);
21
+
22
+ const handleOpenChange = React.useCallback(
23
+ (nextOpen: boolean) => {
24
+ if (nextOpen) setActiveIndex(resolveInitialIndex(actions));
25
+ if (open && !nextOpen) dismiss?.();
26
+ setOpen(nextOpen);
27
+ },
28
+ [actions, dismiss, open],
29
+ );
30
+
31
+ const close = React.useCallback(() => handleOpenChange(false), [handleOpenChange]);
32
+ const activateAction = React.useCallback(
33
+ (action: PopoverMenuAction) => {
34
+ if (action.disabled || passive) return;
35
+ action.activate?.();
36
+ if (closeOnSelect) close();
37
+ },
38
+ [close, closeOnSelect, passive],
39
+ );
40
+
41
+ return {
42
+ activeIndex,
43
+ activateAction,
44
+ close,
45
+ handleOpenChange,
46
+ open,
47
+ setActiveIndex,
48
+ } as const;
49
+ }
50
+
51
+ /*** Returns the first enabled action index for a newly opened menu. */
52
+ function resolveInitialIndex(actions: readonly PopoverMenuAction[]): number {
53
+ const index = actions.findIndex((action) => !action.disabled);
54
+ return index === -1 ? 0 : index;
55
+ }
@@ -0,0 +1,13 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ test('PopoverMenu composes Popover instead of owning anchor and portal mechanics', async () => {
4
+ const source = await Bun.file(
5
+ 'src/features/popover-menu/adapters/inbound/PopoverMenu.tsx',
6
+ ).text();
7
+
8
+ expect(source).toContain("import { Popover } from '../../../popover/public';");
9
+ expect(source).toContain('<Popover');
10
+ expect(source).not.toContain('measureInWindow');
11
+ expect(source).not.toContain('internal/overlay');
12
+ expect(source).not.toContain('<Portal');
13
+ });
@@ -0,0 +1,6 @@
1
+ export type {
2
+ PopoverMenuAction,
3
+ PopoverMenuActionIntent,
4
+ PopoverMenuProps,
5
+ } from '../../types/popoverMenu';
6
+ export { PopoverMenu } from './adapters/inbound/PopoverMenu';
@@ -1,11 +1,11 @@
1
1
  import { describe, expect, it } from 'bun:test';
2
2
 
3
- import { resolveNextMenuIndex } from './navigation';
3
+ import { resolveNextMenuIndex } from './resolveNextMenuIndex';
4
4
 
5
5
  describe('resolveNextMenuIndex', () => {
6
- const items = [{}, { disabled: true }, {}, {}];
6
+ const items = [{}, { disabled: true }, {}, {}] as const;
7
7
 
8
- it('cycles down and up through the menu items', () => {
8
+ it('cycles down and up through enabled actions', () => {
9
9
  expect(resolveNextMenuIndex(items, 0, 'ArrowDown')).toBe(2);
10
10
  expect(resolveNextMenuIndex(items, 0, 'ArrowUp')).toBe(3);
11
11
  });
@@ -15,7 +15,7 @@ describe('resolveNextMenuIndex', () => {
15
15
  expect(resolveNextMenuIndex(items, 0, 'End')).toBe(3);
16
16
  });
17
17
 
18
- it('returns -1 when all items are disabled', () => {
18
+ it('returns -1 when all actions are disabled', () => {
19
19
  expect(resolveNextMenuIndex([{ disabled: true }], 0, 'ArrowDown')).toBe(-1);
20
20
  });
21
21
  });
@@ -0,0 +1,21 @@
1
+ /*** Resolves the next enabled action index for standard menu keyboard navigation. */
2
+ export function resolveNextMenuIndex(
3
+ items: readonly { disabled?: boolean }[],
4
+ currentIndex: number,
5
+ key: string,
6
+ ): number {
7
+ const enabledIndexes = items.flatMap((item, index) => (item.disabled ? [] : [index]));
8
+ if (enabledIndexes.length === 0) return -1;
9
+ if (key === 'Home') return enabledIndexes.at(0) ?? -1;
10
+ if (key === 'End') return enabledIndexes.at(-1) ?? -1;
11
+
12
+ const activeEnabledIndex = enabledIndexes.indexOf(currentIndex);
13
+ const nextEnabledIndex =
14
+ activeEnabledIndex === -1
15
+ ? 0
16
+ : key === 'ArrowUp'
17
+ ? (activeEnabledIndex - 1 + enabledIndexes.length) % enabledIndexes.length
18
+ : (activeEnabledIndex + 1) % enabledIndexes.length;
19
+
20
+ return enabledIndexes.at(nextEnabledIndex) ?? -1;
21
+ }
@@ -0,0 +1,56 @@
1
+ import React from 'react';
2
+ import { Platform, Pressable, View } from 'react-native';
3
+
4
+ import { useTheme } from '../../../../theme/ThemeContext';
5
+ import type { TooltipProps } from '../../../../types/tooltip';
6
+ import { Popover } from '../../../popover/public';
7
+ import { Surface } from '../../../surface/public';
8
+ import { Text } from '../../../typography/public';
9
+ import { useTooltipVisibility } from '../../composition/useTooltipVisibility';
10
+
11
+ /*** Presents delayed hover/focus help through the shared Popover foundation. */
12
+ export function Tooltip({
13
+ children,
14
+ content,
15
+ delay = 150,
16
+ interactionPolicy = 'enabled',
17
+ placement = 'top',
18
+ testID,
19
+ }: TooltipProps) {
20
+ const { theme } = useTheme();
21
+ const passive = interactionPolicy === 'passive';
22
+ const { hide, setVisible, show, visible } = useTooltipVisibility(delay, passive);
23
+
24
+ return (
25
+ <Popover
26
+ closeOnOutsidePress={false}
27
+ interactionPolicy={interactionPolicy}
28
+ onOpenChange={setVisible}
29
+ open={visible}
30
+ placement={placement}
31
+ testID={testID}
32
+ anchor={() => (
33
+ <Pressable
34
+ onBlur={passive ? undefined : hide}
35
+ onFocus={passive ? undefined : show}
36
+ onHoverIn={Platform.OS === 'web' && !passive ? show : undefined}
37
+ onHoverOut={Platform.OS === 'web' && !passive ? hide : undefined}
38
+ >
39
+ {children}
40
+ </Pressable>
41
+ )}
42
+ >
43
+ <View pointerEvents="none">
44
+ <Surface
45
+ p="s"
46
+ style={{ backgroundColor: theme.semantics.surface.inverse }}
47
+ variant="raised"
48
+ >
49
+ <Text emphasis="inverse" variant="caption">
50
+ {content}
51
+ </Text>
52
+ </Surface>
53
+ </View>
54
+ </Popover>
55
+ );
56
+ }
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+
3
+ /*** Manages delayed tooltip visibility and timer cleanup. */
4
+ export function useTooltipVisibility(delay: number, passive: boolean) {
5
+ const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
6
+ const [visible, setVisible] = React.useState(false);
7
+
8
+ const clearTimer = React.useCallback(() => {
9
+ if (timeoutRef.current) {
10
+ clearTimeout(timeoutRef.current);
11
+ timeoutRef.current = null;
12
+ }
13
+ }, []);
14
+
15
+ const show = React.useCallback(() => {
16
+ if (passive) return;
17
+ clearTimer();
18
+ timeoutRef.current = setTimeout(() => setVisible(true), delay);
19
+ }, [clearTimer, delay, passive]);
20
+
21
+ const hide = React.useCallback(() => {
22
+ clearTimer();
23
+ setVisible(false);
24
+ }, [clearTimer]);
25
+
26
+ React.useEffect(() => clearTimer, [clearTimer]);
27
+
28
+ return { hide, setVisible, show, visible } as const;
29
+ }
@@ -0,0 +1,11 @@
1
+ import { expect, test } from 'bun:test';
2
+
3
+ test('Tooltip delegates anchored positioning and portal ownership to Popover', async () => {
4
+ const source = await Bun.file('src/features/tooltip/adapters/inbound/Tooltip.tsx').text();
5
+
6
+ expect(source).toContain("import { Popover } from '../../../popover/public';");
7
+ expect(source).toContain('<Popover');
8
+ expect(source).not.toContain('measureInWindow');
9
+ expect(source).not.toContain('internal/overlay');
10
+ expect(source).not.toContain('<Portal');
11
+ });