@limonify/zest-ui 0.3.2 → 0.4.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 (381) hide show
  1. package/README.md +3 -0
  2. package/lib/alert-dialog/index.parts.d.ts +1 -1
  3. package/lib/alert-dialog/index.parts.d.ts.map +1 -1
  4. package/lib/alert-dialog/index.parts.js.map +1 -1
  5. package/lib/autocomplete/handle.d.ts +15 -0
  6. package/lib/autocomplete/handle.d.ts.map +1 -0
  7. package/lib/autocomplete/handle.js +12 -0
  8. package/lib/autocomplete/handle.js.map +1 -0
  9. package/lib/autocomplete/index.d.ts +1 -0
  10. package/lib/autocomplete/index.d.ts.map +1 -1
  11. package/lib/autocomplete/index.parts.d.ts +1 -0
  12. package/lib/autocomplete/index.parts.d.ts.map +1 -1
  13. package/lib/autocomplete/index.parts.js +3 -1
  14. package/lib/autocomplete/index.parts.js.map +1 -1
  15. package/lib/autocomplete/root/AutocompleteRoot.d.ts +55 -16
  16. package/lib/autocomplete/root/AutocompleteRoot.d.ts.map +1 -1
  17. package/lib/autocomplete/root/AutocompleteRoot.js +2 -18
  18. package/lib/autocomplete/root/AutocompleteRoot.js.map +1 -1
  19. package/lib/avatar/fallback/AvatarFallback.d.ts.map +1 -1
  20. package/lib/avatar/fallback/AvatarFallback.js +3 -1
  21. package/lib/avatar/fallback/AvatarFallback.js.map +1 -1
  22. package/lib/avatar/image/AvatarImage.d.ts.map +1 -1
  23. package/lib/avatar/image/AvatarImage.js +5 -0
  24. package/lib/avatar/image/AvatarImage.js.map +1 -1
  25. package/lib/checkbox/root/CheckboxRoot.d.ts.map +1 -1
  26. package/lib/checkbox/root/CheckboxRoot.js +9 -4
  27. package/lib/checkbox/root/CheckboxRoot.js.map +1 -1
  28. package/lib/combobox/backdrop/ComboboxBackdrop.d.ts.map +1 -1
  29. package/lib/combobox/backdrop/ComboboxBackdrop.js +10 -4
  30. package/lib/combobox/backdrop/ComboboxBackdrop.js.map +1 -1
  31. package/lib/combobox/empty/ComboboxEmpty.d.ts.map +1 -1
  32. package/lib/combobox/empty/ComboboxEmpty.js +4 -1
  33. package/lib/combobox/empty/ComboboxEmpty.js.map +1 -1
  34. package/lib/combobox/index.d.ts +2 -0
  35. package/lib/combobox/index.d.ts.map +1 -1
  36. package/lib/combobox/index.js +3 -1
  37. package/lib/combobox/index.js.map +1 -1
  38. package/lib/combobox/index.parts.d.ts +1 -0
  39. package/lib/combobox/index.parts.d.ts.map +1 -1
  40. package/lib/combobox/index.parts.js +4 -1
  41. package/lib/combobox/index.parts.js.map +1 -1
  42. package/lib/combobox/input/ComboboxInput.d.ts +24 -3
  43. package/lib/combobox/input/ComboboxInput.d.ts.map +1 -1
  44. package/lib/combobox/input/ComboboxInput.js +65 -11
  45. package/lib/combobox/input/ComboboxInput.js.map +1 -1
  46. package/lib/combobox/item/ComboboxItem.d.ts +11 -1
  47. package/lib/combobox/item/ComboboxItem.d.ts.map +1 -1
  48. package/lib/combobox/item/ComboboxItem.js +9 -6
  49. package/lib/combobox/item/ComboboxItem.js.map +1 -1
  50. package/lib/combobox/list/ComboboxList.d.ts +1 -1
  51. package/lib/combobox/list/ComboboxList.d.ts.map +1 -1
  52. package/lib/combobox/list/ComboboxList.js +4 -1
  53. package/lib/combobox/list/ComboboxList.js.map +1 -1
  54. package/lib/combobox/popup/ComboboxPopup.d.ts +26 -0
  55. package/lib/combobox/popup/ComboboxPopup.d.ts.map +1 -1
  56. package/lib/combobox/popup/ComboboxPopup.js +18 -2
  57. package/lib/combobox/popup/ComboboxPopup.js.map +1 -1
  58. package/lib/combobox/portal/ComboboxPortal.d.ts.map +1 -1
  59. package/lib/combobox/portal/ComboboxPortal.js +8 -4
  60. package/lib/combobox/portal/ComboboxPortal.js.map +1 -1
  61. package/lib/combobox/portal/ComboboxPortalContext.d.ts +4 -0
  62. package/lib/combobox/portal/ComboboxPortalContext.d.ts.map +1 -0
  63. package/lib/combobox/portal/ComboboxPortalContext.js +48 -0
  64. package/lib/combobox/portal/ComboboxPortalContext.js.map +1 -0
  65. package/lib/combobox/positioner/ComboboxPositioner.d.ts +7 -2
  66. package/lib/combobox/positioner/ComboboxPositioner.d.ts.map +1 -1
  67. package/lib/combobox/positioner/ComboboxPositioner.js +61 -6
  68. package/lib/combobox/positioner/ComboboxPositioner.js.map +1 -1
  69. package/lib/combobox/positioner/ComboboxPositionerContext.d.ts +9 -0
  70. package/lib/combobox/positioner/ComboboxPositionerContext.d.ts.map +1 -0
  71. package/lib/combobox/positioner/ComboboxPositionerContext.js +48 -0
  72. package/lib/combobox/positioner/ComboboxPositionerContext.js.map +1 -0
  73. package/lib/combobox/root/ComboboxItemsContext.d.ts +18 -0
  74. package/lib/combobox/root/ComboboxItemsContext.d.ts.map +1 -0
  75. package/lib/combobox/root/ComboboxItemsContext.js +55 -0
  76. package/lib/combobox/root/ComboboxItemsContext.js.map +1 -0
  77. package/lib/combobox/root/ComboboxRoot.d.ts +77 -21
  78. package/lib/combobox/root/ComboboxRoot.d.ts.map +1 -1
  79. package/lib/combobox/root/ComboboxRoot.js +4 -23
  80. package/lib/combobox/root/ComboboxRoot.js.map +1 -1
  81. package/lib/combobox/root/ComboboxRootContext.d.ts +4 -54
  82. package/lib/combobox/root/ComboboxRootContext.d.ts.map +1 -1
  83. package/lib/combobox/root/ComboboxRootContext.js.map +1 -1
  84. package/lib/combobox/root/ComboboxTransitionContext.d.ts +8 -0
  85. package/lib/combobox/root/ComboboxTransitionContext.d.ts.map +1 -0
  86. package/lib/combobox/root/ComboboxTransitionContext.js +44 -0
  87. package/lib/combobox/root/ComboboxTransitionContext.js.map +1 -0
  88. package/lib/combobox/root/useRenderComboboxRoot.d.ts +39 -0
  89. package/lib/combobox/root/useRenderComboboxRoot.d.ts.map +1 -0
  90. package/lib/combobox/root/useRenderComboboxRoot.js +136 -0
  91. package/lib/combobox/root/useRenderComboboxRoot.js.map +1 -0
  92. package/lib/combobox/store/ComboboxHandle.d.ts +42 -0
  93. package/lib/combobox/store/ComboboxHandle.d.ts.map +1 -0
  94. package/lib/combobox/store/ComboboxHandle.js +65 -0
  95. package/lib/combobox/store/ComboboxHandle.js.map +1 -0
  96. package/lib/combobox/store/ComboboxStore.d.ts +147 -0
  97. package/lib/combobox/store/ComboboxStore.d.ts.map +1 -0
  98. package/lib/combobox/store/ComboboxStore.js +127 -0
  99. package/lib/combobox/store/ComboboxStore.js.map +1 -0
  100. package/lib/combobox/value/ComboboxValue.d.ts.map +1 -1
  101. package/lib/combobox/value/ComboboxValue.js +2 -1
  102. package/lib/combobox/value/ComboboxValue.js.map +1 -1
  103. package/lib/context-menu/positioner/ContextMenuPositioner.d.ts +19 -3
  104. package/lib/context-menu/positioner/ContextMenuPositioner.d.ts.map +1 -1
  105. package/lib/context-menu/positioner/ContextMenuPositioner.js +56 -7
  106. package/lib/context-menu/positioner/ContextMenuPositioner.js.map +1 -1
  107. package/lib/context-menu/root/ContextMenuRoot.d.ts +8 -0
  108. package/lib/context-menu/root/ContextMenuRoot.d.ts.map +1 -1
  109. package/lib/dialog/backdrop/DialogBackdrop.d.ts +9 -0
  110. package/lib/dialog/backdrop/DialogBackdrop.d.ts.map +1 -1
  111. package/lib/dialog/backdrop/DialogBackdrop.js +3 -1
  112. package/lib/dialog/backdrop/DialogBackdrop.js.map +1 -1
  113. package/lib/dialog/popup/DialogPopup.d.ts +9 -0
  114. package/lib/dialog/popup/DialogPopup.d.ts.map +1 -1
  115. package/lib/dialog/popup/DialogPopup.js +2 -2
  116. package/lib/dialog/popup/DialogPopup.js.map +1 -1
  117. package/lib/dialog/popup/useDialogPopupProps.d.ts +2 -0
  118. package/lib/dialog/popup/useDialogPopupProps.d.ts.map +1 -1
  119. package/lib/dialog/popup/useDialogPopupProps.js +4 -0
  120. package/lib/dialog/popup/useDialogPopupProps.js.map +1 -1
  121. package/lib/dialog/root/DialogNestingContext.d.ts +20 -0
  122. package/lib/dialog/root/DialogNestingContext.d.ts.map +1 -0
  123. package/lib/dialog/root/DialogNestingContext.js +53 -0
  124. package/lib/dialog/root/DialogNestingContext.js.map +1 -0
  125. package/lib/dialog/root/useRenderDialogRoot.d.ts.map +1 -1
  126. package/lib/dialog/root/useRenderDialogRoot.js +26 -2
  127. package/lib/dialog/root/useRenderDialogRoot.js.map +1 -1
  128. package/lib/dialog/store/DialogStore.d.ts +19 -2
  129. package/lib/dialog/store/DialogStore.d.ts.map +1 -1
  130. package/lib/dialog/store/DialogStore.js +14 -2
  131. package/lib/dialog/store/DialogStore.js.map +1 -1
  132. package/lib/drawer/index.parts.d.ts +1 -1
  133. package/lib/drawer/index.parts.d.ts.map +1 -1
  134. package/lib/drawer/index.parts.js.map +1 -1
  135. package/lib/drawer/popup/DrawerPopup.d.ts +9 -0
  136. package/lib/drawer/popup/DrawerPopup.d.ts.map +1 -1
  137. package/lib/drawer/popup/DrawerPopup.js +3 -1
  138. package/lib/drawer/popup/DrawerPopup.js.map +1 -1
  139. package/lib/filter/index.d.ts +2 -0
  140. package/lib/filter/index.d.ts.map +1 -0
  141. package/lib/filter/index.js +6 -0
  142. package/lib/filter/index.js.map +1 -0
  143. package/lib/filter/useFilter.d.ts +45 -0
  144. package/lib/filter/useFilter.d.ts.map +1 -0
  145. package/lib/filter/useFilter.js +96 -0
  146. package/lib/filter/useFilter.js.map +1 -0
  147. package/lib/index.d.ts +1 -0
  148. package/lib/index.d.ts.map +1 -1
  149. package/lib/index.js +1 -0
  150. package/lib/index.js.map +1 -1
  151. package/lib/internals/field/useFieldControlRegistration.d.ts +25 -6
  152. package/lib/internals/field/useFieldControlRegistration.d.ts.map +1 -1
  153. package/lib/internals/field/useFieldControlRegistration.js +93 -6
  154. package/lib/internals/field/useFieldControlRegistration.js.map +1 -1
  155. package/lib/menu/popup/MenuPopup.d.ts +10 -0
  156. package/lib/menu/popup/MenuPopup.d.ts.map +1 -1
  157. package/lib/menu/popup/MenuPopup.js +10 -1
  158. package/lib/menu/popup/MenuPopup.js.map +1 -1
  159. package/lib/menu/positioner/MenuPositioner.d.ts +10 -0
  160. package/lib/menu/positioner/MenuPositioner.d.ts.map +1 -1
  161. package/lib/menu/positioner/MenuPositioner.js +9 -1
  162. package/lib/menu/positioner/MenuPositioner.js.map +1 -1
  163. package/lib/menu/store/MenuStore.d.ts +11 -0
  164. package/lib/menu/store/MenuStore.d.ts.map +1 -1
  165. package/lib/menu/store/MenuStore.js +4 -0
  166. package/lib/menu/store/MenuStore.js.map +1 -1
  167. package/lib/menu/submenu-trigger/MenuSubmenuTrigger.d.ts.map +1 -1
  168. package/lib/menu/submenu-trigger/MenuSubmenuTrigger.js +3 -0
  169. package/lib/menu/submenu-trigger/MenuSubmenuTrigger.js.map +1 -1
  170. package/lib/menu/trigger/MenuTrigger.d.ts.map +1 -1
  171. package/lib/menu/trigger/MenuTrigger.js +4 -1
  172. package/lib/menu/trigger/MenuTrigger.js.map +1 -1
  173. package/lib/meter/indicator/MeterIndicator.d.ts +2 -1
  174. package/lib/meter/indicator/MeterIndicator.d.ts.map +1 -1
  175. package/lib/meter/indicator/MeterIndicator.js +1 -2
  176. package/lib/meter/indicator/MeterIndicator.js.map +1 -1
  177. package/lib/meter/label/MeterLabel.d.ts +2 -1
  178. package/lib/meter/label/MeterLabel.d.ts.map +1 -1
  179. package/lib/meter/label/MeterLabel.js +1 -2
  180. package/lib/meter/label/MeterLabel.js.map +1 -1
  181. package/lib/meter/root/MeterRoot.d.ts +21 -0
  182. package/lib/meter/root/MeterRoot.d.ts.map +1 -1
  183. package/lib/meter/root/MeterRoot.js +2 -2
  184. package/lib/meter/root/MeterRoot.js.map +1 -1
  185. package/lib/meter/root/MeterRootContext.d.ts +5 -0
  186. package/lib/meter/root/MeterRootContext.d.ts.map +1 -1
  187. package/lib/meter/root/MeterRootContext.js.map +1 -1
  188. package/lib/meter/track/MeterTrack.d.ts +2 -1
  189. package/lib/meter/track/MeterTrack.d.ts.map +1 -1
  190. package/lib/meter/track/MeterTrack.js +1 -2
  191. package/lib/meter/track/MeterTrack.js.map +1 -1
  192. package/lib/meter/value/MeterValue.d.ts +2 -1
  193. package/lib/meter/value/MeterValue.d.ts.map +1 -1
  194. package/lib/meter/value/MeterValue.js +1 -2
  195. package/lib/meter/value/MeterValue.js.map +1 -1
  196. package/lib/number-field/input/NumberFieldInput.d.ts.map +1 -1
  197. package/lib/number-field/input/NumberFieldInput.js +11 -2
  198. package/lib/number-field/input/NumberFieldInput.js.map +1 -1
  199. package/lib/number-field/root/NumberFieldRoot.d.ts.map +1 -1
  200. package/lib/number-field/root/NumberFieldRoot.js +5 -2
  201. package/lib/number-field/root/NumberFieldRoot.js.map +1 -1
  202. package/lib/otp-field/input/OTPFieldInput.d.ts.map +1 -1
  203. package/lib/otp-field/input/OTPFieldInput.js +4 -0
  204. package/lib/otp-field/input/OTPFieldInput.js.map +1 -1
  205. package/lib/otp-field/root/OTPFieldRoot.d.ts.map +1 -1
  206. package/lib/otp-field/root/OTPFieldRoot.js +20 -2
  207. package/lib/otp-field/root/OTPFieldRoot.js.map +1 -1
  208. package/lib/popover/popup/PopoverPopup.d.ts +10 -0
  209. package/lib/popover/popup/PopoverPopup.d.ts.map +1 -1
  210. package/lib/popover/popup/PopoverPopup.js +10 -1
  211. package/lib/popover/popup/PopoverPopup.js.map +1 -1
  212. package/lib/popover/positioner/PopoverPositioner.d.ts +10 -0
  213. package/lib/popover/positioner/PopoverPositioner.d.ts.map +1 -1
  214. package/lib/popover/positioner/PopoverPositioner.js +4 -0
  215. package/lib/popover/positioner/PopoverPositioner.js.map +1 -1
  216. package/lib/popover/store/PopoverStore.d.ts +11 -0
  217. package/lib/popover/store/PopoverStore.d.ts.map +1 -1
  218. package/lib/popover/store/PopoverStore.js +4 -0
  219. package/lib/popover/store/PopoverStore.js.map +1 -1
  220. package/lib/popover/trigger/PopoverTrigger.d.ts.map +1 -1
  221. package/lib/popover/trigger/PopoverTrigger.js +4 -1
  222. package/lib/popover/trigger/PopoverTrigger.js.map +1 -1
  223. package/lib/progress/root/ProgressRoot.d.ts +21 -0
  224. package/lib/progress/root/ProgressRoot.d.ts.map +1 -1
  225. package/lib/progress/root/ProgressRoot.js +1 -1
  226. package/lib/progress/root/ProgressRoot.js.map +1 -1
  227. package/lib/radio-group/RadioGroup.d.ts.map +1 -1
  228. package/lib/radio-group/RadioGroup.js +5 -2
  229. package/lib/radio-group/RadioGroup.js.map +1 -1
  230. package/lib/select/backdrop/SelectBackdrop.d.ts.map +1 -1
  231. package/lib/select/backdrop/SelectBackdrop.js +3 -0
  232. package/lib/select/backdrop/SelectBackdrop.js.map +1 -1
  233. package/lib/select/index.parts.d.ts +1 -0
  234. package/lib/select/index.parts.d.ts.map +1 -1
  235. package/lib/select/index.parts.js +4 -1
  236. package/lib/select/index.parts.js.map +1 -1
  237. package/lib/select/popup/SelectPopup.d.ts +8 -0
  238. package/lib/select/popup/SelectPopup.d.ts.map +1 -1
  239. package/lib/select/popup/SelectPopup.js +10 -1
  240. package/lib/select/popup/SelectPopup.js.map +1 -1
  241. package/lib/select/positioner/SelectPositioner.d.ts +4 -0
  242. package/lib/select/positioner/SelectPositioner.d.ts.map +1 -1
  243. package/lib/select/positioner/SelectPositioner.js +2 -0
  244. package/lib/select/positioner/SelectPositioner.js.map +1 -1
  245. package/lib/select/root/SelectRoot.d.ts +45 -4
  246. package/lib/select/root/SelectRoot.d.ts.map +1 -1
  247. package/lib/select/root/SelectRoot.js +43 -5
  248. package/lib/select/root/SelectRoot.js.map +1 -1
  249. package/lib/select/store/SelectHandle.d.ts +42 -0
  250. package/lib/select/store/SelectHandle.d.ts.map +1 -0
  251. package/lib/select/store/SelectHandle.js +69 -0
  252. package/lib/select/store/SelectHandle.js.map +1 -0
  253. package/lib/select/store/SelectStore.d.ts +28 -0
  254. package/lib/select/store/SelectStore.d.ts.map +1 -1
  255. package/lib/select/store/SelectStore.js +15 -1
  256. package/lib/select/store/SelectStore.js.map +1 -1
  257. package/lib/select/trigger/SelectTrigger.d.ts +18 -3
  258. package/lib/select/trigger/SelectTrigger.d.ts.map +1 -1
  259. package/lib/select/trigger/SelectTrigger.js +49 -4
  260. package/lib/select/trigger/SelectTrigger.js.map +1 -1
  261. package/lib/slider/root/SliderRoot.d.ts.map +1 -1
  262. package/lib/slider/root/SliderRoot.js +9 -1
  263. package/lib/slider/root/SliderRoot.js.map +1 -1
  264. package/lib/slider/thumb/SliderThumb.d.ts.map +1 -1
  265. package/lib/slider/thumb/SliderThumb.js +10 -2
  266. package/lib/slider/thumb/SliderThumb.js.map +1 -1
  267. package/lib/switch/root/SwitchRoot.d.ts.map +1 -1
  268. package/lib/switch/root/SwitchRoot.js +6 -2
  269. package/lib/switch/root/SwitchRoot.js.map +1 -1
  270. package/lib/tabs/tab/TabsTab.d.ts +2 -10
  271. package/lib/tabs/tab/TabsTab.d.ts.map +1 -1
  272. package/lib/tooltip/popup/TooltipPopup.d.ts +10 -0
  273. package/lib/tooltip/popup/TooltipPopup.d.ts.map +1 -1
  274. package/lib/tooltip/popup/TooltipPopup.js +10 -1
  275. package/lib/tooltip/popup/TooltipPopup.js.map +1 -1
  276. package/lib/tooltip/positioner/TooltipPositioner.d.ts +10 -0
  277. package/lib/tooltip/positioner/TooltipPositioner.d.ts.map +1 -1
  278. package/lib/tooltip/positioner/TooltipPositioner.js +4 -0
  279. package/lib/tooltip/positioner/TooltipPositioner.js.map +1 -1
  280. package/lib/tooltip/store/TooltipStore.d.ts +11 -0
  281. package/lib/tooltip/store/TooltipStore.d.ts.map +1 -1
  282. package/lib/tooltip/store/TooltipStore.js +11 -1
  283. package/lib/tooltip/store/TooltipStore.js.map +1 -1
  284. package/lib/tooltip/trigger/TooltipTrigger.d.ts.map +1 -1
  285. package/lib/tooltip/trigger/TooltipTrigger.js +4 -1
  286. package/lib/tooltip/trigger/TooltipTrigger.js.map +1 -1
  287. package/package.json +1 -1
  288. package/src/alert-dialog/index.parts.ts +1 -1
  289. package/src/autocomplete/handle.ts +18 -0
  290. package/src/autocomplete/index.parts.ts +1 -0
  291. package/src/autocomplete/index.ts +1 -0
  292. package/src/autocomplete/root/AutocompleteRoot.tsx +65 -50
  293. package/src/avatar/fallback/AvatarFallback.tsx +3 -1
  294. package/src/avatar/image/AvatarImage.tsx +5 -0
  295. package/src/checkbox/root/CheckboxRoot.tsx +7 -2
  296. package/src/combobox/backdrop/ComboboxBackdrop.tsx +10 -3
  297. package/src/combobox/empty/ComboboxEmpty.tsx +5 -1
  298. package/src/combobox/index.parts.ts +1 -0
  299. package/src/combobox/index.ts +2 -0
  300. package/src/combobox/input/ComboboxInput.tsx +112 -27
  301. package/src/combobox/item/ComboboxItem.tsx +24 -6
  302. package/src/combobox/list/ComboboxList.tsx +7 -2
  303. package/src/combobox/popup/ComboboxPopup.tsx +44 -2
  304. package/src/combobox/portal/ComboboxPortal.tsx +19 -13
  305. package/src/combobox/portal/ComboboxPortalContext.ts +15 -0
  306. package/src/combobox/positioner/ComboboxPositioner.tsx +45 -10
  307. package/src/combobox/positioner/ComboboxPositionerContext.ts +23 -0
  308. package/src/combobox/root/ComboboxItemsContext.ts +32 -0
  309. package/src/combobox/root/ComboboxRoot.tsx +94 -56
  310. package/src/combobox/root/ComboboxRootContext.ts +3 -54
  311. package/src/combobox/root/ComboboxTransitionContext.ts +15 -0
  312. package/src/combobox/root/useRenderComboboxRoot.tsx +189 -0
  313. package/src/combobox/store/ComboboxHandle.ts +64 -0
  314. package/src/combobox/store/ComboboxStore.ts +249 -0
  315. package/src/combobox/value/ComboboxValue.tsx +2 -1
  316. package/src/context-menu/positioner/ContextMenuPositioner.tsx +90 -10
  317. package/src/context-menu/root/ContextMenuRoot.tsx +8 -0
  318. package/src/dialog/backdrop/DialogBackdrop.tsx +12 -1
  319. package/src/dialog/popup/DialogPopup.tsx +11 -2
  320. package/src/dialog/popup/useDialogPopupProps.ts +4 -0
  321. package/src/dialog/root/DialogNestingContext.ts +27 -0
  322. package/src/dialog/root/useRenderDialogRoot.tsx +37 -2
  323. package/src/dialog/store/DialogStore.ts +24 -2
  324. package/src/drawer/index.parts.ts +1 -1
  325. package/src/drawer/popup/DrawerPopup.tsx +12 -1
  326. package/src/filter/index.ts +5 -0
  327. package/src/filter/useFilter.ts +95 -0
  328. package/src/index.ts +1 -0
  329. package/src/internals/field/useFieldControlRegistration.ts +86 -6
  330. package/src/menu/popup/MenuPopup.tsx +20 -1
  331. package/src/menu/positioner/MenuPositioner.tsx +19 -1
  332. package/src/menu/store/MenuStore.ts +13 -0
  333. package/src/menu/submenu-trigger/MenuSubmenuTrigger.tsx +3 -0
  334. package/src/menu/trigger/MenuTrigger.tsx +5 -2
  335. package/src/meter/indicator/MeterIndicator.tsx +3 -4
  336. package/src/meter/label/MeterLabel.tsx +3 -4
  337. package/src/meter/root/MeterRoot.tsx +29 -4
  338. package/src/meter/root/MeterRootContext.ts +5 -0
  339. package/src/meter/track/MeterTrack.tsx +3 -4
  340. package/src/meter/value/MeterValue.tsx +3 -4
  341. package/src/number-field/input/NumberFieldInput.tsx +11 -2
  342. package/src/number-field/root/NumberFieldRoot.tsx +4 -2
  343. package/src/otp-field/input/OTPFieldInput.tsx +4 -0
  344. package/src/otp-field/root/OTPFieldRoot.tsx +22 -2
  345. package/src/popover/popup/PopoverPopup.tsx +20 -1
  346. package/src/popover/positioner/PopoverPositioner.tsx +14 -0
  347. package/src/popover/store/PopoverStore.ts +13 -0
  348. package/src/popover/trigger/PopoverTrigger.tsx +5 -2
  349. package/src/progress/root/ProgressRoot.tsx +25 -1
  350. package/src/radio-group/RadioGroup.tsx +5 -2
  351. package/src/select/backdrop/SelectBackdrop.tsx +4 -0
  352. package/src/select/index.parts.ts +1 -0
  353. package/src/select/popup/SelectPopup.tsx +18 -1
  354. package/src/select/positioner/SelectPositioner.tsx +9 -3
  355. package/src/select/root/SelectRoot.tsx +109 -9
  356. package/src/select/store/SelectHandle.ts +68 -0
  357. package/src/select/store/SelectStore.ts +38 -1
  358. package/src/select/trigger/SelectTrigger.tsx +78 -7
  359. package/src/slider/root/SliderRoot.tsx +11 -1
  360. package/src/slider/thumb/SliderThumb.tsx +10 -1
  361. package/src/switch/root/SwitchRoot.tsx +5 -2
  362. package/src/tabs/tab/TabsTab.tsx +2 -10
  363. package/src/tooltip/popup/TooltipPopup.tsx +20 -1
  364. package/src/tooltip/positioner/TooltipPositioner.tsx +14 -0
  365. package/src/tooltip/store/TooltipStore.ts +20 -1
  366. package/src/tooltip/trigger/TooltipTrigger.tsx +5 -2
  367. package/lib/combobox/root/useComboboxRoot.d.ts +0 -29
  368. package/lib/combobox/root/useComboboxRoot.d.ts.map +0 -1
  369. package/lib/combobox/root/useComboboxRoot.js +0 -176
  370. package/lib/combobox/root/useComboboxRoot.js.map +0 -1
  371. package/lib/utils/fastObjectShallowCompare.d.ts +0 -2
  372. package/lib/utils/fastObjectShallowCompare.d.ts.map +0 -1
  373. package/lib/utils/fastObjectShallowCompare.js +0 -29
  374. package/lib/utils/fastObjectShallowCompare.js.map +0 -1
  375. package/lib/utils/mergeObjects.d.ts +0 -2
  376. package/lib/utils/mergeObjects.d.ts.map +0 -1
  377. package/lib/utils/mergeObjects.js +0 -16
  378. package/lib/utils/mergeObjects.js.map +0 -1
  379. package/src/combobox/root/useComboboxRoot.ts +0 -199
  380. package/src/utils/fastObjectShallowCompare.ts +0 -30
  381. package/src/utils/mergeObjects.ts +0 -15
@@ -0,0 +1,249 @@
1
+ import type { TextInput } from 'react-native';
2
+ import { createSelector } from '../../store/createSelector';
3
+ import { ReactStore } from '../../store/ReactStore';
4
+ import { PopupTriggerMap } from '../../utils/popups/PopupTriggerMap';
5
+ import type { ComboboxRoot } from '../root/ComboboxRoot';
6
+
7
+ /**
8
+ * The items to filter and choose from: strings, or `{ value, label }` records
9
+ * for non-string values.
10
+ */
11
+ export type ComboboxItems = ReadonlyArray<string | { value: unknown; label: string }>;
12
+
13
+ export interface ComboboxItem {
14
+ value: unknown;
15
+ label: string;
16
+ }
17
+
18
+ /** Normalizes the `items` prop into `{ value, label }` records. */
19
+ export function normalizeComboboxItems(items: ComboboxItems | undefined): ComboboxItem[] {
20
+ if (!items) {
21
+ return [];
22
+ }
23
+
24
+ return items.map((item) =>
25
+ typeof item === 'string' ? { value: item, label: item } : { value: item.value, label: item.label },
26
+ );
27
+ }
28
+
29
+ export type State = {
30
+ /**
31
+ * The uncontrolled open state. Read through the `open` selector, which
32
+ * resolves the controlled prop first.
33
+ */
34
+ open: boolean;
35
+ /**
36
+ * The controlled `open` prop, when provided.
37
+ */
38
+ openProp: boolean | undefined;
39
+ /**
40
+ * The uncontrolled selected value. Read through the `value` selector.
41
+ */
42
+ value: unknown;
43
+ /**
44
+ * The controlled `value` prop, when provided.
45
+ */
46
+ valueProp: unknown;
47
+ /**
48
+ * The uncontrolled input text. Read through the `inputValue` selector.
49
+ */
50
+ inputValue: string;
51
+ /**
52
+ * The controlled `inputValue` prop, when provided.
53
+ */
54
+ inputValueProp: string | undefined;
55
+ /**
56
+ * Every item, normalized.
57
+ */
58
+ items: ComboboxItem[];
59
+ /**
60
+ * `'combobox'` selects a value and shows its label; `'autocomplete'` is free
61
+ * text with suggestions, where the typed string is the value.
62
+ */
63
+ mode: 'combobox' | 'autocomplete';
64
+ disabled: boolean;
65
+ disablePointerDismissal: boolean;
66
+ /**
67
+ * Whether focusing the input opens the list.
68
+ */
69
+ openOnFocus: boolean;
70
+ /**
71
+ * The anchor's native node, carried across the portal boundary.
72
+ */
73
+ triggerNode: unknown;
74
+ /**
75
+ * The input's measured width, used to size the popup.
76
+ */
77
+ triggerWidth: number | undefined;
78
+ /**
79
+ * The input's measured height.
80
+ */
81
+ triggerHeight: number | undefined;
82
+ update: (() => void) | undefined;
83
+ /**
84
+ * Ref to the input element, for programmatic blur.
85
+ */
86
+ inputRef: React.RefObject<TextInput | null> | undefined;
87
+ /**
88
+ * The payload of the trigger the popup was opened by, handed to the root's
89
+ * children when they are a function.
90
+ */
91
+ payload: unknown;
92
+ /**
93
+ * The id of the trigger the popup is associated with, or `null` for none.
94
+ */
95
+ triggerId: string | null;
96
+ /**
97
+ * The controlled `triggerId` prop, when provided.
98
+ */
99
+ triggerIdProp: string | null | undefined;
100
+ };
101
+
102
+ type Context = {
103
+ onOpenChange:
104
+ | ((open: boolean, eventDetails: ComboboxRoot.ChangeEventDetails) => void)
105
+ | undefined;
106
+ onValueChange:
107
+ | ((value: any, eventDetails: ComboboxRoot.ChangeEventDetails) => void)
108
+ | undefined;
109
+ onInputValueChange:
110
+ | ((value: string, eventDetails: ComboboxRoot.ChangeEventDetails) => void)
111
+ | undefined;
112
+ /**
113
+ * Every trigger bound to this root, by id. A handle resolves `open(id)`
114
+ * through this, which is what lets an input rendered outside the root open it.
115
+ */
116
+ triggerNodes: PopupTriggerMap;
117
+ };
118
+
119
+ const selectors = {
120
+ open: createSelector((state: State) => state.openProp ?? state.open),
121
+ value: createSelector((state: State) =>
122
+ state.valueProp !== undefined ? state.valueProp : state.value,
123
+ ),
124
+ inputValue: createSelector((state: State) => state.inputValueProp ?? state.inputValue),
125
+ items: createSelector((state: State) => state.items),
126
+ mode: createSelector((state: State) => state.mode),
127
+ disabled: createSelector((state: State) => state.disabled),
128
+ disablePointerDismissal: createSelector((state: State) => state.disablePointerDismissal),
129
+ openOnFocus: createSelector((state: State) => state.openOnFocus),
130
+ triggerNode: createSelector((state: State) => state.triggerNode),
131
+ triggerWidth: createSelector((state: State) => state.triggerWidth),
132
+ triggerHeight: createSelector((state: State) => state.triggerHeight),
133
+ update: createSelector((state: State) => state.update),
134
+ inputRef: createSelector((state: State) => state.inputRef),
135
+ payload: createSelector((state: State) => state.payload),
136
+ triggerId: createSelector((state: State) => state.triggerIdProp ?? state.triggerId),
137
+ };
138
+
139
+ /**
140
+ * The store behind `Combobox.Root` and `Autocomplete.Root`.
141
+ *
142
+ * It follows the same controlled-prop and cancelable-event contract as the other
143
+ * popup families: every change fires its callback first and only commits the
144
+ * uncontrolled key when the consumer has not called `eventDetails.cancel()`.
145
+ */
146
+ export class ComboboxStore extends ReactStore<Readonly<State>, Context, typeof selectors> {
147
+ constructor(initialState?: Partial<State>) {
148
+ super(
149
+ {
150
+ open: false,
151
+ openProp: undefined,
152
+ value: null,
153
+ valueProp: undefined,
154
+ inputValue: '',
155
+ inputValueProp: undefined,
156
+ items: [],
157
+ mode: 'combobox',
158
+ disabled: false,
159
+ disablePointerDismissal: false,
160
+ openOnFocus: true,
161
+ triggerNode: null,
162
+ triggerWidth: undefined,
163
+ triggerHeight: undefined,
164
+ update: undefined,
165
+ inputRef: undefined,
166
+ payload: undefined,
167
+ triggerId: null,
168
+ triggerIdProp: undefined,
169
+ ...initialState,
170
+ },
171
+ {
172
+ onOpenChange: undefined,
173
+ onValueChange: undefined,
174
+ onInputValueChange: undefined,
175
+ triggerNodes: new PopupTriggerMap(),
176
+ },
177
+ selectors,
178
+ );
179
+ }
180
+
181
+ public setOpen = (nextOpen: boolean, eventDetails: ComboboxRoot.ChangeEventDetails) => {
182
+ if (nextOpen === this.select('open')) {
183
+ return;
184
+ }
185
+
186
+ this.context.onOpenChange?.(nextOpen, eventDetails);
187
+
188
+ if (eventDetails.isCanceled) {
189
+ return;
190
+ }
191
+
192
+ this.set('open', nextOpen);
193
+ };
194
+
195
+ public setValue = (nextValue: unknown, eventDetails: ComboboxRoot.ChangeEventDetails) => {
196
+ this.context.onValueChange?.(nextValue, eventDetails);
197
+
198
+ if (eventDetails.isCanceled) {
199
+ return;
200
+ }
201
+
202
+ this.set('value', nextValue);
203
+ };
204
+
205
+ public setInputValue = (nextValue: string, eventDetails: ComboboxRoot.ChangeEventDetails) => {
206
+ this.context.onInputValueChange?.(nextValue, eventDetails);
207
+
208
+ if (eventDetails.isCanceled) {
209
+ return;
210
+ }
211
+
212
+ this.set('inputValue', nextValue);
213
+ };
214
+
215
+ /**
216
+ * Reflects a value the consumer changed from outside into the input text.
217
+ *
218
+ * This is not a change the consumer made through the combobox, so it fires no
219
+ * callback — it writes the uncontrolled key directly.
220
+ */
221
+ public reflectInputValue = (nextValue: string) => {
222
+ this.set('inputValue', nextValue);
223
+ };
224
+
225
+ /**
226
+ * Selects an item: records the value, fills the input with its label, and
227
+ * closes the popup.
228
+ *
229
+ * One `eventDetails` object is shared by all three, so canceling in any
230
+ * handler stops the rest — the same contract the group components use.
231
+ */
232
+ public selectItem = (item: ComboboxItem, eventDetails: ComboboxRoot.ChangeEventDetails) => {
233
+ if (this.select('mode') === 'combobox') {
234
+ this.setValue(item.value, eventDetails);
235
+
236
+ if (eventDetails.isCanceled) {
237
+ return;
238
+ }
239
+ }
240
+
241
+ this.setInputValue(item.label, eventDetails);
242
+
243
+ if (eventDetails.isCanceled) {
244
+ return;
245
+ }
246
+
247
+ this.setOpen(false, eventDetails);
248
+ };
249
+ }
@@ -13,7 +13,8 @@ import type { ZestUIComponentProps } from '../../types';
13
13
  export function ComboboxValue(componentProps: ComboboxValue.Props) {
14
14
  const { render, className, style, children, ref, ...elementProps } = componentProps;
15
15
 
16
- const { inputValue } = useComboboxRootContext();
16
+ const store = useComboboxRootContext();
17
+ const inputValue = store.useState('inputValue');
17
18
 
18
19
  const state: ComboboxValueState = { value: inputValue };
19
20
 
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import * as React from 'react';
3
- import { View } from 'react-native';
3
+ import { View, useWindowDimensions, type LayoutChangeEvent } from 'react-native';
4
4
  import { useMenuRootContext } from '../../menu/root/MenuRootContext';
5
5
  import { useMenuPortalContext } from '../../menu/portal/MenuPortalContext';
6
6
  import { MenuPositionerContext } from '../../menu/positioner/MenuPositionerContext';
@@ -15,31 +15,97 @@ const NO_ARROW = { current: null };
15
15
  * Positions the context menu popup at the point the long press landed.
16
16
  * Renders a `<View>`.
17
17
  *
18
- * Unlike `Menu.Positioner`, it anchors to a screen point rather than a trigger
19
- * element, so there is no collision-aware floating-ui pass — the popup is placed
20
- * directly at the press coordinates.
18
+ * Unlike `Menu.Positioner` it anchors to a screen point rather than a trigger
19
+ * element, so it does not go through `useAnchorPositioning`: floating-ui
20
+ * measures a real node, and there is no node here. Collision handling is
21
+ * therefore its own — the popup flips to the other side of the press point when
22
+ * it would otherwise run off the screen, and is kept `collisionPadding` inside
23
+ * the edges. `side` and `align` on state report where it actually landed.
21
24
  */
22
25
  export function ContextMenuPositioner(componentProps: ContextMenuPositioner.Props) {
23
- const { render, className, style, ref, ...elementProps } = componentProps;
26
+ const {
27
+ render,
28
+ className,
29
+ collisionPadding = 5,
30
+ style,
31
+ ref,
32
+ ...elementProps
33
+ } = componentProps;
24
34
 
25
35
  useMenuPortalContext();
26
36
  const store = useMenuRootContext();
27
37
  const { anchor } = useContextMenuRootContext();
28
38
 
29
39
  const open = store.useState('open');
40
+ const { width: screenWidth, height: screenHeight } = useWindowDimensions();
41
+
42
+ // The popup's own size, which only exists after it has been laid out. Until
43
+ // then it is placed at the press point unflipped, exactly as before.
44
+ const [size, setSize] = React.useState<{ width: number; height: number } | undefined>(undefined);
45
+
46
+ const onLayout = React.useCallback((event: LayoutChangeEvent) => {
47
+ const { width, height } = event.nativeEvent.layout;
48
+ setSize((previous) =>
49
+ previous?.width === width && previous?.height === height ? previous : { width, height },
50
+ );
51
+ }, []);
52
+
53
+ // A closed menu remeasures when it reopens somewhere else, so stale geometry
54
+ // never decides the first frame of the next open.
55
+ React.useEffect(() => {
56
+ if (!open) {
57
+ setSize(undefined);
58
+ }
59
+ }, [open]);
60
+
61
+ const placement = React.useMemo(() => {
62
+ if (size === undefined) {
63
+ return { left: anchor.x, top: anchor.y, side: 'bottom' as Side, align: 'start' as Align };
64
+ }
30
65
 
31
- const state: ContextMenuPositionerState = { open, side: 'bottom', align: 'start' };
66
+ const flipHorizontally = anchor.x + size.width > screenWidth - collisionPadding;
67
+ const flipVertically = anchor.y + size.height > screenHeight - collisionPadding;
68
+
69
+ const rawLeft = flipHorizontally ? anchor.x - size.width : anchor.x;
70
+ const rawTop = flipVertically ? anchor.y - size.height : anchor.y;
71
+
72
+ // Clamping matters when the popup is larger than the space on either side:
73
+ // flipping alone would just move the overflow to the opposite edge.
74
+ const maxLeft = Math.max(collisionPadding, screenWidth - size.width - collisionPadding);
75
+ const maxTop = Math.max(collisionPadding, screenHeight - size.height - collisionPadding);
76
+
77
+ return {
78
+ left: Math.min(Math.max(rawLeft, collisionPadding), maxLeft),
79
+ top: Math.min(Math.max(rawTop, collisionPadding), maxTop),
80
+ side: (flipVertically ? 'top' : 'bottom') as Side,
81
+ align: (flipHorizontally ? 'end' : 'start') as Align,
82
+ };
83
+ }, [anchor.x, anchor.y, size, screenWidth, screenHeight, collisionPadding]);
84
+
85
+ const state: ContextMenuPositionerState = {
86
+ open,
87
+ side: placement.side,
88
+ align: placement.align,
89
+ };
32
90
 
33
91
  const contextValue: MenuPositionerContext = React.useMemo(
34
- () => ({ side: 'bottom', align: 'start', arrowRef: NO_ARROW, arrowStyles: {} }),
35
- [],
92
+ () => ({
93
+ side: placement.side,
94
+ align: placement.align,
95
+ arrowRef: NO_ARROW,
96
+ arrowStyles: {},
97
+ }),
98
+ [placement.side, placement.align],
36
99
  );
37
100
 
38
101
  const element = useRenderElement(View, componentProps, {
39
102
  state,
40
103
  ref,
41
104
  props: [
42
- { style: { position: 'absolute' as const, left: anchor.x, top: anchor.y } },
105
+ {
106
+ style: { position: 'absolute' as const, left: placement.left, top: placement.top },
107
+ onLayout,
108
+ },
43
109
  elementProps,
44
110
  ],
45
111
  });
@@ -53,12 +119,26 @@ export function ContextMenuPositioner(componentProps: ContextMenuPositioner.Prop
53
119
 
54
120
  export interface ContextMenuPositionerState {
55
121
  open: boolean;
122
+ /**
123
+ * `'bottom'` when the popup hangs below the press point, `'top'` when it was
124
+ * flipped above it to stay on screen.
125
+ */
56
126
  side: Side;
127
+ /**
128
+ * `'start'` when the popup extends right of the press point, `'end'` when it
129
+ * was flipped to its left.
130
+ */
57
131
  align: Align;
58
132
  }
59
133
 
60
134
  export interface ContextMenuPositionerProps
61
- extends ZestUIComponentProps<typeof View, ContextMenuPositionerState> {}
135
+ extends ZestUIComponentProps<typeof View, ContextMenuPositionerState> {
136
+ /**
137
+ * How much space to keep between the popup and the edges of the screen.
138
+ * @default 5
139
+ */
140
+ collisionPadding?: number | undefined;
141
+ }
62
142
 
63
143
  export namespace ContextMenuPositioner {
64
144
  export type State = ContextMenuPositionerState;
@@ -43,4 +43,12 @@ export interface ContextMenuRootProps
43
43
  export namespace ContextMenuRoot {
44
44
  export type State = ContextMenuRootState;
45
45
  export type Props = ContextMenuRootProps;
46
+ /**
47
+ * A context menu is a menu, so it reuses the menu's action and event
48
+ * contracts — a consumer should not have to reach for `Menu.Root`'s types to
49
+ * type a `ContextMenu.Root` handler.
50
+ */
51
+ export type Actions = MenuRoot.Actions;
52
+ export type ChangeEventReason = MenuRoot.ChangeEventReason;
53
+ export type ChangeEventDetails = MenuRoot.ChangeEventDetails;
46
54
  }
@@ -16,8 +16,10 @@ export function DialogBackdrop(componentProps: DialogBackdrop.Props) {
16
16
 
17
17
  const store = useDialogRootContext();
18
18
  const open = store.useState('open');
19
+ const nested = store.useState('nested');
20
+ const nestedDialogOpen = store.useState('nestedDialogOpen');
19
21
 
20
- const state: DialogBackdropState = { open };
22
+ const state: DialogBackdropState = { open, nested, nestedDialogOpen };
21
23
 
22
24
  return useRenderElement(View, componentProps, {
23
25
  state,
@@ -39,6 +41,15 @@ export interface DialogBackdropState {
39
41
  * Whether the dialog is currently open.
40
42
  */
41
43
  open: boolean;
44
+ /**
45
+ * Whether this dialog is rendered inside another dialog's tree.
46
+ */
47
+ nested: boolean;
48
+ /**
49
+ * Whether a dialog nested inside this one is open — a backdrop usually wants
50
+ * to darken further while it is.
51
+ */
52
+ nestedDialogOpen: boolean;
42
53
  }
43
54
 
44
55
  export interface DialogBackdropProps
@@ -12,9 +12,9 @@ import { useDialogPopupProps } from './useDialogPopupProps';
12
12
  export function DialogPopup(componentProps: DialogPopup.Props) {
13
13
  const { render, className, style, ref, ...elementProps } = componentProps;
14
14
 
15
- const { open, transitionStatus, props } = useDialogPopupProps();
15
+ const { open, transitionStatus, nested, nestedDialogOpen, props } = useDialogPopupProps();
16
16
 
17
- const state: DialogPopupState = { open, transitionStatus };
17
+ const state: DialogPopupState = { open, transitionStatus, nested, nestedDialogOpen };
18
18
 
19
19
  return useRenderElement(View, componentProps, {
20
20
  state,
@@ -33,6 +33,15 @@ export interface DialogPopupState {
33
33
  * to `undefined` after one frame), `'ending'` once it is closing.
34
34
  */
35
35
  transitionStatus: TransitionStatus;
36
+ /**
37
+ * Whether this dialog is rendered inside another dialog's tree.
38
+ */
39
+ nested: boolean;
40
+ /**
41
+ * Whether a dialog nested inside this one is open — the cue to scale or dim
42
+ * this popup, which the web version drives with a CSS variable.
43
+ */
44
+ nestedDialogOpen: boolean;
36
45
  }
37
46
 
38
47
  export interface DialogPopupProps extends ZestUIComponentProps<typeof View, DialogPopupState> {}
@@ -16,10 +16,14 @@ export function useDialogPopupProps() {
16
16
  const titleElementId = store.useState('titleElementId');
17
17
  const descriptionElementId = store.useState('descriptionElementId');
18
18
  const role = store.useState('role');
19
+ const nested = store.useState('nested');
20
+ const nestedDialogOpen = store.useState('nestedDialogOpen');
19
21
 
20
22
  return {
21
23
  store,
22
24
  open,
25
+ nested,
26
+ nestedDialogOpen,
23
27
  transitionStatus: transitionContext?.transitionStatus,
24
28
  props: {
25
29
  role,
@@ -0,0 +1,27 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+
4
+ export interface DialogNestingContext {
5
+ /**
6
+ * Called by a nested root as it opens and closes, so the parent can keep a
7
+ * count of how many of its descendants are open.
8
+ */
9
+ onNestedOpenChange: (open: boolean) => void;
10
+ }
11
+
12
+ /**
13
+ * Lets a dialog know it has a dialog above it, and lets a parent know one of its
14
+ * descendants is open.
15
+ *
16
+ * On the web Base UI counts nested dialogs so the one underneath can be scaled
17
+ * back or dimmed. React Native has no CSS to express that, so the count reaches
18
+ * the consumer the way everything else does: as `nested` and `nestedDialogOpen`
19
+ * on the popup and backdrop state.
20
+ */
21
+ export const DialogNestingContext = React.createContext<DialogNestingContext | undefined>(
22
+ undefined,
23
+ );
24
+
25
+ export function useDialogNestingContext() {
26
+ return React.useContext(DialogNestingContext);
27
+ }
@@ -3,8 +3,10 @@ import * as React from 'react';
3
3
  import { useRefWithInit } from '../../hooks/useRefWithInit';
4
4
  import { useTransitionStatus } from '../../internals/useTransitionStatus';
5
5
  import { usePopupRootHandle } from '../../utils/popups/usePopupRootHandle';
6
+ import { useStableCallback } from '../../hooks/useStableCallback';
6
7
  import { DialogStore } from '../store/DialogStore';
7
8
  import type { DialogRootProps } from './DialogRoot';
9
+ import { DialogNestingContext, useDialogNestingContext } from './DialogNestingContext';
8
10
  import { DialogRootContext } from './DialogRootContext';
9
11
  import { DialogTransitionContext } from './DialogTransitionContext';
10
12
 
@@ -36,6 +38,9 @@ export function useRenderDialogRoot<Payload = unknown>(
36
38
  const disablePointerDismissal = isAlertDialog || disablePointerDismissalProp;
37
39
  const role: 'dialog' | 'alertdialog' = isAlertDialog ? 'alertdialog' : 'dialog';
38
40
 
41
+ const parent = useDialogNestingContext();
42
+ const nested = parent !== undefined;
43
+
39
44
  const store = useRefWithInit(
40
45
  () =>
41
46
  new DialogStore({
@@ -45,19 +50,42 @@ export function useRenderDialogRoot<Payload = unknown>(
45
50
  role,
46
51
  triggerId: defaultTriggerId,
47
52
  triggerIdProp: triggerId,
53
+ nested,
48
54
  }),
49
55
  ).current;
50
56
 
51
57
  store.useControlledProp('openProp', open);
52
58
  store.useControlledProp('triggerIdProp', triggerId);
53
59
  store.useContextCallback('onOpenChange', onOpenChange);
54
- store.useSyncedValues({ disablePointerDismissal, role });
60
+ store.useSyncedValues({ disablePointerDismissal, role, nested });
55
61
 
56
62
  usePopupRootHandle({ store, handle, actionsRef });
57
63
 
58
64
  const resolvedOpen = store.useState('open');
59
65
  const { transitionStatus } = useTransitionStatus(resolvedOpen, false, true);
60
66
 
67
+ // Tell the dialog above this one whether it now has an open descendant. The
68
+ // cleanup is what keeps the parent's count right when this root unmounts
69
+ // while still open.
70
+ React.useEffect(() => {
71
+ if (parent === undefined || !resolvedOpen) {
72
+ return undefined;
73
+ }
74
+
75
+ parent.onNestedOpenChange(true);
76
+
77
+ return () => {
78
+ parent.onNestedOpenChange(false);
79
+ };
80
+ }, [parent, resolvedOpen]);
81
+
82
+ const onNestedOpenChange = useStableCallback((open: boolean) => {
83
+ store.setNestedOpen(open);
84
+ // A dialog two levels down is still nested inside this one's parent, so the
85
+ // count propagates all the way up.
86
+ parent?.onNestedOpenChange(open);
87
+ });
88
+
61
89
  const payload = store.useState('payload') as Payload;
62
90
 
63
91
  const transitionContextValue = React.useMemo(
@@ -65,10 +93,17 @@ export function useRenderDialogRoot<Payload = unknown>(
65
93
  [transitionStatus],
66
94
  );
67
95
 
96
+ const nestingContextValue = React.useMemo(
97
+ () => ({ onNestedOpenChange }),
98
+ [onNestedOpenChange],
99
+ );
100
+
68
101
  return (
69
102
  <DialogRootContext.Provider value={store}>
70
103
  <DialogTransitionContext.Provider value={transitionContextValue}>
71
- {typeof children === 'function' ? children(payload) : children}
104
+ <DialogNestingContext.Provider value={nestingContextValue}>
105
+ {typeof children === 'function' ? children(payload) : children}
106
+ </DialogNestingContext.Provider>
72
107
  </DialogTransitionContext.Provider>
73
108
  </DialogRootContext.Provider>
74
109
  );
@@ -35,6 +35,15 @@ export type State = {
35
35
  * The controlled `triggerId` prop, when provided.
36
36
  */
37
37
  triggerIdProp: string | null | undefined;
38
+ /**
39
+ * Whether this dialog is itself rendered inside another dialog's tree.
40
+ */
41
+ nested: boolean;
42
+ /**
43
+ * How many dialogs nested inside this one are currently open. A count rather
44
+ * than a flag because siblings can overlap while one animates out.
45
+ */
46
+ nestedOpenCount: number;
38
47
  };
39
48
 
40
49
  type Context<Reason extends string> = {
@@ -56,6 +65,8 @@ const selectors = {
56
65
  descriptionElementId: createSelector((state: State) => state.descriptionElementId),
57
66
  disablePointerDismissal: createSelector((state: State) => state.disablePointerDismissal),
58
67
  role: createSelector((state: State) => state.role),
68
+ nested: createSelector((state: State) => state.nested),
69
+ nestedDialogOpen: createSelector((state: State) => state.nestedOpenCount > 0),
59
70
  };
60
71
 
61
72
  /**
@@ -67,8 +78,7 @@ const selectors = {
67
78
  * `DialogRootContext` erases it to `any` (the same trick the group components
68
79
  * use) so the parts stay non-generic.
69
80
  *
70
- * TODO(later): nested dialog counting, openMethod interaction typing,
71
- * transitions (`mounted`/`transitionStatus`), handles/payloads.
81
+ * TODO(later): openMethod interaction typing.
72
82
  */
73
83
  export class DialogStore<Reason extends string = DialogRootChangeEventReason> extends ReactStore<
74
84
  Readonly<State>,
@@ -87,6 +97,8 @@ export class DialogStore<Reason extends string = DialogRootChangeEventReason> ex
87
97
  payload: undefined,
88
98
  triggerId: null,
89
99
  triggerIdProp: undefined,
100
+ nested: false,
101
+ nestedOpenCount: 0,
90
102
  ...initialState,
91
103
  },
92
104
  { onOpenChange: undefined, triggerNodes: new PopupTriggerMap() },
@@ -107,4 +119,14 @@ export class DialogStore<Reason extends string = DialogRootChangeEventReason> ex
107
119
 
108
120
  this.set('open', nextOpen);
109
121
  };
122
+
123
+ /**
124
+ * Records that a dialog nested inside this one opened or closed.
125
+ *
126
+ * Increments rather than assigns, so two descendants closing out of order
127
+ * cannot leave the count stuck above zero.
128
+ */
129
+ public setNestedOpen = (open: boolean) => {
130
+ this.set('nestedOpenCount', Math.max(0, this.state.nestedOpenCount + (open ? 1 : -1)));
131
+ };
110
132
  }
@@ -1,5 +1,5 @@
1
1
  export { DrawerRoot as Root } from './root/DrawerRoot';
2
- export { createDrawerHandle as createHandle } from './handle';
2
+ export { createDrawerHandle as createHandle, type DrawerHandle as Handle } from './handle';
3
3
  export { DrawerPopup as Popup } from './popup/DrawerPopup';
4
4
  export { DrawerSwipeArea as SwipeArea } from './swipe-area/DrawerSwipeArea';
5
5