@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
@@ -60,7 +60,7 @@ export function DrawerPopup(componentProps: DrawerPopup.Props) {
60
60
  setActiveSnapPoint,
61
61
  snapToSequentialPoints,
62
62
  } = useDrawerSnapPoints();
63
- const { store, open, transitionStatus, props } = useDialogPopupProps();
63
+ const { store, open, transitionStatus, nested, nestedDialogOpen, props } = useDialogPopupProps();
64
64
 
65
65
  const [swiping, setSwiping] = React.useState(false);
66
66
  const [swipeMovement, setSwipeMovement] = React.useState(0);
@@ -159,6 +159,8 @@ export function DrawerPopup(componentProps: DrawerPopup.Props) {
159
159
  const state: DrawerPopupState = {
160
160
  open,
161
161
  transitionStatus,
162
+ nested,
163
+ nestedDialogOpen,
162
164
  swiping,
163
165
  swipeMovement,
164
166
  swipeDirection,
@@ -199,6 +201,15 @@ export interface DrawerPopupState {
199
201
  * to `undefined` after one frame), `'ending'` once it is closing.
200
202
  */
201
203
  transitionStatus: TransitionStatus;
204
+ /**
205
+ * Whether this drawer is rendered inside another dialog's tree.
206
+ */
207
+ nested: boolean;
208
+ /**
209
+ * Whether a dialog nested inside this one is open — the cue to scale or dim
210
+ * this popup, which the web version drives with a CSS variable.
211
+ */
212
+ nestedDialogOpen: boolean;
202
213
  /**
203
214
  * Whether a swipe is currently in progress.
204
215
  */
@@ -0,0 +1,5 @@
1
+ export {
2
+ useFilter,
3
+ type UseFilterParameters,
4
+ type UseFilterReturnValue,
5
+ } from './useFilter';
@@ -0,0 +1,95 @@
1
+ 'use client';
2
+ import * as React from 'react';
3
+
4
+ export interface UseFilterParameters extends Intl.CollatorOptions {
5
+ /**
6
+ * The locale to compare in, as a BCP 47 language tag. Defaults to the
7
+ * runtime's.
8
+ */
9
+ locale?: string | undefined;
10
+ }
11
+
12
+ export interface UseFilterReturnValue {
13
+ /**
14
+ * Whether `string` starts with `substring`.
15
+ */
16
+ startsWith: (string: string, substring: string) => boolean;
17
+ /**
18
+ * Whether `string` ends with `substring`.
19
+ */
20
+ endsWith: (string: string, substring: string) => boolean;
21
+ /**
22
+ * Whether `string` contains `substring` anywhere.
23
+ */
24
+ contains: (string: string, substring: string) => boolean;
25
+ }
26
+
27
+ /**
28
+ * Locale-aware string matching for filtering a list.
29
+ *
30
+ * `Intl.Collator` is what makes this better than `toLowerCase().includes()`: at
31
+ * the default `'base'` sensitivity, "resume" matches "Résumé" and "PENO" matches
32
+ * "Jalapeño" — which a lowercase comparison never will. Pass
33
+ * `sensitivity: 'case'` (or any other `Intl.CollatorOptions`) to tighten it.
34
+ *
35
+ * All three compare a **fixed-width** window, so a query only matches a run of
36
+ * the same length. Characters that fold to a different number of characters are
37
+ * therefore not matched across that boundary: "straße" (6) does not find
38
+ * "STRASSE" (7), even though the collator considers the two equal. Scanning
39
+ * every width instead would make every keystroke quadratic.
40
+ *
41
+ * ```tsx
42
+ * const { contains } = useFilter({ sensitivity: 'base' });
43
+ * const matches = items.filter((item) => contains(item.label, query));
44
+ * ```
45
+ *
46
+ * This is the default filter behind `Combobox.Root` and `Autocomplete.Root`;
47
+ * use it directly when you filter the items yourself.
48
+ */
49
+ export function useFilter(options?: UseFilterParameters): UseFilterReturnValue {
50
+ const { locale, ...collatorOptions } = options ?? {};
51
+
52
+ // `usage: 'search'` is what makes a collator answer "are these equal for
53
+ // matching purposes" rather than "how do these sort".
54
+ const collator = React.useMemo(
55
+ () =>
56
+ new Intl.Collator(locale, { usage: 'search', sensitivity: 'base', ...collatorOptions }),
57
+ // eslint-disable-next-line react-hooks/exhaustive-deps
58
+ [locale, JSON.stringify(collatorOptions)],
59
+ );
60
+
61
+ return React.useMemo(
62
+ () => ({
63
+ startsWith(string: string, substring: string) {
64
+ if (substring.length === 0) {
65
+ return true;
66
+ }
67
+
68
+ return collator.compare(string.slice(0, substring.length), substring) === 0;
69
+ },
70
+ endsWith(string: string, substring: string) {
71
+ if (substring.length === 0) {
72
+ return true;
73
+ }
74
+
75
+ return collator.compare(string.slice(-substring.length), substring) === 0;
76
+ },
77
+ contains(string: string, substring: string) {
78
+ if (substring.length === 0) {
79
+ return true;
80
+ }
81
+
82
+ // A collator compares whole strings, so a scan is the only way to ask
83
+ // about a substring.
84
+ for (let index = 0; index <= string.length - substring.length; index += 1) {
85
+ if (collator.compare(string.slice(index, index + substring.length), substring) === 0) {
86
+ return true;
87
+ }
88
+ }
89
+
90
+ return false;
91
+ },
92
+ }),
93
+ [collator],
94
+ );
95
+ }
package/src/index.ts CHANGED
@@ -31,6 +31,7 @@ export * from './tooltip';
31
31
  export * from './toggle';
32
32
  export * from './toggle-group';
33
33
 
34
+ export * from './filter';
34
35
  export * from './store';
35
36
  export * from './hooks';
36
37
  export * from './types';
@@ -1,23 +1,59 @@
1
1
  'use client';
2
+ import * as React from 'react';
2
3
  import { useFieldRootContext } from '../../field/root/FieldRootContext';
3
4
  import { useStableCallback } from '../../hooks/useStableCallback';
4
5
 
6
+ /** Whether a value counts as "the user has put something in this control". */
7
+ function defaultIsFilled(value: unknown): boolean {
8
+ if (value == null || value === false || value === '') {
9
+ return false;
10
+ }
11
+
12
+ if (Array.isArray(value)) {
13
+ return value.length > 0;
14
+ }
15
+
16
+ return true;
17
+ }
18
+
19
+ export interface UseFieldControlRegistrationParameters {
20
+ /**
21
+ * The control's value at mount. `dirty` is whether the current value still
22
+ * equals this one.
23
+ */
24
+ initialValue?: unknown;
25
+ /**
26
+ * Whether a value counts as filled. Defaults to "not null, empty, or false".
27
+ */
28
+ isFilled?: ((value: unknown) => boolean) | undefined;
29
+ }
30
+
5
31
  /**
6
- * Wires a non-text form control (Checkbox, Switch, RadioGroup, NumberField) into
7
- * a surrounding `Field.Root`, when there is one.
32
+ * Wires a non-text form control (Checkbox, Switch, RadioGroup, NumberField,
33
+ * Slider, Select, OTPField) into a surrounding `Field.Root`, when there is one.
8
34
  *
9
35
  * A field-aware control:
10
36
  * - is labelled by `Field.Label` and described by `Field.Description`/`Error`
11
37
  * (through `accessibilityLabelledBy`/`accessibilityDescribedBy`);
12
38
  * - inherits the field's (and its fieldset's) `disabled`;
13
- * - runs the field's `validate` when its value changes.
39
+ * - runs the field's `validate` when its value changes;
40
+ * - reports `dirty`/`filled`/`touched`/`focused` back to the field, which is
41
+ * what `Field.Validity` and `Field.Error` branch on.
14
42
  *
15
43
  * Outside a `Field.Root` everything is `undefined`/no-op, so a standalone
16
44
  * control is unaffected.
17
45
  */
18
- export function useFieldControlRegistration() {
46
+ export function useFieldControlRegistration(
47
+ parameters: UseFieldControlRegistrationParameters = {},
48
+ ) {
49
+ const { initialValue, isFilled = defaultIsFilled } = parameters;
50
+
19
51
  const field = useFieldRootContext(false);
20
52
 
53
+ // Only the value the control mounted with decides `dirty`; a later prop change
54
+ // from the consumer is a new baseline they set themselves, not user input.
55
+ const initialValueRef = React.useRef(initialValue);
56
+
21
57
  const validate = useStableCallback((value: unknown) => {
22
58
  if (!field) {
23
59
  return;
@@ -26,6 +62,44 @@ export function useFieldControlRegistration() {
26
62
  field.setValidityData({ valid: errors.length === 0, errors });
27
63
  });
28
64
 
65
+ /**
66
+ * Records a user-driven value change: the field becomes dirty (unless the
67
+ * value is back to where it started), filled tracks the new value, and
68
+ * `onChange` validation runs.
69
+ */
70
+ const markChanged = useStableCallback((value: unknown) => {
71
+ if (!field) {
72
+ return;
73
+ }
74
+
75
+ field.setDirty(!Object.is(value, initialValueRef.current));
76
+ field.setFilled(isFilled(value));
77
+
78
+ if (field.validationMode === 'onChange') {
79
+ validate(value);
80
+ }
81
+ });
82
+
83
+ /**
84
+ * Records that the user is done with the control — the counterpart of a blur
85
+ * on a text input, which is when `onBlur` validation runs.
86
+ */
87
+ const markTouched = useStableCallback((value: unknown) => {
88
+ if (!field) {
89
+ return;
90
+ }
91
+
92
+ field.setTouched(true);
93
+
94
+ if (field.validationMode === 'onBlur') {
95
+ validate(value);
96
+ }
97
+ });
98
+
99
+ const markFocused = useStableCallback((focused: boolean) => {
100
+ field?.setFocused(focused);
101
+ });
102
+
29
103
  const describedBy =
30
104
  field && field.messageIds.length > 0 ? field.messageIds.join(' ') : undefined;
31
105
 
@@ -36,18 +110,24 @@ export function useFieldControlRegistration() {
36
110
  fieldDisabled: field?.disabled ?? false,
37
111
  /**
38
112
  * Accessibility props associating the control with the field's label and
39
- * messages. Spread into the control's props before `elementProps`.
113
+ * messages, and reporting its validity. Spread into the control's props
114
+ * before `elementProps`.
40
115
  */
41
116
  fieldProps: {
42
117
  accessibilityLabelledBy: field?.labelId,
43
118
  accessibilityDescribedBy: describedBy,
44
119
  'aria-labelledby': field?.labelId,
45
120
  'aria-describedby': describedBy,
121
+ 'aria-invalid': field?.validityData.valid === false || undefined,
46
122
  },
47
123
  /**
48
- * Runs the field's validation for a new control value. No-op without a field.
124
+ * Runs the field's validation for a new control value, regardless of
125
+ * `validationMode`. No-op without a field.
49
126
  */
50
127
  validateField: validate,
128
+ markChanged,
129
+ markTouched,
130
+ markFocused,
51
131
  /**
52
132
  * Whether a surrounding field is present at all.
53
133
  */
@@ -21,8 +21,17 @@ export function MenuPopup(componentProps: MenuPopup.Props) {
21
21
  const { transitionStatus } = useMenuTransitionContext() ?? { transitionStatus: undefined };
22
22
 
23
23
  const open = store.useState('open');
24
+ const triggerWidth = store.useState('triggerWidth');
25
+ const triggerHeight = store.useState('triggerHeight');
24
26
 
25
- const state: MenuPopupState = { open, transitionStatus, side, align };
27
+ const state: MenuPopupState = {
28
+ open,
29
+ transitionStatus,
30
+ side,
31
+ align,
32
+ triggerWidth,
33
+ triggerHeight,
34
+ };
26
35
 
27
36
  const element = useRenderElement(View, componentProps, {
28
37
  state,
@@ -60,6 +69,16 @@ export interface MenuPopupState {
60
69
  * The alignment the popup was actually placed with.
61
70
  */
62
71
  align: Align;
72
+ /**
73
+ * The trigger's measured width, available for consumers to size the popup
74
+ * against its anchor. This is the React Native equivalent of the web's
75
+ * `--anchor-width` CSS variable.
76
+ */
77
+ triggerWidth: number | undefined;
78
+ /**
79
+ * The trigger's measured height.
80
+ */
81
+ triggerHeight: number | undefined;
63
82
  }
64
83
 
65
84
  export interface MenuPopupProps extends ZestUIComponentProps<typeof View, MenuPopupState> {}
@@ -38,6 +38,8 @@ export function MenuPositioner(componentProps: MenuPositioner.Props) {
38
38
  useMenuPortalContext();
39
39
  const store = useMenuRootContext();
40
40
  const open = store.useState('open');
41
+ const triggerWidth = store.useState('triggerWidth');
42
+ const triggerHeight = store.useState('triggerHeight');
41
43
  const triggerNode = store.useState('triggerNode');
42
44
 
43
45
  const positioning = useAnchorPositioning({
@@ -65,7 +67,13 @@ export function MenuPositioner(componentProps: MenuPositioner.Props) {
65
67
 
66
68
  const mergedRef = useMergedRefs(ref, refs.setFloating);
67
69
 
68
- const state: MenuPositionerState = { open, side: positioning.side, align: positioning.align };
70
+ const state: MenuPositionerState = {
71
+ open,
72
+ side: positioning.side,
73
+ align: positioning.align,
74
+ triggerWidth,
75
+ triggerHeight,
76
+ };
69
77
 
70
78
  const contextValue: MenuPositionerContext = React.useMemo(
71
79
  () => ({ side: positioning.side, align: positioning.align, arrowRef, arrowStyles }),
@@ -95,6 +103,16 @@ export interface MenuPositionerState {
95
103
  open: boolean;
96
104
  side: Side;
97
105
  align: Align;
106
+ /**
107
+ * The trigger's measured width, available for consumers to size the popup
108
+ * against its anchor. This is the React Native equivalent of the web's
109
+ * `--anchor-width` CSS variable.
110
+ */
111
+ triggerWidth: number | undefined;
112
+ /**
113
+ * The trigger's measured height.
114
+ */
115
+ triggerHeight: number | undefined;
98
116
  }
99
117
 
100
118
  export interface MenuPositionerProps
@@ -11,6 +11,15 @@ export type State = {
11
11
  * The anchor's native node, carried across the portal boundary.
12
12
  */
13
13
  triggerNode: unknown;
14
+ /**
15
+ * The trigger's measured width, the React Native counterpart of the web's
16
+ * `--anchor-width` CSS variable.
17
+ */
18
+ triggerWidth: number | undefined;
19
+ /**
20
+ * The trigger's measured height.
21
+ */
22
+ triggerHeight: number | undefined;
14
23
  update: (() => void) | undefined;
15
24
  /**
16
25
  * The payload of the trigger the popup was opened by, handed to the root's
@@ -40,6 +49,8 @@ const selectors = {
40
49
  open: createSelector((state: State) => state.openProp ?? state.open),
41
50
  disablePointerDismissal: createSelector((state: State) => state.disablePointerDismissal),
42
51
  triggerNode: createSelector((state: State) => state.triggerNode),
52
+ triggerWidth: createSelector((state: State) => state.triggerWidth),
53
+ triggerHeight: createSelector((state: State) => state.triggerHeight),
43
54
  update: createSelector((state: State) => state.update),
44
55
  payload: createSelector((state: State) => state.payload),
45
56
  triggerId: createSelector((state: State) => state.triggerIdProp ?? state.triggerId),
@@ -53,6 +64,8 @@ export class MenuStore extends ReactStore<Readonly<State>, Context, typeof selec
53
64
  openProp: undefined,
54
65
  disablePointerDismissal: false,
55
66
  triggerNode: null,
67
+ triggerWidth: undefined,
68
+ triggerHeight: undefined,
56
69
  update: undefined,
57
70
  payload: undefined,
58
71
  triggerId: null,
@@ -62,6 +62,9 @@ export function MenuSubmenuTrigger(componentProps: MenuSubmenuTrigger.Props) {
62
62
  {
63
63
  onLayout(event: LayoutChangeEvent) {
64
64
  onLayout(event);
65
+ const { width, height } = event.nativeEvent.layout;
66
+ store.set('triggerWidth', width);
67
+ store.set('triggerHeight', height);
65
68
  store.state.update?.();
66
69
  },
67
70
  accessibilityRole: 'menuitem' as const,
@@ -1,6 +1,6 @@
1
1
  'use client';
2
2
  import * as React from 'react';
3
- import { Pressable, type GestureResponderEvent } from 'react-native';
3
+ import { Pressable, type GestureResponderEvent, type LayoutChangeEvent } from 'react-native';
4
4
  import { MenuRootContext } from '../root/MenuRootContext';
5
5
  import { useRenderElement } from '../../use-render/useRenderElement';
6
6
  import { useButton } from '../../internals/use-button/useButton';
@@ -98,7 +98,10 @@ export function MenuTrigger<Payload = unknown>(componentProps: MenuTrigger.Props
98
98
  onPressOut() {
99
99
  setPressed(false);
100
100
  },
101
- onLayout() {
101
+ onLayout(event: LayoutChangeEvent) {
102
+ const { width, height } = event.nativeEvent.layout;
103
+ store.set('triggerWidth', width);
104
+ store.set('triggerHeight', height);
102
105
  store.state.update?.();
103
106
  },
104
107
  accessibilityState: { expanded: open, disabled: disabled || undefined },
@@ -2,6 +2,7 @@
2
2
  import { View } from 'react-native';
3
3
  import { useMeterRootContext } from '../root/MeterRootContext';
4
4
  import { useRenderElement } from '../../use-render/useRenderElement';
5
+ import type { MeterRootState } from '../root/MeterRoot';
5
6
  import type { ZestUIComponentProps } from '../../types';
6
7
 
7
8
  /**
@@ -11,9 +12,7 @@ import type { ZestUIComponentProps } from '../../types';
11
12
  export function MeterIndicator(componentProps: MeterIndicator.Props) {
12
13
  const { render, className, style, ref, ...elementProps } = componentProps;
13
14
 
14
- const { percentageValue } = useMeterRootContext();
15
-
16
- const state: MeterIndicatorState = {};
15
+ const { percentageValue, state } = useMeterRootContext();
17
16
 
18
17
  return useRenderElement(View, componentProps, {
19
18
  state,
@@ -22,7 +21,7 @@ export function MeterIndicator(componentProps: MeterIndicator.Props) {
22
21
  });
23
22
  }
24
23
 
25
- export interface MeterIndicatorState {}
24
+ export interface MeterIndicatorState extends MeterRootState {}
26
25
 
27
26
  export interface MeterIndicatorProps
28
27
  extends ZestUIComponentProps<typeof View, MeterIndicatorState> {}
@@ -4,6 +4,7 @@ import { useMeterRootContext } from '../root/MeterRootContext';
4
4
  import { useRenderElement } from '../../use-render/useRenderElement';
5
5
  import { useId } from '../../hooks/useId';
6
6
  import { useIsoLayoutEffect } from '../../hooks/useIsoLayoutEffect';
7
+ import type { MeterRootState } from '../root/MeterRoot';
7
8
  import type { ZestUIComponentProps } from '../../types';
8
9
 
9
10
  /**
@@ -13,7 +14,7 @@ import type { ZestUIComponentProps } from '../../types';
13
14
  export function MeterLabel(componentProps: MeterLabel.Props) {
14
15
  const { render, className, style, nativeID: idProp, ref, ...elementProps } = componentProps;
15
16
 
16
- const { setLabelId } = useMeterRootContext();
17
+ const { setLabelId, state } = useMeterRootContext();
17
18
 
18
19
  const id = useId(idProp ?? undefined);
19
20
 
@@ -22,8 +23,6 @@ export function MeterLabel(componentProps: MeterLabel.Props) {
22
23
  return () => setLabelId(undefined);
23
24
  }, [id, setLabelId]);
24
25
 
25
- const state: MeterLabelState = {};
26
-
27
26
  return useRenderElement(Text, componentProps, {
28
27
  state,
29
28
  ref,
@@ -31,7 +30,7 @@ export function MeterLabel(componentProps: MeterLabel.Props) {
31
30
  });
32
31
  }
33
32
 
34
- export interface MeterLabelState {}
33
+ export interface MeterLabelState extends MeterRootState {}
35
34
 
36
35
  export interface MeterLabelProps extends ZestUIComponentProps<typeof Text, MeterLabelState> {}
37
36
 
@@ -45,11 +45,14 @@ export function MeterRoot(componentProps: MeterRoot.Props) {
45
45
  ? formatNumber(value, locale, format)
46
46
  : formatNumber(percentageValue / 100, locale, { style: 'percent' });
47
47
 
48
- const state: MeterRootState = React.useMemo(() => ({}), []);
48
+ const state: MeterRootState = React.useMemo(
49
+ () => ({ value: clampedValue, min, max, percent: percentageValue, formattedValue }),
50
+ [clampedValue, min, max, percentageValue, formattedValue],
51
+ );
49
52
 
50
53
  const contextValue: MeterRootContext = React.useMemo(
51
- () => ({ formattedValue, percentageValue, setLabelId, value }),
52
- [formattedValue, percentageValue, value],
54
+ () => ({ formattedValue, percentageValue, setLabelId, state, value }),
55
+ [formattedValue, percentageValue, state, value],
53
56
  );
54
57
 
55
58
  const element = useRenderElement(View, componentProps, {
@@ -83,7 +86,29 @@ function valueToPercent(value: number, min: number, max: number) {
83
86
  return ((value - min) * 100) / (max - min);
84
87
  }
85
88
 
86
- export interface MeterRootState {}
89
+ export interface MeterRootState {
90
+ /**
91
+ * The current value, clamped into `[min, max]`.
92
+ */
93
+ value: number;
94
+ /**
95
+ * The minimum value.
96
+ */
97
+ min: number;
98
+ /**
99
+ * The maximum value.
100
+ */
101
+ max: number;
102
+ /**
103
+ * How far the value sits through the range, `0`–`100`. This is what an
104
+ * indicator's width comes from.
105
+ */
106
+ percent: number;
107
+ /**
108
+ * The value as text, through `format`/`locale`.
109
+ */
110
+ formattedValue: string;
111
+ }
87
112
 
88
113
  export interface MeterRootProps extends ZestUIComponentProps<typeof View, MeterRootState> {
89
114
  /**
@@ -1,7 +1,12 @@
1
1
  'use client';
2
2
  import * as React from 'react';
3
+ import type { MeterRootState } from './MeterRoot';
3
4
 
4
5
  export interface MeterRootContext {
6
+ /**
7
+ * The state every part publishes to its style and render functions.
8
+ */
9
+ state: MeterRootState;
5
10
  /**
6
11
  * The current value.
7
12
  */
@@ -2,6 +2,7 @@
2
2
  import { View } from 'react-native';
3
3
  import { useMeterRootContext } from '../root/MeterRootContext';
4
4
  import { useRenderElement } from '../../use-render/useRenderElement';
5
+ import type { MeterRootState } from '../root/MeterRoot';
5
6
  import type { ZestUIComponentProps } from '../../types';
6
7
 
7
8
  /**
@@ -11,9 +12,7 @@ import type { ZestUIComponentProps } from '../../types';
11
12
  export function MeterTrack(componentProps: MeterTrack.Props) {
12
13
  const { render, className, style, ref, ...elementProps } = componentProps;
13
14
 
14
- useMeterRootContext();
15
-
16
- const state: MeterTrackState = {};
15
+ const { state } = useMeterRootContext();
17
16
 
18
17
  return useRenderElement(View, componentProps, {
19
18
  state,
@@ -22,7 +21,7 @@ export function MeterTrack(componentProps: MeterTrack.Props) {
22
21
  });
23
22
  }
24
23
 
25
- export interface MeterTrackState {}
24
+ export interface MeterTrackState extends MeterRootState {}
26
25
 
27
26
  export interface MeterTrackProps extends ZestUIComponentProps<typeof View, MeterTrackState> {}
28
27
 
@@ -3,6 +3,7 @@ import type * as React from 'react';
3
3
  import { Text } from 'react-native';
4
4
  import { useMeterRootContext } from '../root/MeterRootContext';
5
5
  import { useRenderElement } from '../../use-render/useRenderElement';
6
+ import type { MeterRootState } from '../root/MeterRoot';
6
7
  import type { ZestUIComponentProps } from '../../types';
7
8
 
8
9
  /**
@@ -15,9 +16,7 @@ import type { ZestUIComponentProps } from '../../types';
15
16
  export function MeterValue(componentProps: MeterValue.Props) {
16
17
  const { render, className, style, children, ref, ...elementProps } = componentProps;
17
18
 
18
- const { value, formattedValue } = useMeterRootContext();
19
-
20
- const state: MeterValueState = {};
19
+ const { value, formattedValue, state } = useMeterRootContext();
21
20
 
22
21
  return useRenderElement(Text, componentProps, {
23
22
  state,
@@ -35,7 +34,7 @@ export function MeterValue(componentProps: MeterValue.Props) {
35
34
  });
36
35
  }
37
36
 
38
- export interface MeterValueState {}
37
+ export interface MeterValueState extends MeterRootState {}
39
38
 
40
39
  export interface MeterValueProps
41
40
  extends Omit<ZestUIComponentProps<typeof Text, MeterValueState>, 'children'> {
@@ -43,7 +43,7 @@ export function NumberFieldInput(componentProps: NumberFieldInput.Props) {
43
43
 
44
44
  const { disabled, readOnly, value, inputValue } = state;
45
45
 
46
- const { fieldProps } = useFieldControlRegistration();
46
+ const { fieldProps, markTouched, markFocused } = useFieldControlRegistration();
47
47
 
48
48
  const handleChangeText = useStableCallback((text: string) => {
49
49
  if (disabled || readOnly) {
@@ -147,7 +147,16 @@ export function NumberFieldInput(componentProps: NumberFieldInput.Props) {
147
147
  nativeID: id,
148
148
  value: inputValue,
149
149
  onChangeText: handleChangeText,
150
- onBlur: handleBlur,
150
+ onFocus() {
151
+ markFocused(true);
152
+ },
153
+ onBlur(event: NativeSyntheticEvent<TextInputFocusEventData>) {
154
+ markFocused(false);
155
+ // Leaving the input is what ends the interaction, the same way it does
156
+ // for a text control — so this is where `onBlur` validation runs.
157
+ markTouched(valueRef.current);
158
+ handleBlur(event);
159
+ },
151
160
  editable: !disabled && !readOnly,
152
161
  // `numeric` rather than `decimal-pad`: a locale's format may include a
153
162
  // decimal separator, a minus sign or a currency symbol, and the numeric
@@ -52,7 +52,9 @@ export function NumberFieldRoot(componentProps: NumberFieldRoot.Props) {
52
52
  ...elementProps
53
53
  } = componentProps;
54
54
 
55
- const { fieldDisabled, validateField } = useFieldControlRegistration();
55
+ const { fieldDisabled, markChanged } = useFieldControlRegistration({
56
+ initialValue: defaultValue ?? valueProp ?? null,
57
+ });
56
58
 
57
59
  const disabled = disabledProp || fieldDisabled;
58
60
 
@@ -135,7 +137,7 @@ export function NumberFieldRoot(componentProps: NumberFieldRoot.Props) {
135
137
 
136
138
  valueRef.current = validatedValue;
137
139
  setValueState(validatedValue);
138
- validateField(validatedValue);
140
+ markChanged(validatedValue);
139
141
  }
140
142
 
141
143
  lastChangedValueRef.current = validatedValue;
@@ -163,6 +163,10 @@ export function OTPFieldInput(componentProps: OTPFieldInput.Props) {
163
163
  autoComplete: (index === 0 ? autoComplete : 'off') as 'one-time-code' | 'off',
164
164
  textContentType: index === 0 ? ('oneTimeCode' as const) : ('none' as const),
165
165
  enterKeyHint: index === length - 1 ? ('done' as const) : ('next' as const),
166
+ // Every slot is an identical one-character box, so position is the only
167
+ // thing that tells them apart out loud. A consumer-supplied
168
+ // `accessibilityLabel` in `elementProps` still wins.
169
+ accessibilityLabel: `Digit ${index + 1} of ${length}`,
166
170
  accessibilityState: { disabled: disabled || undefined },
167
171
  },
168
172
  elementProps,