@base-ui/react 1.4.1 → 1.5.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 (579) hide show
  1. package/CHANGELOG.md +125 -8
  2. package/README.md +1 -1
  3. package/accordion/item/AccordionItem.d.ts +4 -0
  4. package/accordion/item/AccordionItem.js +5 -4
  5. package/accordion/panel/AccordionPanel.js +29 -51
  6. package/accordion/root/AccordionRoot.js +5 -6
  7. package/accordion/trigger/AccordionTrigger.js +3 -4
  8. package/alert-dialog/handle.d.ts +14 -1
  9. package/alert-dialog/handle.js +22 -5
  10. package/alert-dialog/index.d.ts +1 -1
  11. package/alert-dialog/index.parts.d.ts +2 -3
  12. package/alert-dialog/index.parts.js +4 -5
  13. package/alert-dialog/root/AlertDialogRoot.d.ts +7 -7
  14. package/alert-dialog/root/AlertDialogRoot.js +2 -56
  15. package/alert-dialog/trigger/AlertDialogTrigger.d.ts +25 -0
  16. package/alert-dialog/trigger/AlertDialogTrigger.js +15 -0
  17. package/alert-dialog/trigger/AlertDialogTriggerDataAttributes.d.ts +10 -0
  18. package/alert-dialog/trigger/AlertDialogTriggerDataAttributes.js +18 -0
  19. package/autocomplete/root/AutocompleteRoot.js +9 -10
  20. package/avatar/image/AvatarImage.js +4 -4
  21. package/checkbox/indicator/CheckboxIndicator.js +2 -2
  22. package/checkbox/root/CheckboxRoot.js +49 -11
  23. package/checkbox-group/CheckboxGroup.js +1 -5
  24. package/collapsible/panel/CollapsiblePanel.js +29 -51
  25. package/collapsible/panel/useCollapsiblePanel.d.ts +9 -23
  26. package/collapsible/panel/useCollapsiblePanel.js +308 -268
  27. package/collapsible/root/CollapsibleRoot.d.ts +1 -1
  28. package/collapsible/root/CollapsibleRootContext.d.ts +0 -2
  29. package/collapsible/root/useCollapsibleRoot.d.ts +0 -27
  30. package/collapsible/root/useCollapsibleRoot.js +2 -64
  31. package/collapsible/trigger/CollapsibleTrigger.js +5 -6
  32. package/combobox/arrow/ComboboxArrow.js +1 -1
  33. package/combobox/backdrop/ComboboxBackdrop.js +1 -1
  34. package/combobox/chip/ComboboxChip.js +7 -2
  35. package/combobox/clear/ComboboxClear.d.ts +4 -0
  36. package/combobox/clear/ComboboxClear.js +1 -0
  37. package/combobox/clear/ComboboxClearDataAttributes.d.ts +4 -0
  38. package/combobox/clear/ComboboxClearDataAttributes.js +4 -0
  39. package/combobox/icon/ComboboxIcon.js +1 -1
  40. package/combobox/input/ComboboxInput.js +15 -6
  41. package/combobox/item/ComboboxItem.js +8 -14
  42. package/combobox/item-indicator/ComboboxItemIndicator.js +1 -2
  43. package/combobox/root/AriaCombobox.js +61 -28
  44. package/combobox/store.d.ts +4 -8
  45. package/combobox/store.js +2 -1
  46. package/combobox/utils/ComboboxInternalDismissButton.js +2 -3
  47. package/csp-provider/CSPProvider.js +1 -1
  48. package/dialog/close/DialogClose.js +6 -6
  49. package/dialog/popup/DialogPopup.js +9 -4
  50. package/dialog/root/DialogRoot.d.ts +1 -2
  51. package/dialog/root/DialogRoot.js +3 -73
  52. package/dialog/root/DialogRootContext.d.ts +1 -0
  53. package/dialog/root/DialogRootContext.js +3 -1
  54. package/dialog/root/useDialogRoot.d.ts +12 -4
  55. package/dialog/root/useDialogRoot.js +27 -25
  56. package/dialog/root/useRenderDialogRoot.d.ts +4 -0
  57. package/dialog/root/useRenderDialogRoot.js +96 -0
  58. package/dialog/store/DialogHandle.d.ts +1 -1
  59. package/dialog/store/DialogHandle.js +2 -2
  60. package/dialog/store/DialogStore.d.ts +88 -1
  61. package/dialog/store/DialogStore.js +12 -17
  62. package/dialog/trigger/DialogTrigger.d.ts +1 -1
  63. package/dialog/trigger/DialogTrigger.js +11 -4
  64. package/dialog/viewport/DialogViewport.js +4 -3
  65. package/drawer/popup/DrawerPopup.js +13 -9
  66. package/drawer/root/DrawerRoot.js +11 -11
  67. package/drawer/root/DrawerRootContext.d.ts +1 -1
  68. package/drawer/swipe-area/DrawerSwipeArea.js +6 -5
  69. package/drawer/viewport/DrawerViewport.js +13 -14
  70. package/esm/accordion/item/AccordionItem.d.ts +4 -0
  71. package/esm/accordion/item/AccordionItem.js +5 -4
  72. package/esm/accordion/panel/AccordionPanel.js +29 -51
  73. package/esm/accordion/root/AccordionRoot.js +5 -6
  74. package/esm/accordion/trigger/AccordionTrigger.js +4 -5
  75. package/esm/alert-dialog/handle.d.ts +14 -1
  76. package/esm/alert-dialog/handle.js +20 -5
  77. package/esm/alert-dialog/index.d.ts +1 -1
  78. package/esm/alert-dialog/index.parts.d.ts +2 -3
  79. package/esm/alert-dialog/index.parts.js +2 -3
  80. package/esm/alert-dialog/root/AlertDialogRoot.d.ts +7 -7
  81. package/esm/alert-dialog/root/AlertDialogRoot.js +2 -55
  82. package/esm/alert-dialog/trigger/AlertDialogTrigger.d.ts +25 -0
  83. package/esm/alert-dialog/trigger/AlertDialogTrigger.js +10 -0
  84. package/esm/alert-dialog/trigger/AlertDialogTriggerDataAttributes.d.ts +10 -0
  85. package/esm/alert-dialog/trigger/AlertDialogTriggerDataAttributes.js +12 -0
  86. package/esm/autocomplete/root/AutocompleteRoot.js +9 -10
  87. package/esm/avatar/image/AvatarImage.js +4 -4
  88. package/esm/checkbox/indicator/CheckboxIndicator.js +2 -2
  89. package/esm/checkbox/root/CheckboxRoot.js +49 -11
  90. package/esm/checkbox-group/CheckboxGroup.js +1 -5
  91. package/esm/collapsible/panel/CollapsiblePanel.js +29 -51
  92. package/esm/collapsible/panel/useCollapsiblePanel.d.ts +9 -23
  93. package/esm/collapsible/panel/useCollapsiblePanel.js +309 -269
  94. package/esm/collapsible/root/CollapsibleRoot.d.ts +1 -1
  95. package/esm/collapsible/root/CollapsibleRootContext.d.ts +0 -2
  96. package/esm/collapsible/root/useCollapsibleRoot.d.ts +0 -27
  97. package/esm/collapsible/root/useCollapsibleRoot.js +2 -64
  98. package/esm/collapsible/trigger/CollapsibleTrigger.js +5 -6
  99. package/esm/combobox/arrow/ComboboxArrow.js +1 -1
  100. package/esm/combobox/backdrop/ComboboxBackdrop.js +1 -1
  101. package/esm/combobox/chip/ComboboxChip.js +7 -2
  102. package/esm/combobox/clear/ComboboxClear.d.ts +4 -0
  103. package/esm/combobox/clear/ComboboxClear.js +1 -0
  104. package/esm/combobox/clear/ComboboxClearDataAttributes.d.ts +4 -0
  105. package/esm/combobox/clear/ComboboxClearDataAttributes.js +4 -0
  106. package/esm/combobox/icon/ComboboxIcon.js +1 -1
  107. package/esm/combobox/input/ComboboxInput.js +16 -7
  108. package/esm/combobox/item/ComboboxItem.js +8 -14
  109. package/esm/combobox/item-indicator/ComboboxItemIndicator.js +1 -2
  110. package/esm/combobox/root/AriaCombobox.js +62 -29
  111. package/esm/combobox/store.d.ts +4 -8
  112. package/esm/combobox/store.js +2 -1
  113. package/esm/combobox/utils/ComboboxInternalDismissButton.js +2 -3
  114. package/esm/csp-provider/CSPProvider.js +1 -1
  115. package/esm/dialog/close/DialogClose.js +6 -6
  116. package/esm/dialog/popup/DialogPopup.js +9 -4
  117. package/esm/dialog/root/DialogRoot.d.ts +1 -2
  118. package/esm/dialog/root/DialogRoot.js +4 -72
  119. package/esm/dialog/root/DialogRootContext.d.ts +1 -0
  120. package/esm/dialog/root/DialogRootContext.js +2 -0
  121. package/esm/dialog/root/useDialogRoot.d.ts +12 -4
  122. package/esm/dialog/root/useDialogRoot.js +28 -27
  123. package/esm/dialog/root/useRenderDialogRoot.d.ts +4 -0
  124. package/esm/dialog/root/useRenderDialogRoot.js +90 -0
  125. package/esm/dialog/store/DialogHandle.d.ts +1 -1
  126. package/esm/dialog/store/DialogHandle.js +2 -2
  127. package/esm/dialog/store/DialogStore.d.ts +88 -1
  128. package/esm/dialog/store/DialogStore.js +13 -18
  129. package/esm/dialog/trigger/DialogTrigger.d.ts +1 -1
  130. package/esm/dialog/trigger/DialogTrigger.js +12 -5
  131. package/esm/dialog/viewport/DialogViewport.js +4 -3
  132. package/esm/drawer/popup/DrawerPopup.js +14 -9
  133. package/esm/drawer/root/DrawerRoot.js +11 -11
  134. package/esm/drawer/root/DrawerRootContext.d.ts +1 -1
  135. package/esm/drawer/swipe-area/DrawerSwipeArea.js +6 -5
  136. package/esm/drawer/viewport/DrawerViewport.js +13 -14
  137. package/esm/field/control/FieldControl.js +2 -6
  138. package/esm/field/item/FieldItem.js +1 -4
  139. package/esm/field/root/FieldRoot.js +11 -3
  140. package/esm/field/root/useFieldValidation.d.ts +1 -0
  141. package/esm/field/root/useFieldValidation.js +23 -20
  142. package/esm/field/utils/getCombinedFieldValidityData.d.ts +1 -1
  143. package/esm/field/validity/FieldValidity.d.ts +1 -1
  144. package/esm/floating-ui-react/components/FloatingDelayGroup.js +3 -3
  145. package/esm/floating-ui-react/components/FloatingFocusManager.d.ts +1 -1
  146. package/esm/floating-ui-react/components/FloatingFocusManager.js +20 -8
  147. package/esm/floating-ui-react/components/FloatingPortal.js +3 -3
  148. package/esm/floating-ui-react/hooks/useClick.js +83 -74
  149. package/esm/floating-ui-react/hooks/useClientPoint.js +29 -20
  150. package/esm/floating-ui-react/hooks/useDismiss.d.ts +1 -1
  151. package/esm/floating-ui-react/hooks/useDismiss.js +82 -93
  152. package/esm/floating-ui-react/hooks/useFloating.js +37 -32
  153. package/esm/floating-ui-react/hooks/useFloatingRootContext.d.ts +1 -1
  154. package/esm/floating-ui-react/hooks/useFloatingRootContext.js +2 -2
  155. package/esm/floating-ui-react/hooks/useFocus.js +84 -81
  156. package/esm/floating-ui-react/hooks/useHover.js +72 -76
  157. package/esm/floating-ui-react/hooks/useHoverFloatingInteraction.js +49 -44
  158. package/esm/floating-ui-react/hooks/useHoverInteractionSharedState.js +1 -1
  159. package/esm/floating-ui-react/hooks/useHoverReferenceInteraction.d.ts +7 -2
  160. package/esm/floating-ui-react/hooks/useHoverReferenceInteraction.js +44 -39
  161. package/esm/floating-ui-react/hooks/useHoverShared.d.ts +2 -1
  162. package/esm/floating-ui-react/hooks/useHoverShared.js +4 -0
  163. package/esm/floating-ui-react/hooks/useListNavigation.d.ts +1 -3
  164. package/esm/floating-ui-react/hooks/useListNavigation.js +83 -74
  165. package/esm/floating-ui-react/hooks/useSyncedFloatingRootContext.d.ts +9 -6
  166. package/esm/floating-ui-react/hooks/useSyncedFloatingRootContext.js +25 -20
  167. package/esm/floating-ui-react/hooks/useTypeahead.d.ts +2 -2
  168. package/esm/floating-ui-react/hooks/useTypeahead.js +33 -52
  169. package/esm/floating-ui-react/index.d.ts +0 -2
  170. package/esm/floating-ui-react/index.js +0 -2
  171. package/esm/floating-ui-react/types.d.ts +2 -7
  172. package/esm/floating-ui-react/utils/composite.js +2 -0
  173. package/esm/floating-ui-react/utils/enqueueFocus.d.ts +1 -1
  174. package/esm/floating-ui-react/utils/enqueueFocus.js +10 -7
  175. package/esm/floating-ui-react/utils/getEmptyRootContext.js +1 -1
  176. package/esm/form/Form.js +2 -2
  177. package/esm/index.js +1 -1
  178. package/esm/internals/composite/composite.d.ts +0 -1
  179. package/esm/internals/composite/composite.js +1 -2
  180. package/esm/internals/composite/root/useCompositeRoot.js +2 -2
  181. package/esm/internals/createBaseUIEventDetails.d.ts +2 -0
  182. package/esm/internals/csp-context/index.d.ts +2 -0
  183. package/esm/internals/csp-context/index.js +1 -0
  184. package/esm/internals/field-register-control/index.d.ts +0 -1
  185. package/esm/internals/field-register-control/useFieldControlRegistration.d.ts +2 -1
  186. package/esm/internals/field-register-control/useFieldControlRegistration.js +11 -14
  187. package/esm/internals/field-register-control/useRegisterFieldControl.d.ts +1 -4
  188. package/esm/internals/field-register-control/useRegisterFieldControl.js +6 -11
  189. package/esm/internals/field-root-context/FieldRootContext.d.ts +1 -0
  190. package/esm/internals/field-root-context/FieldRootContext.js +3 -2
  191. package/esm/internals/form-context/FormContext.d.ts +5 -1
  192. package/esm/internals/reason-parts.d.ts +2 -0
  193. package/esm/internals/reason-parts.js +2 -0
  194. package/esm/internals/types.d.ts +1 -0
  195. package/esm/internals/use-button/useButton.js +4 -4
  196. package/esm/internals/usePressAndHold.js +2 -2
  197. package/esm/internals/useRenderElement.js +2 -0
  198. package/esm/menu/arrow/MenuArrow.js +1 -1
  199. package/esm/menu/backdrop/MenuBackdrop.js +1 -1
  200. package/esm/menu/checkbox-item/MenuCheckboxItem.js +5 -7
  201. package/esm/menu/group/MenuGroup.js +1 -4
  202. package/esm/menu/group/MenuGroupContext.d.ts +1 -3
  203. package/esm/menu/group/MenuGroupContext.js +1 -1
  204. package/esm/menu/group-label/MenuGroupLabel.js +4 -6
  205. package/esm/menu/link-item/MenuLinkItem.js +2 -2
  206. package/esm/menu/popup/MenuPopup.js +5 -5
  207. package/esm/menu/radio-group/MenuRadioGroup.js +11 -5
  208. package/esm/menu/radio-item/MenuRadioItem.js +5 -7
  209. package/esm/menu/root/MenuRoot.js +63 -68
  210. package/esm/menu/store/MenuHandle.js +1 -1
  211. package/esm/menu/store/MenuStore.d.ts +87 -0
  212. package/esm/menu/submenu-trigger/MenuSubmenuTrigger.js +8 -5
  213. package/esm/menu/trigger/MenuTrigger.js +13 -10
  214. package/esm/menu/viewport/MenuViewport.d.ts +2 -2
  215. package/esm/menu/viewport/MenuViewport.js +2 -2
  216. package/esm/navigation-menu/arrow/NavigationMenuArrow.js +1 -1
  217. package/esm/navigation-menu/backdrop/NavigationMenuBackdrop.js +1 -1
  218. package/esm/navigation-menu/content/NavigationMenuContent.js +8 -5
  219. package/esm/navigation-menu/icon/NavigationMenuIcon.js +1 -1
  220. package/esm/navigation-menu/item/NavigationMenuItem.js +2 -2
  221. package/esm/navigation-menu/list/NavigationMenuList.js +1 -1
  222. package/esm/navigation-menu/popup/NavigationMenuPopup.js +2 -2
  223. package/esm/navigation-menu/root/NavigationMenuRoot.js +1 -3
  224. package/esm/navigation-menu/trigger/NavigationMenuTrigger.js +24 -17
  225. package/esm/navigation-menu/utils/isOutsideMenuEvent.d.ts +0 -1
  226. package/esm/navigation-menu/utils/isOutsideMenuEvent.js +1 -5
  227. package/esm/navigation-menu/viewport/NavigationMenuViewport.js +2 -3
  228. package/esm/number-field/input/NumberFieldInput.js +3 -5
  229. package/esm/number-field/root/NumberFieldRoot.js +5 -2
  230. package/esm/number-field/scrub-area/NumberFieldScrubArea.js +7 -3
  231. package/esm/otp-field/input/OTPFieldInput.js +43 -29
  232. package/esm/otp-field/root/OTPFieldRoot.d.ts +17 -8
  233. package/esm/otp-field/root/OTPFieldRoot.js +33 -33
  234. package/esm/otp-field/root/OTPFieldRootContext.d.ts +1 -1
  235. package/esm/otp-field/utils/otp.d.ts +5 -4
  236. package/esm/otp-field/utils/otp.js +23 -12
  237. package/esm/popover/arrow/PopoverArrow.js +1 -1
  238. package/esm/popover/backdrop/PopoverBackdrop.js +1 -1
  239. package/esm/popover/close/PopoverClose.js +2 -2
  240. package/esm/popover/description/PopoverDescription.js +1 -7
  241. package/esm/popover/popup/PopoverPopup.d.ts +1 -1
  242. package/esm/popover/popup/PopoverPopup.js +16 -10
  243. package/esm/popover/popup/PopoverPopupDataAttributes.d.ts +1 -1
  244. package/esm/popover/popup/PopoverPopupDataAttributes.js +1 -1
  245. package/esm/popover/positioner/PopoverPositioner.js +5 -5
  246. package/esm/popover/root/PopoverRoot.d.ts +1 -1
  247. package/esm/popover/root/PopoverRoot.js +42 -47
  248. package/esm/popover/store/PopoverHandle.js +1 -1
  249. package/esm/popover/store/PopoverStore.d.ts +91 -4
  250. package/esm/popover/store/PopoverStore.js +17 -18
  251. package/esm/popover/title/PopoverTitle.js +1 -7
  252. package/esm/popover/trigger/PopoverTrigger.js +24 -17
  253. package/esm/popover/viewport/PopoverViewport.d.ts +3 -3
  254. package/esm/popover/viewport/PopoverViewport.js +2 -2
  255. package/esm/popover/viewport/PopoverViewportDataAttributes.d.ts +1 -1
  256. package/esm/popover/viewport/PopoverViewportDataAttributes.js +1 -1
  257. package/esm/preview-card/positioner/PreviewCardPositioner.js +11 -1
  258. package/esm/preview-card/root/PreviewCardRoot.d.ts +1 -1
  259. package/esm/preview-card/root/PreviewCardRoot.js +32 -22
  260. package/esm/preview-card/store/PreviewCardHandle.js +1 -1
  261. package/esm/preview-card/store/PreviewCardStore.d.ts +90 -2
  262. package/esm/preview-card/store/PreviewCardStore.js +19 -31
  263. package/esm/preview-card/trigger/PreviewCardTrigger.js +6 -3
  264. package/esm/preview-card/viewport/PreviewCardViewport.d.ts +2 -2
  265. package/esm/preview-card/viewport/PreviewCardViewport.js +2 -2
  266. package/esm/preview-card/viewport/PreviewCardViewportDataAttributes.d.ts +2 -2
  267. package/esm/preview-card/viewport/PreviewCardViewportDataAttributes.js +2 -2
  268. package/esm/progress/indicator/ProgressIndicator.js +6 -11
  269. package/esm/progress/root/ProgressRoot.d.ts +1 -1
  270. package/esm/radio/root/RadioRoot.js +7 -3
  271. package/esm/radio-group/RadioGroup.js +4 -11
  272. package/esm/radio-group/RadioGroupContext.d.ts +0 -1
  273. package/esm/scroll-area/content/ScrollAreaContent.js +4 -4
  274. package/esm/scroll-area/root/ScrollAreaRoot.js +1 -1
  275. package/esm/scroll-area/scrollbar/ScrollAreaScrollbar.js +16 -20
  276. package/esm/scroll-area/viewport/ScrollAreaViewport.js +6 -10
  277. package/esm/select/arrow/SelectArrow.js +1 -1
  278. package/esm/select/backdrop/SelectBackdrop.js +1 -1
  279. package/esm/select/group/SelectGroup.js +1 -1
  280. package/esm/select/group-label/SelectGroupLabel.js +2 -2
  281. package/esm/select/icon/SelectIcon.js +1 -1
  282. package/esm/select/item/SelectItem.js +46 -32
  283. package/esm/select/item/SelectItemContext.d.ts +1 -1
  284. package/esm/select/item-indicator/SelectItemIndicator.js +1 -2
  285. package/esm/select/item-text/SelectItemText.js +9 -6
  286. package/esm/select/list/SelectList.js +1 -1
  287. package/esm/select/popup/SelectPopup.js +8 -3
  288. package/esm/select/positioner/SelectPositioner.js +3 -0
  289. package/esm/select/root/SelectRoot.js +46 -40
  290. package/esm/select/root/SelectRootContext.d.ts +4 -5
  291. package/esm/select/store.d.ts +3 -0
  292. package/esm/select/store.js +1 -0
  293. package/esm/select/trigger/SelectTrigger.d.ts +5 -0
  294. package/esm/select/trigger/SelectTrigger.js +19 -33
  295. package/esm/select/trigger/SelectTriggerDataAttributes.d.ts +5 -0
  296. package/esm/select/trigger/SelectTriggerDataAttributes.js +5 -0
  297. package/esm/slider/control/SliderControl.js +10 -12
  298. package/esm/slider/root/SliderRoot.js +1 -4
  299. package/esm/slider/thumb/SliderThumb.js +32 -30
  300. package/esm/slider/value/SliderValue.js +7 -15
  301. package/esm/switch/root/SwitchRoot.js +10 -10
  302. package/esm/switch/thumb/SwitchThumb.js +1 -9
  303. package/esm/tabs/indicator/TabsIndicator.js +14 -19
  304. package/esm/tabs/list/TabsList.js +4 -10
  305. package/esm/tabs/list/TabsListContext.d.ts +2 -1
  306. package/esm/tabs/panel/TabsPanel.js +1 -1
  307. package/esm/tabs/root/TabsRoot.d.ts +16 -1
  308. package/esm/tabs/root/TabsRoot.js +73 -25
  309. package/esm/tabs/root/TabsRootContext.d.ts +0 -2
  310. package/esm/toast/provider/ToastProvider.js +1 -1
  311. package/esm/toast/root/ToastRoot.d.ts +1 -1
  312. package/esm/toast/root/ToastRoot.js +108 -131
  313. package/esm/toast/root/ToastRootDataAttributes.d.ts +1 -1
  314. package/esm/toast/root/ToastRootDataAttributes.js +1 -1
  315. package/esm/toast/store.d.ts +9 -1
  316. package/esm/toast/store.js +19 -13
  317. package/esm/toast/useToastManager.d.ts +1 -1
  318. package/esm/toast/viewport/ToastViewport.js +1 -1
  319. package/esm/toggle/Toggle.js +5 -9
  320. package/esm/toggle-group/ToggleGroup.d.ts +2 -2
  321. package/esm/toggle-group/ToggleGroup.js +6 -13
  322. package/esm/toolbar/link/ToolbarLink.d.ts +1 -1
  323. package/esm/toolbar/link/ToolbarLink.js +1 -2
  324. package/esm/tooltip/arrow/TooltipArrow.js +3 -3
  325. package/esm/tooltip/popup/TooltipPopup.js +5 -4
  326. package/esm/tooltip/root/TooltipRoot.js +35 -26
  327. package/esm/tooltip/store/TooltipHandle.js +1 -1
  328. package/esm/tooltip/store/TooltipStore.d.ts +90 -2
  329. package/esm/tooltip/store/TooltipStore.js +18 -31
  330. package/esm/tooltip/trigger/TooltipTrigger.js +151 -20
  331. package/esm/tooltip/viewport/TooltipViewport.d.ts +2 -2
  332. package/esm/tooltip/viewport/TooltipViewport.js +2 -2
  333. package/esm/tooltip/viewport/TooltipViewportDataAttributes.d.ts +1 -1
  334. package/esm/tooltip/viewport/TooltipViewportDataAttributes.js +1 -1
  335. package/esm/unstable-use-media-query/index.js +1 -1
  336. package/esm/utils/popups/index.d.ts +1 -0
  337. package/esm/utils/popups/index.js +1 -0
  338. package/esm/utils/popups/inlineRect.d.ts +15 -0
  339. package/esm/utils/popups/inlineRect.js +191 -0
  340. package/esm/utils/popups/popupStoreUtils.d.ts +28 -10
  341. package/esm/utils/popups/popupStoreUtils.js +105 -20
  342. package/esm/utils/popups/popupTriggerMap.js +2 -0
  343. package/esm/utils/popups/store.d.ts +15 -2
  344. package/esm/utils/popups/store.js +38 -2
  345. package/esm/utils/popups/useTriggerFocusGuards.js +4 -5
  346. package/esm/utils/useAnchorPositioning.d.ts +5 -0
  347. package/esm/utils/useAnchorPositioning.js +12 -9
  348. package/esm/utils/useOpenInteractionType.d.ts +4 -0
  349. package/esm/utils/useOpenInteractionType.js +23 -18
  350. package/field/control/FieldControl.js +2 -6
  351. package/field/item/FieldItem.js +1 -4
  352. package/field/root/FieldRoot.js +11 -3
  353. package/field/root/useFieldValidation.d.ts +1 -0
  354. package/field/root/useFieldValidation.js +23 -20
  355. package/field/utils/getCombinedFieldValidityData.d.ts +1 -1
  356. package/field/validity/FieldValidity.d.ts +1 -1
  357. package/floating-ui-react/components/FloatingDelayGroup.js +3 -3
  358. package/floating-ui-react/components/FloatingFocusManager.d.ts +1 -1
  359. package/floating-ui-react/components/FloatingFocusManager.js +20 -8
  360. package/floating-ui-react/components/FloatingPortal.js +3 -3
  361. package/floating-ui-react/hooks/useClick.js +82 -73
  362. package/floating-ui-react/hooks/useClientPoint.js +29 -20
  363. package/floating-ui-react/hooks/useDismiss.d.ts +1 -1
  364. package/floating-ui-react/hooks/useDismiss.js +82 -92
  365. package/floating-ui-react/hooks/useFloating.js +36 -32
  366. package/floating-ui-react/hooks/useFloatingRootContext.d.ts +1 -1
  367. package/floating-ui-react/hooks/useFloatingRootContext.js +2 -2
  368. package/floating-ui-react/hooks/useFocus.js +84 -81
  369. package/floating-ui-react/hooks/useHover.js +74 -78
  370. package/floating-ui-react/hooks/useHoverFloatingInteraction.js +48 -43
  371. package/floating-ui-react/hooks/useHoverInteractionSharedState.js +1 -1
  372. package/floating-ui-react/hooks/useHoverReferenceInteraction.d.ts +7 -2
  373. package/floating-ui-react/hooks/useHoverReferenceInteraction.js +43 -38
  374. package/floating-ui-react/hooks/useHoverShared.d.ts +2 -1
  375. package/floating-ui-react/hooks/useHoverShared.js +11 -0
  376. package/floating-ui-react/hooks/useListNavigation.d.ts +1 -3
  377. package/floating-ui-react/hooks/useListNavigation.js +83 -74
  378. package/floating-ui-react/hooks/useSyncedFloatingRootContext.d.ts +9 -6
  379. package/floating-ui-react/hooks/useSyncedFloatingRootContext.js +26 -20
  380. package/floating-ui-react/hooks/useTypeahead.d.ts +2 -2
  381. package/floating-ui-react/hooks/useTypeahead.js +33 -52
  382. package/floating-ui-react/index.d.ts +0 -2
  383. package/floating-ui-react/index.js +0 -14
  384. package/floating-ui-react/types.d.ts +2 -7
  385. package/floating-ui-react/utils/composite.js +2 -0
  386. package/floating-ui-react/utils/enqueueFocus.d.ts +1 -1
  387. package/floating-ui-react/utils/enqueueFocus.js +10 -7
  388. package/floating-ui-react/utils/getEmptyRootContext.js +1 -1
  389. package/form/Form.js +2 -2
  390. package/index.js +1 -1
  391. package/internals/composite/composite.d.ts +0 -1
  392. package/internals/composite/composite.js +2 -3
  393. package/internals/composite/root/useCompositeRoot.js +1 -1
  394. package/internals/createBaseUIEventDetails.d.ts +2 -0
  395. package/internals/csp-context/index.d.ts +2 -0
  396. package/internals/csp-context/index.js +18 -0
  397. package/internals/field-register-control/index.d.ts +0 -1
  398. package/internals/field-register-control/useFieldControlRegistration.d.ts +2 -1
  399. package/internals/field-register-control/useFieldControlRegistration.js +11 -14
  400. package/internals/field-register-control/useRegisterFieldControl.d.ts +1 -4
  401. package/internals/field-register-control/useRegisterFieldControl.js +6 -11
  402. package/internals/field-root-context/FieldRootContext.d.ts +1 -0
  403. package/internals/field-root-context/FieldRootContext.js +4 -3
  404. package/internals/form-context/FormContext.d.ts +5 -1
  405. package/internals/reason-parts.d.ts +2 -0
  406. package/internals/reason-parts.js +3 -1
  407. package/internals/types.d.ts +1 -0
  408. package/internals/use-button/useButton.js +4 -4
  409. package/internals/usePressAndHold.js +2 -2
  410. package/internals/useRenderElement.js +2 -0
  411. package/menu/arrow/MenuArrow.js +1 -1
  412. package/menu/backdrop/MenuBackdrop.js +1 -1
  413. package/menu/checkbox-item/MenuCheckboxItem.js +5 -7
  414. package/menu/group/MenuGroup.js +1 -4
  415. package/menu/group/MenuGroupContext.d.ts +1 -3
  416. package/menu/group/MenuGroupContext.js +1 -1
  417. package/menu/group-label/MenuGroupLabel.js +4 -6
  418. package/menu/link-item/MenuLinkItem.js +2 -2
  419. package/menu/popup/MenuPopup.js +5 -5
  420. package/menu/radio-group/MenuRadioGroup.js +11 -5
  421. package/menu/radio-item/MenuRadioItem.js +5 -7
  422. package/menu/root/MenuRoot.js +60 -65
  423. package/menu/store/MenuHandle.js +1 -1
  424. package/menu/store/MenuStore.d.ts +87 -0
  425. package/menu/submenu-trigger/MenuSubmenuTrigger.js +7 -4
  426. package/menu/trigger/MenuTrigger.js +12 -9
  427. package/menu/viewport/MenuViewport.d.ts +2 -2
  428. package/menu/viewport/MenuViewport.js +2 -2
  429. package/navigation-menu/arrow/NavigationMenuArrow.js +1 -1
  430. package/navigation-menu/backdrop/NavigationMenuBackdrop.js +1 -1
  431. package/navigation-menu/content/NavigationMenuContent.js +8 -5
  432. package/navigation-menu/icon/NavigationMenuIcon.js +1 -1
  433. package/navigation-menu/item/NavigationMenuItem.js +2 -2
  434. package/navigation-menu/list/NavigationMenuList.js +1 -1
  435. package/navigation-menu/popup/NavigationMenuPopup.js +2 -2
  436. package/navigation-menu/root/NavigationMenuRoot.js +1 -3
  437. package/navigation-menu/trigger/NavigationMenuTrigger.js +23 -16
  438. package/navigation-menu/utils/isOutsideMenuEvent.d.ts +0 -1
  439. package/navigation-menu/utils/isOutsideMenuEvent.js +1 -5
  440. package/navigation-menu/viewport/NavigationMenuViewport.js +2 -3
  441. package/number-field/input/NumberFieldInput.js +3 -5
  442. package/number-field/root/NumberFieldRoot.js +5 -2
  443. package/number-field/scrub-area/NumberFieldScrubArea.js +7 -3
  444. package/otp-field/input/OTPFieldInput.js +42 -28
  445. package/otp-field/root/OTPFieldRoot.d.ts +17 -8
  446. package/otp-field/root/OTPFieldRoot.js +32 -32
  447. package/otp-field/root/OTPFieldRootContext.d.ts +1 -1
  448. package/otp-field/utils/otp.d.ts +5 -4
  449. package/otp-field/utils/otp.js +24 -12
  450. package/package.json +331 -317
  451. package/popover/arrow/PopoverArrow.js +1 -1
  452. package/popover/backdrop/PopoverBackdrop.js +1 -1
  453. package/popover/close/PopoverClose.js +2 -2
  454. package/popover/description/PopoverDescription.js +1 -7
  455. package/popover/popup/PopoverPopup.d.ts +1 -1
  456. package/popover/popup/PopoverPopup.js +16 -10
  457. package/popover/popup/PopoverPopupDataAttributes.d.ts +1 -1
  458. package/popover/popup/PopoverPopupDataAttributes.js +1 -1
  459. package/popover/positioner/PopoverPositioner.js +5 -5
  460. package/popover/root/PopoverRoot.d.ts +1 -1
  461. package/popover/root/PopoverRoot.js +39 -44
  462. package/popover/store/PopoverHandle.js +1 -1
  463. package/popover/store/PopoverStore.d.ts +91 -4
  464. package/popover/store/PopoverStore.js +16 -19
  465. package/popover/title/PopoverTitle.js +1 -7
  466. package/popover/trigger/PopoverTrigger.js +23 -16
  467. package/popover/viewport/PopoverViewport.d.ts +3 -3
  468. package/popover/viewport/PopoverViewport.js +2 -2
  469. package/popover/viewport/PopoverViewportDataAttributes.d.ts +1 -1
  470. package/popover/viewport/PopoverViewportDataAttributes.js +1 -1
  471. package/preview-card/positioner/PreviewCardPositioner.js +11 -1
  472. package/preview-card/root/PreviewCardRoot.d.ts +1 -1
  473. package/preview-card/root/PreviewCardRoot.js +30 -20
  474. package/preview-card/store/PreviewCardHandle.js +1 -1
  475. package/preview-card/store/PreviewCardStore.d.ts +90 -2
  476. package/preview-card/store/PreviewCardStore.js +18 -30
  477. package/preview-card/trigger/PreviewCardTrigger.js +5 -2
  478. package/preview-card/viewport/PreviewCardViewport.d.ts +2 -2
  479. package/preview-card/viewport/PreviewCardViewport.js +2 -2
  480. package/preview-card/viewport/PreviewCardViewportDataAttributes.d.ts +2 -2
  481. package/preview-card/viewport/PreviewCardViewportDataAttributes.js +2 -2
  482. package/progress/indicator/ProgressIndicator.js +6 -11
  483. package/progress/root/ProgressRoot.d.ts +1 -1
  484. package/radio/root/RadioRoot.js +7 -3
  485. package/radio-group/RadioGroup.js +4 -11
  486. package/radio-group/RadioGroupContext.d.ts +0 -1
  487. package/scroll-area/content/ScrollAreaContent.js +4 -4
  488. package/scroll-area/root/ScrollAreaRoot.js +1 -1
  489. package/scroll-area/scrollbar/ScrollAreaScrollbar.js +16 -20
  490. package/scroll-area/viewport/ScrollAreaViewport.js +6 -10
  491. package/select/arrow/SelectArrow.js +1 -1
  492. package/select/backdrop/SelectBackdrop.js +1 -1
  493. package/select/group/SelectGroup.js +1 -1
  494. package/select/group-label/SelectGroupLabel.js +2 -2
  495. package/select/icon/SelectIcon.js +1 -1
  496. package/select/item/SelectItem.js +46 -32
  497. package/select/item/SelectItemContext.d.ts +1 -1
  498. package/select/item-indicator/SelectItemIndicator.js +1 -2
  499. package/select/item-text/SelectItemText.js +9 -6
  500. package/select/list/SelectList.js +1 -1
  501. package/select/popup/SelectPopup.js +8 -3
  502. package/select/positioner/SelectPositioner.js +3 -0
  503. package/select/root/SelectRoot.js +45 -39
  504. package/select/root/SelectRootContext.d.ts +4 -5
  505. package/select/store.d.ts +3 -0
  506. package/select/store.js +1 -0
  507. package/select/trigger/SelectTrigger.d.ts +5 -0
  508. package/select/trigger/SelectTrigger.js +19 -33
  509. package/select/trigger/SelectTriggerDataAttributes.d.ts +5 -0
  510. package/select/trigger/SelectTriggerDataAttributes.js +5 -0
  511. package/slider/control/SliderControl.js +9 -11
  512. package/slider/root/SliderRoot.js +1 -4
  513. package/slider/thumb/SliderThumb.js +32 -30
  514. package/slider/value/SliderValue.js +7 -15
  515. package/switch/root/SwitchRoot.js +10 -10
  516. package/switch/thumb/SwitchThumb.js +1 -9
  517. package/tabs/indicator/TabsIndicator.js +14 -19
  518. package/tabs/list/TabsList.js +4 -10
  519. package/tabs/list/TabsListContext.d.ts +2 -1
  520. package/tabs/panel/TabsPanel.js +1 -1
  521. package/tabs/root/TabsRoot.d.ts +16 -1
  522. package/tabs/root/TabsRoot.js +71 -24
  523. package/tabs/root/TabsRootContext.d.ts +0 -2
  524. package/toast/provider/ToastProvider.js +1 -1
  525. package/toast/root/ToastRoot.d.ts +1 -1
  526. package/toast/root/ToastRoot.js +110 -133
  527. package/toast/root/ToastRootDataAttributes.d.ts +1 -1
  528. package/toast/root/ToastRootDataAttributes.js +1 -1
  529. package/toast/store.d.ts +9 -1
  530. package/toast/store.js +18 -12
  531. package/toast/useToastManager.d.ts +1 -1
  532. package/toast/viewport/ToastViewport.js +1 -1
  533. package/toggle/Toggle.js +5 -9
  534. package/toggle-group/ToggleGroup.d.ts +2 -2
  535. package/toggle-group/ToggleGroup.js +6 -13
  536. package/toolbar/link/ToolbarLink.d.ts +1 -1
  537. package/toolbar/link/ToolbarLink.js +1 -2
  538. package/tooltip/arrow/TooltipArrow.js +3 -3
  539. package/tooltip/popup/TooltipPopup.js +5 -4
  540. package/tooltip/root/TooltipRoot.js +32 -23
  541. package/tooltip/store/TooltipHandle.js +1 -1
  542. package/tooltip/store/TooltipStore.d.ts +90 -2
  543. package/tooltip/store/TooltipStore.js +17 -30
  544. package/tooltip/trigger/TooltipTrigger.js +152 -20
  545. package/tooltip/viewport/TooltipViewport.d.ts +2 -2
  546. package/tooltip/viewport/TooltipViewport.js +2 -2
  547. package/tooltip/viewport/TooltipViewportDataAttributes.d.ts +1 -1
  548. package/tooltip/viewport/TooltipViewportDataAttributes.js +1 -1
  549. package/unstable-use-media-query/index.js +1 -1
  550. package/utils/popups/index.d.ts +1 -0
  551. package/utils/popups/index.js +11 -0
  552. package/utils/popups/inlineRect.d.ts +15 -0
  553. package/utils/popups/inlineRect.js +198 -0
  554. package/utils/popups/popupStoreUtils.d.ts +28 -10
  555. package/utils/popups/popupStoreUtils.js +110 -20
  556. package/utils/popups/popupTriggerMap.js +2 -0
  557. package/utils/popups/store.d.ts +15 -2
  558. package/utils/popups/store.js +39 -2
  559. package/utils/popups/useTriggerFocusGuards.js +4 -5
  560. package/utils/useAnchorPositioning.d.ts +5 -0
  561. package/utils/useAnchorPositioning.js +12 -9
  562. package/utils/useOpenInteractionType.d.ts +4 -0
  563. package/utils/useOpenInteractionType.js +24 -17
  564. package/checkbox-group/index.parts.d.ts +0 -1
  565. package/checkbox-group/index.parts.js +0 -12
  566. package/esm/checkbox-group/index.parts.d.ts +0 -1
  567. package/esm/checkbox-group/index.parts.js +0 -1
  568. package/esm/floating-ui-react/hooks/useInteractions.d.ts +0 -20
  569. package/esm/floating-ui-react/hooks/useInteractions.js +0 -88
  570. package/esm/floating-ui-react/hooks/useRole.d.ts +0 -17
  571. package/esm/floating-ui-react/hooks/useRole.js +0 -113
  572. package/floating-ui-react/hooks/useInteractions.d.ts +0 -20
  573. package/floating-ui-react/hooks/useInteractions.js +0 -95
  574. package/floating-ui-react/hooks/useRole.d.ts +0 -17
  575. package/floating-ui-react/hooks/useRole.js +0 -120
  576. /package/{csp-provider → esm/internals/csp-context}/CSPContext.d.ts +0 -0
  577. /package/esm/{csp-provider → internals/csp-context}/CSPContext.js +0 -0
  578. /package/{esm/csp-provider → internals/csp-context}/CSPContext.d.ts +0 -0
  579. /package/{csp-provider → internals/csp-context}/CSPContext.js +0 -0
@@ -3,340 +3,380 @@
3
3
  import * as React from 'react';
4
4
  import { addEventListener } from '@base-ui/utils/addEventListener';
5
5
  import { useIsoLayoutEffect } from '@base-ui/utils/useIsoLayoutEffect';
6
- import { useStableCallback } from '@base-ui/utils/useStableCallback';
7
6
  import { useMergedRefs } from '@base-ui/utils/useMergedRefs';
8
- import { useOnMount } from '@base-ui/utils/useOnMount';
9
- import { AnimationFrame, useAnimationFrame } from '@base-ui/utils/useAnimationFrame';
7
+ import { AnimationFrame } from '@base-ui/utils/useAnimationFrame';
8
+ import { useStableCallback } from '@base-ui/utils/useStableCallback';
9
+ import { useValueAsRef } from '@base-ui/utils/useValueAsRef';
10
10
  import { warn } from '@base-ui/utils/warn';
11
+ import { ownerWindow } from '@base-ui/utils/owner';
11
12
  import { createChangeEventDetails } from "../../internals/createBaseUIEventDetails.js";
12
13
  import { REASONS } from "../../internals/reasons.js";
14
+ import { useOpenChangeComplete } from "../../internals/useOpenChangeComplete.js";
15
+ import { useAnimationsFinished } from "../../internals/useAnimationsFinished.js";
13
16
  import { CollapsiblePanelDataAttributes } from "./CollapsiblePanelDataAttributes.js";
14
- import { AccordionRootDataAttributes } from "../../accordion/root/AccordionRootDataAttributes.js";
17
+ const EMPTY_DIMENSIONS = {
18
+ height: undefined,
19
+ width: undefined
20
+ };
15
21
  export function useCollapsiblePanel(parameters) {
16
22
  const {
17
- abortControllerRef,
18
- animationTypeRef,
19
23
  externalRef,
20
- height,
21
24
  hiddenUntilFound,
22
- keepMounted,
23
25
  id: idParam,
26
+ keepMounted,
24
27
  mounted,
25
28
  onOpenChange,
26
29
  open,
27
- panelRef,
28
- runOnceAnimationsFinish,
29
- setDimensions,
30
30
  setMounted,
31
31
  setOpen,
32
- setVisible,
33
- transitionDimensionRef,
34
- visible,
35
- width
32
+ transitionStatus
36
33
  } = parameters;
37
- const isBeforeMatchRef = React.useRef(false);
38
- const latestAnimationNameRef = React.useRef(null);
39
- const shouldCancelInitialOpenAnimationRef = React.useRef(open);
40
- const shouldCancelInitialOpenTransitionRef = React.useRef(open);
41
- const endingStyleFrame = useAnimationFrame();
42
-
43
- /**
44
- * When opening, the `hidden` attribute is removed immediately.
45
- * When closing, the `hidden` attribute is set after any exit animations runs.
46
- */
47
- const hidden = React.useMemo(() => {
48
- if (animationTypeRef.current === 'css-animation') {
49
- return !visible;
50
- }
51
- return !open && !mounted;
52
- }, [open, mounted, visible, animationTypeRef]);
34
+ const panelRef = React.useRef(null);
35
+ const animationTypeRef = React.useRef(null);
36
+ const [dimensions, setDimensionsUnwrapped] = React.useState(EMPTY_DIMENSIONS);
37
+ const lastMeasuredDimensionsRef = React.useRef(EMPTY_DIMENSIONS);
38
+ // `beforematch` should reveal the matched content immediately, so the next
39
+ // open cycle skips author-defined motion once and then returns to normal.
40
+ const shouldSkipNextOpenRef = React.useRef(false);
41
+ // Keyframe mount animations on initially open panels cause a visible layout
42
+ // shift during the server-rendered first paint, so suppress that first open
43
+ // lifecycle until the panel has been closed once.
44
+ const shouldPreventMountAnimationRef = React.useRef(open);
45
+ // React.Activity tears down Effects while preserving state, so revealing an
46
+ // already-open panel would otherwise replay its CSS keyframe open animation.
47
+ const shouldPreventActivityResumeAnimationRef = React.useRef(false);
48
+ // Some open paths intentionally bypass motion, but the shared root transition
49
+ // status still advances asynchronously. Override the panel to idle so its data
50
+ // attributes and dimension cleanup reflect the immediate open state.
51
+ const [forcePanelIdle, setForcePanelIdle] = React.useState(false);
52
+ const pendingTemporaryStyleRestoreRef = React.useRef(null);
53
+ const mergedPanelRef = useMergedRefs(externalRef, panelRef);
54
+ const latestStateRef = useValueAsRef({
55
+ mounted,
56
+ open
57
+ });
58
+ // Only used to handle panel close
59
+ const runOnceCloseAnimationsFinish = useAnimationsFinished(panelRef, false, false);
60
+ const hidden = !open && !mounted;
61
+ const panelTransitionStatus = forcePanelIdle ? 'idle' : transitionStatus;
62
+ const shouldPreventOpenAnimation = open && (
63
+ // These 2 refs are safe to read in render, they are only written from committed
64
+ // layout/effect paths and gate one-shot motion suppression for the next open
65
+ // lifecycle. They intentionally expose the last committed motion snapshot.
66
+ shouldPreventMountAnimationRef.current || shouldPreventActivityResumeAnimationRef.current);
67
+ const renderedDimensions = !open && mounted &&
68
+ // These 2 refs are also safe to read in render, both hold the last committed
69
+ // animation mode and measurement. This fallback only restores a previously
70
+ // measured pixel size after the live dimensions state has been reset back to `auto`.
71
+ animationTypeRef.current === 'css-animation' && dimensions.height === undefined && dimensions.width === undefined ? lastMeasuredDimensionsRef.current : dimensions;
72
+ const shouldPersistHiddenTransitionStyles = hiddenUntilFound && hidden && animationTypeRef.current !== 'css-animation';
53
73
 
54
- /**
55
- * When `keepMounted` is `true` this runs once as soon as it exists in the DOM
56
- * regardless of initial open state.
57
- *
58
- * When `keepMounted` is `false` this runs on every mount, typically every
59
- * time it opens. If the panel is in the middle of a close transition that is
60
- * interrupted and re-opens, this won't run as the panel was not unmounted.
61
- */
62
- const handlePanelRef = useStableCallback(element => {
63
- if (!element) {
64
- return undefined;
74
+ // Most measured dimensions are reused later when CSS keyframe closes need a
75
+ // pixel size after the rendered dimensions have been reset back to `auto`.
76
+ // Passing `false` is only for clearing the current dimensions state.
77
+ const setDimensions = useStableCallback((nextDimensions, shouldCacheMeasurement = true) => {
78
+ if (shouldCacheMeasurement) {
79
+ lastMeasuredDimensionsRef.current = nextDimensions;
65
80
  }
66
- if (animationTypeRef.current == null || transitionDimensionRef.current == null) {
67
- const panelStyles = getComputedStyle(element);
68
- const hasAnimation = panelStyles.animationName !== 'none' && panelStyles.animationName !== '';
69
- const hasTransition = panelStyles.transitionDuration !== '0s' && panelStyles.transitionDuration !== '';
70
-
71
- /**
72
- * animationTypeRef is safe to read in render because it's only ever set
73
- * once here during the first render and never again.
74
- * https://react.dev/learn/referencing-values-with-refs#best-practices-for-refs
75
- */
76
- if (hasAnimation && hasTransition) {
77
- if (process.env.NODE_ENV !== 'production') {
78
- warn('CSS transitions and CSS animations both detected on Collapsible or Accordion panel.', 'Only one of either animation type should be used.');
79
- }
80
- } else if (panelStyles.animationName === 'none' && panelStyles.transitionDuration !== '0s') {
81
- animationTypeRef.current = 'css-transition';
82
- } else if (panelStyles.animationName !== 'none' && panelStyles.transitionDuration === '0s') {
83
- animationTypeRef.current = 'css-animation';
84
- } else {
85
- animationTypeRef.current = 'none';
86
- }
81
+ setDimensionsUnwrapped(nextDimensions);
82
+ });
83
+ const restorePendingTemporaryStyle = useStableCallback(() => {
84
+ pendingTemporaryStyleRestoreRef.current?.();
85
+ pendingTemporaryStyleRestoreRef.current = null;
86
+ });
87
+ const setPendingTemporaryStyleRestore = useStableCallback(restore => {
88
+ restorePendingTemporaryStyle();
89
+ pendingTemporaryStyleRestoreRef.current = () => {
90
+ pendingTemporaryStyleRestoreRef.current = null;
91
+ restore();
92
+ };
93
+ });
87
94
 
88
- /**
89
- * We need to know in advance which side is being collapsed when using CSS
90
- * transitions in order to set the value of width/height to `0px` momentarily.
91
- * Setting both to `0px` will break layout.
92
- */
93
- if (element.getAttribute(AccordionRootDataAttributes.orientation) === 'horizontal' || panelStyles.transitionProperty.indexOf('width') > -1) {
94
- transitionDimensionRef.current = 'width';
95
- } else {
96
- transitionDimensionRef.current = 'height';
97
- }
98
- }
99
- if (animationTypeRef.current !== 'css-transition') {
100
- return undefined;
95
+ // React.Activity unmounts Effects while preserving component state. If that
96
+ // teardown happens while an already-open keyframe panel is visible, remember
97
+ // to suppress the replayed open animation on the next committed reveal.
98
+ const markActivityResumeAnimationSuppressed = useStableCallback(() => {
99
+ if (open && mounted && animationTypeRef.current === 'css-animation') {
100
+ shouldPreventActivityResumeAnimationRef.current = true;
101
101
  }
102
- if (height === undefined || width === undefined) {
103
- setDimensions({
104
- height: element.scrollHeight,
105
- width: element.scrollWidth
106
- });
107
- if (shouldCancelInitialOpenTransitionRef.current) {
108
- element.style.setProperty('transition-duration', '0s');
109
- }
102
+ });
103
+ useIsoLayoutEffect(() => {
104
+ // `forcePanelIdle` is only a temporary override for open paths that skip
105
+ // motion. Keep it active while the shared root still reports `starting`,
106
+ // then drop it once the root transition state catches up.
107
+ if (!forcePanelIdle || transitionStatus === 'starting') {
108
+ return;
110
109
  }
111
- let frame = -1;
112
- let nextFrame = -1;
113
- frame = AnimationFrame.request(() => {
114
- shouldCancelInitialOpenTransitionRef.current = false;
115
- nextFrame = AnimationFrame.request(() => {
116
- /**
117
- * This is slightly faster than another RAF and is the earliest
118
- * opportunity to remove the temporary `transition-duration: 0s` that
119
- * was applied to cancel opening transitions of initially open panels.
120
- * https://nolanlawson.com/2018/09/25/accurately-measuring-layout-on-the-web/
121
- */
122
- setTimeout(() => {
123
- element.style.removeProperty('transition-duration');
124
- });
125
- });
126
- });
110
+ setForcePanelIdle(false);
111
+ }, [forcePanelIdle, transitionStatus]);
112
+ React.useEffect(() => {
127
113
  return () => {
128
- AnimationFrame.cancel(frame);
129
- AnimationFrame.cancel(nextFrame);
114
+ markActivityResumeAnimationSuppressed();
115
+ restorePendingTemporaryStyle();
130
116
  };
131
- });
132
- const mergedPanelRef = useMergedRefs(externalRef, panelRef, handlePanelRef);
117
+ }, [markActivityResumeAnimationSuppressed, restorePendingTemporaryStyle]);
133
118
  useIsoLayoutEffect(() => {
134
- if (animationTypeRef.current !== 'css-transition') {
135
- return undefined;
136
- }
137
119
  const panel = panelRef.current;
138
120
  if (!panel) {
139
121
  return undefined;
140
122
  }
141
- let resizeFrame = -1;
142
- if (abortControllerRef.current != null) {
143
- abortControllerRef.current.abort();
144
- abortControllerRef.current = null;
123
+
124
+ // `beforematch` can temporarily force a `0s` motion duration so the matched
125
+ // content reveals immediately. Restore the authored duration before detecting
126
+ // the next close animation type, otherwise that first close is misread as
127
+ // "no motion" and the close transition or keyframe gets skipped.
128
+ if (!open && pendingTemporaryStyleRestoreRef.current) {
129
+ restorePendingTemporaryStyle();
145
130
  }
146
- if (open) {
147
- const originalLayoutStyles = {
148
- 'justify-content': panel.style.justifyContent,
149
- 'align-items': panel.style.alignItems,
150
- 'align-content': panel.style.alignContent,
151
- 'justify-items': panel.style.justifyItems
152
- };
131
+ const animationType = getAnimationType(panel, shouldPreventOpenAnimation);
132
+ animationTypeRef.current = animationType;
153
133
 
154
- /* opening */
155
- Object.keys(originalLayoutStyles).forEach(key => {
156
- panel.style.setProperty(key, 'initial', 'important');
157
- });
134
+ // Initially open keyframe panels skip their first paint animation to avoid
135
+ // layout shift, but we still need to cache the expanded size so the first
136
+ // close animation can start from pixels instead of `auto`.
137
+ if (open && transitionStatus === 'idle' && shouldPreventMountAnimationRef.current && animationType === 'css-animation') {
138
+ lastMeasuredDimensionsRef.current = getDimensions(panel);
139
+ return undefined;
140
+ }
158
141
 
159
- /**
160
- * When `keepMounted={false}` and the panel is initially closed, the very
161
- * first time it opens (not any subsequent opens) `data-starting-style` is
162
- * off or missing by a frame so we need to set it manually. Otherwise any
163
- * CSS properties expected to transition using [data-starting-style] may
164
- * be mis-timed and appear to be complete skipped.
165
- */
166
- if (!shouldCancelInitialOpenTransitionRef.current && !keepMounted) {
167
- panel.setAttribute(CollapsiblePanelDataAttributes.startingStyle, '');
168
- }
169
- setDimensions({
170
- height: panel.scrollHeight,
171
- width: panel.scrollWidth
172
- });
173
- resizeFrame = AnimationFrame.request(() => {
174
- Object.entries(originalLayoutStyles).forEach(([key, value]) => {
175
- if (value === '') {
176
- panel.style.removeProperty(key);
177
- } else {
178
- panel.style.setProperty(key, value);
179
- }
180
- });
181
- });
182
- } else {
183
- if (panel.scrollHeight === 0 && panel.scrollWidth === 0) {
142
+ // Handle the opening pass: measure the expanded size and, when necessary,
143
+ // neutralize author-defined motion so the panel can open immediately.
144
+ if (open && transitionStatus === 'starting') {
145
+ // `beforematch` opens should reveal the panel immediately so find-in-page
146
+ // does not wait for the author-defined transition or animation to finish.
147
+ const skipNextOpen = shouldSkipNextOpenRef.current;
148
+ shouldSkipNextOpenRef.current = false;
149
+ if (animationType === 'none') {
150
+ setDimensions(getDimensions(panel));
151
+ setForcePanelIdle(true);
184
152
  return undefined;
185
153
  }
186
-
187
- /* closing */
188
- setDimensions({
189
- height: panel.scrollHeight,
190
- width: panel.scrollWidth
191
- });
192
- const abortController = new AbortController();
193
- abortControllerRef.current = abortController;
194
- const signal = abortController.signal;
195
- let attributeObserver = null;
196
- const endingStyleAttribute = CollapsiblePanelDataAttributes.endingStyle;
197
-
198
- // Wait for `[data-ending-style]` to be applied.
199
- attributeObserver = new MutationObserver(mutationList => {
200
- const hasEndingStyle = mutationList.some(mutation => mutation.type === 'attributes' && mutation.attributeName === endingStyleAttribute);
201
- if (hasEndingStyle) {
202
- attributeObserver?.disconnect();
203
- attributeObserver = null;
204
- runOnceAnimationsFinish(() => {
205
- setDimensions({
206
- height: 0,
207
- width: 0
208
- });
209
- panel.style.removeProperty('content-visibility');
210
- setMounted(false);
211
- if (abortControllerRef.current === abortController) {
212
- abortControllerRef.current = null;
213
- }
214
- }, signal);
154
+ if (animationType === 'css-transition') {
155
+ const restoreLayoutStyles = resetLayoutStyles(panel);
156
+ setDimensions(getDimensions(panel));
157
+ if (!skipNextOpen) {
158
+ return restoreLayoutStyles;
215
159
  }
216
- });
217
- attributeObserver.observe(panel, {
218
- attributes: true,
219
- attributeFilter: [endingStyleAttribute]
220
- });
221
- return () => {
222
- attributeObserver?.disconnect();
223
- endingStyleFrame.cancel();
224
- if (abortControllerRef.current === abortController) {
225
- abortController.abort();
226
- abortControllerRef.current = null;
160
+ const restoreTransitionDuration = setTemporaryStyle(panel, 'transition-duration', '0s');
161
+ setPendingTemporaryStyleRestore(restoreTransitionDuration);
162
+ setForcePanelIdle(true);
163
+ return restoreLayoutStyles;
164
+ }
165
+ if (animationType === 'css-animation') {
166
+ setDimensions(getDimensions(panel));
167
+ if (!skipNextOpen) {
168
+ const restoreAnimationName = setTemporaryStyle(panel, 'animation-name', 'none');
169
+ restoreAnimationName();
170
+ return undefined;
227
171
  }
228
- };
172
+ const restoreAnimationName = setTemporaryStyle(panel, 'animation-name', 'none');
173
+ const restoreAnimationDuration = setTemporaryStyle(panel, 'animation-duration', '0s');
174
+ restoreAnimationName();
175
+ setPendingTemporaryStyleRestore(restoreAnimationDuration);
176
+ setForcePanelIdle(true);
177
+ return undefined;
178
+ }
229
179
  }
230
- return () => {
231
- AnimationFrame.cancel(resizeFrame);
232
- };
233
- }, [abortControllerRef, animationTypeRef, endingStyleFrame, hiddenUntilFound, keepMounted, mounted, open, panelRef, runOnceAnimationsFinish, setDimensions, setMounted]);
234
- useIsoLayoutEffect(() => {
235
- if (animationTypeRef.current !== 'css-animation') {
236
- return;
180
+
181
+ // Capture the current size as soon as close is requested, before the
182
+ // deferred ending phase applies closed styles. This keeps close transitions
183
+ // starting from a measured pixel value, including interrupted opens.
184
+ if (!open && mounted && (transitionStatus === 'idle' || transitionStatus === 'starting')) {
185
+ if (animationType === 'none') {
186
+ setDimensions(EMPTY_DIMENSIONS, false);
187
+ setMounted(false);
188
+ return undefined;
189
+ }
190
+ if (animationType === 'css-animation') {
191
+ shouldPreventMountAnimationRef.current = false;
192
+ shouldPreventActivityResumeAnimationRef.current = false;
193
+ }
194
+ setDimensions(getDimensions(panel));
195
+ return undefined;
237
196
  }
238
- const panel = panelRef.current;
239
- if (!panel) {
240
- return;
197
+ if (transitionStatus !== 'ending') {
198
+ return undefined;
241
199
  }
242
- latestAnimationNameRef.current = panel.style.animationName || latestAnimationNameRef.current;
243
- panel.style.setProperty('animation-name', 'none');
244
- setDimensions({
245
- height: panel.scrollHeight,
246
- width: panel.scrollWidth
247
- });
248
- if (!shouldCancelInitialOpenAnimationRef.current && !isBeforeMatchRef.current) {
249
- panel.style.removeProperty('animation-name');
200
+ if (animationType === 'none') {
201
+ setMounted(false);
202
+ return undefined;
203
+ }
204
+ const nextDimensions = getDimensions(panel);
205
+ const hasMeasuredSize = (nextDimensions.height ?? 0) > 0 || (nextDimensions.width ?? 0) > 0;
206
+ if (!hasMeasuredSize) {
207
+ setMounted(false);
208
+ return undefined;
250
209
  }
251
- if (open) {
252
- if (abortControllerRef.current != null) {
253
- abortControllerRef.current.abort();
254
- abortControllerRef.current = null;
210
+ setDimensions(nextDimensions);
211
+ if (animationType === 'css-animation') {
212
+ const restoreAnimationName = setTemporaryStyle(panel, 'animation-name', 'none');
213
+ restoreAnimationName();
214
+ }
215
+ return undefined;
216
+ }, [mounted, open, restorePendingTemporaryStyle, setDimensions, setMounted, setPendingTemporaryStyleRestore, shouldPreventOpenAnimation, transitionStatus]);
217
+ useOpenChangeComplete({
218
+ enabled: open && mounted && panelTransitionStatus === 'idle',
219
+ open: true,
220
+ ref: panelRef,
221
+ onComplete() {
222
+ if (!open) {
223
+ return;
255
224
  }
256
- setMounted(true);
257
- setVisible(true);
258
- } else {
259
- abortControllerRef.current = new AbortController();
260
- runOnceAnimationsFinish(() => {
261
- setMounted(false);
262
- setVisible(false);
263
- abortControllerRef.current = null;
264
- }, abortControllerRef.current.signal);
225
+ setDimensions(EMPTY_DIMENSIONS, false);
265
226
  }
266
- }, [abortControllerRef, animationTypeRef, open, panelRef, runOnceAnimationsFinish, setDimensions, setMounted, setVisible, visible]);
267
- useOnMount(() => {
268
- const frame = AnimationFrame.request(() => {
269
- shouldCancelInitialOpenAnimationRef.current = false;
270
- });
271
- return () => AnimationFrame.cancel(frame);
272
227
  });
273
- useIsoLayoutEffect(() => {
274
- if (!hiddenUntilFound) {
228
+
229
+ // Closing panels need extra sequencing beyond `useOpenChangeComplete`.
230
+ // This passive effect runs after the `ending` render has committed, so
231
+ // `[data-ending-style]` is already present. Chrome can still register the
232
+ // exit transition one frame later when an Accordion closes one item while
233
+ // opening another, so wait one frame before watching animations.
234
+ // See https://github.com/mui/base-ui/issues/3099
235
+ React.useEffect(() => {
236
+ if (open || !mounted || panelTransitionStatus !== 'ending') {
275
237
  return undefined;
276
238
  }
277
239
  const panel = panelRef.current;
278
240
  if (!panel) {
279
241
  return undefined;
280
242
  }
281
- let frame = -1;
282
- let nextFrame = -1;
283
- if (open && isBeforeMatchRef.current) {
284
- panel.style.transitionDuration = '0s';
285
- setDimensions({
286
- height: panel.scrollHeight,
287
- width: panel.scrollWidth
288
- });
289
- frame = AnimationFrame.request(() => {
290
- isBeforeMatchRef.current = false;
291
- nextFrame = AnimationFrame.request(() => {
292
- setTimeout(() => {
293
- panel.style.removeProperty('transition-duration');
294
- });
295
- });
296
- });
243
+ const abortController = new AbortController();
244
+ let endingStyleFrame = -1;
245
+ function handleComplete() {
246
+ if (latestStateRef.current.open) {
247
+ return;
248
+ }
249
+ setMounted(false);
250
+ setDimensions(EMPTY_DIMENSIONS, false);
297
251
  }
252
+ endingStyleFrame = AnimationFrame.request(() => {
253
+ if (!abortController.signal.aborted) {
254
+ runOnceCloseAnimationsFinish(handleComplete, abortController.signal);
255
+ }
256
+ });
298
257
  return () => {
299
- AnimationFrame.cancel(frame);
300
- AnimationFrame.cancel(nextFrame);
258
+ AnimationFrame.cancel(endingStyleFrame);
259
+ abortController.abort();
301
260
  };
302
- }, [hiddenUntilFound, open, panelRef, setDimensions]);
261
+ }, [latestStateRef, mounted, open, panelTransitionStatus, runOnceCloseAnimationsFinish, setDimensions, setMounted]);
303
262
  useIsoLayoutEffect(() => {
304
263
  const panel = panelRef.current;
305
- if (panel && hiddenUntilFound && hidden) {
306
- /**
307
- * React only supports a boolean for the `hidden` attribute and forces
308
- * legit string values to booleans so we have to force it back in the DOM
309
- * when necessary: https://github.com/facebook/react/issues/24740
310
- */
311
- panel.setAttribute('hidden', 'until-found');
312
- /**
313
- * Set data-starting-style here to persist the closed styles, this is to
314
- * prevent transitions from starting when the `hidden` attribute changes
315
- * to `'until-found'` as they could have different `display` properties:
316
- * https://github.com/tailwindlabs/tailwindcss/pull/14625
317
- */
318
- if (animationTypeRef.current === 'css-transition') {
319
- panel.setAttribute(CollapsiblePanelDataAttributes.startingStyle, '');
320
- }
264
+ if (!panel || !hiddenUntilFound || !hidden) {
265
+ return;
321
266
  }
322
- }, [hiddenUntilFound, hidden, animationTypeRef, panelRef]);
267
+
268
+ // React only supports a boolean for the `hidden` attribute and forces
269
+ // legit string values to booleans so we have to force it back in the DOM
270
+ // when necessary: https://github.com/facebook/react/issues/24740
271
+ panel.setAttribute('hidden', 'until-found');
272
+ }, [hidden, hiddenUntilFound]);
323
273
  React.useEffect(function registerBeforeMatchListener() {
324
274
  const panel = panelRef.current;
325
275
  if (!panel) {
326
276
  return undefined;
327
277
  }
328
278
  function handleBeforeMatch(event) {
329
- isBeforeMatchRef.current = true;
279
+ shouldSkipNextOpenRef.current = true;
330
280
  setOpen(true);
331
281
  onOpenChange(true, createChangeEventDetails(REASONS.none, event));
332
282
  }
333
283
  return addEventListener(panel, 'beforematch', handleBeforeMatch);
334
- }, [onOpenChange, panelRef, setOpen]);
335
- return React.useMemo(() => ({
284
+ }, [onOpenChange, setOpen]);
285
+ const shouldRender = keepMounted || hiddenUntilFound || mounted || open;
286
+ return {
287
+ height: renderedDimensions.height,
336
288
  props: {
289
+ ...(shouldPersistHiddenTransitionStyles ? {
290
+ [CollapsiblePanelDataAttributes.startingStyle]: ''
291
+ } : undefined),
337
292
  hidden,
338
- id: idParam,
339
- ref: mergedPanelRef
293
+ id: idParam
294
+ },
295
+ ref: mergedPanelRef,
296
+ shouldPreventOpenAnimation,
297
+ shouldRender,
298
+ transitionStatus: panelTransitionStatus,
299
+ width: renderedDimensions.width
300
+ };
301
+ }
302
+ function getDimensions(element) {
303
+ return {
304
+ height: element.scrollHeight,
305
+ width: element.scrollWidth
306
+ };
307
+ }
308
+ function getAnimationType(element, hasSuppressedMountAnimation = false) {
309
+ const panelStyles = ownerWindow(element).getComputedStyle(element);
310
+ const hasAnimation = (panelStyles.animationName.split(',').map(name => name.trim()).some(name => name !== '' && name !== 'none') || hasSuppressedMountAnimation) && hasNonZeroDuration(panelStyles.animationDuration);
311
+ const hasTransition = hasNonZeroDuration(panelStyles.transitionDuration);
312
+ if (hasAnimation && hasTransition) {
313
+ if (process.env.NODE_ENV !== 'production') {
314
+ warn('CSS transitions and CSS animations both detected on Collapsible or Accordion panel.', 'Only one of either animation type should be used.');
315
+ }
316
+ return 'css-transition';
317
+ }
318
+ if (hasTransition) {
319
+ return 'css-transition';
320
+ }
321
+ if (hasAnimation) {
322
+ return 'css-animation';
323
+ }
324
+ return 'none';
325
+ }
326
+ function hasNonZeroDuration(value) {
327
+ return value.split(',').map(part => part.trim()).some(part => part !== '' && Number.parseFloat(part) > 0);
328
+ }
329
+
330
+ /**
331
+ * Temporarily overrides an inline style property and returns a cleanup that
332
+ * restores the previous inline value and priority.
333
+ * @param element - The element whose inline style should be updated.
334
+ * @param property - The CSS property name to override.
335
+ * @param value - The temporary value to assign.
336
+ * @returns A cleanup function that restores the original inline style state.
337
+ */
338
+ function setTemporaryStyle(element, property, value) {
339
+ const previousValue = element.style.getPropertyValue(property);
340
+ const previousPriority = element.style.getPropertyPriority(property);
341
+ element.style.setProperty(property, value);
342
+ return () => {
343
+ if (previousValue === '') {
344
+ element.style.removeProperty(property);
345
+ return;
340
346
  }
341
- }), [hidden, idParam, mergedPanelRef]);
347
+ element.style.setProperty(property, previousValue, previousPriority);
348
+ };
349
+ }
350
+
351
+ /**
352
+ * Temporarily resets inline alignment styles that can distort scroll-based
353
+ * size measurements, then restores them on the next animation frame.
354
+ * @param element - The panel element being measured.
355
+ * @returns A cleanup function that cancels the scheduled restore and reapplies
356
+ * the original inline layout styles immediately.
357
+ */
358
+ function resetLayoutStyles(element) {
359
+ const originalLayoutStyles = {
360
+ 'justify-content': element.style.justifyContent,
361
+ 'align-items': element.style.alignItems,
362
+ 'align-content': element.style.alignContent,
363
+ 'justify-items': element.style.justifyItems
364
+ };
365
+ Object.keys(originalLayoutStyles).forEach(key => {
366
+ element.style.setProperty(key, 'initial', 'important');
367
+ });
368
+ function restoreLayoutStyles() {
369
+ Object.entries(originalLayoutStyles).forEach(([key, value]) => {
370
+ if (value === '') {
371
+ element.style.removeProperty(key);
372
+ return;
373
+ }
374
+ element.style.setProperty(key, value);
375
+ });
376
+ }
377
+ const frame = AnimationFrame.request(restoreLayoutStyles);
378
+ return () => {
379
+ AnimationFrame.cancel(frame);
380
+ restoreLayoutStyles();
381
+ };
342
382
  }