@ui-sdk/web 0.0.0-stage → 0.1.0-beta.1

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 (467) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +10 -2
  3. package/browser/web/accordion.js +1 -0
  4. package/browser/web/alert-dialog.js +1 -0
  5. package/browser/web/autocomplete.js +1 -0
  6. package/browser/web/avatar.js +1 -0
  7. package/browser/web/button.js +1 -0
  8. package/browser/web/calendar.js +1 -0
  9. package/browser/web/carousel.js +1 -0
  10. package/browser/web/checkbox.js +1 -0
  11. package/browser/web/chunks/0eRGibL6.js +1 -0
  12. package/browser/web/chunks/2bIEUO6_.js +1 -0
  13. package/browser/web/chunks/5A9w2ULz.js +1 -0
  14. package/browser/web/chunks/5RSyydjV.js +1 -0
  15. package/browser/web/chunks/9pWTy2wl.js +1 -0
  16. package/browser/web/chunks/B0f2j6zz.js +1 -0
  17. package/browser/web/chunks/B1rux6br.js +1 -0
  18. package/browser/web/chunks/B3-APaNf.js +1 -0
  19. package/browser/web/chunks/B7EgSgjQ.js +1 -0
  20. package/browser/web/chunks/B8QcOzJU.js +1 -0
  21. package/browser/web/chunks/BFueEkwm.js +3 -0
  22. package/browser/web/chunks/BJj3oS29.js +1 -0
  23. package/browser/web/chunks/BNSvTCpr.js +1 -0
  24. package/browser/web/chunks/BPD0kAZB.js +1 -0
  25. package/browser/web/chunks/BQeduZqT.js +1 -0
  26. package/browser/web/chunks/B_CVFyiz.js +1 -0
  27. package/browser/web/chunks/B_ns1YHO.js +1 -0
  28. package/browser/web/chunks/BcwkVTsR.js +1 -0
  29. package/browser/web/chunks/BeaQ6TYM.js +1 -0
  30. package/browser/web/chunks/BgBB013u.js +1 -0
  31. package/browser/web/chunks/BgsKhHDm.js +1 -0
  32. package/browser/web/chunks/BjMcbuhi.js +9 -0
  33. package/browser/web/chunks/Bjiy_F2N.js +1 -0
  34. package/browser/web/chunks/BqScCLwv.js +1 -0
  35. package/browser/web/chunks/BqX3Utqx.js +1 -0
  36. package/browser/web/chunks/BtVkQf3z.js +1 -0
  37. package/browser/web/chunks/By30h-7B.js +1 -0
  38. package/browser/web/chunks/C1zqYpy2.js +1 -0
  39. package/browser/web/chunks/C436juFf.js +1 -0
  40. package/browser/web/chunks/C4_bULs1.js +1 -0
  41. package/browser/web/chunks/C4omqJEz.js +1 -0
  42. package/browser/web/chunks/C7tt0T3n.js +1 -0
  43. package/browser/web/chunks/C8w7s5dg.js +1 -0
  44. package/browser/web/chunks/CElY4erO.js +1 -0
  45. package/browser/web/chunks/CFODo-m2.js +1 -0
  46. package/browser/web/chunks/CL4wjam9.js +1 -0
  47. package/browser/web/chunks/CMg-8UVf.js +1 -0
  48. package/browser/web/chunks/COWKfakj.js +1 -0
  49. package/browser/web/chunks/CQBU0qZ4.js +1 -0
  50. package/browser/web/chunks/CV6uABgc.js +1 -0
  51. package/browser/web/chunks/C_SWFpWQ.js +1 -0
  52. package/browser/web/chunks/C_Uanbrl.js +1 -0
  53. package/browser/web/chunks/Ce_CSanC.js +1 -0
  54. package/browser/web/chunks/Cg-ZmRhS.js +1 -0
  55. package/browser/web/chunks/Cgf2wPI_.js +1 -0
  56. package/browser/web/chunks/Cuep_3Vf.js +1 -0
  57. package/browser/web/chunks/D02PaHn6.js +1 -0
  58. package/browser/web/chunks/D5LiaI5R.js +1 -0
  59. package/browser/web/chunks/D9UgVYvF.js +1 -0
  60. package/browser/web/chunks/DC08tnkE.js +2 -0
  61. package/browser/web/chunks/DG8YGSvu.js +1 -0
  62. package/browser/web/chunks/DIQd0FYD.js +1 -0
  63. package/browser/web/chunks/DMLqacE8.js +1 -0
  64. package/browser/web/chunks/DSUI3yI-.js +1 -0
  65. package/browser/web/chunks/DT3Hb76X.js +1 -0
  66. package/browser/web/chunks/DY75g3-W.js +1 -0
  67. package/browser/web/chunks/DZpNQKZ9.js +1 -0
  68. package/browser/web/chunks/Db-W1wrC.js +1 -0
  69. package/browser/web/chunks/Dc1xK3Ii.js +1 -0
  70. package/browser/web/chunks/DdF6awFs.js +1 -0
  71. package/browser/web/chunks/DdaCLml8.js +1 -0
  72. package/browser/web/chunks/Dh8MLjQO.js +1 -0
  73. package/browser/web/chunks/DiuslJRy.js +1 -0
  74. package/browser/web/chunks/DjRcw3Y0.js +1 -0
  75. package/browser/web/chunks/Djdv-16J.js +1 -0
  76. package/browser/web/chunks/DnFXe_lF.js +1 -0
  77. package/browser/web/chunks/DneGv1oC.js +1 -0
  78. package/browser/web/chunks/DsVxP-p-.js +1 -0
  79. package/browser/web/chunks/DwDilCo2.js +1 -0
  80. package/browser/web/chunks/DwV48AXv.js +1 -0
  81. package/browser/web/chunks/F37aF4-L.js +1 -0
  82. package/browser/web/chunks/LyB6XIbN.js +1 -0
  83. package/browser/web/chunks/NKnbYGjC.js +1 -0
  84. package/browser/web/chunks/Vk-rVi16.js +1 -0
  85. package/browser/web/chunks/XsM0J1Ex.js +1 -0
  86. package/browser/web/chunks/asSN9iEp.js +1 -0
  87. package/browser/web/chunks/c-AjHfdF.js +1 -0
  88. package/browser/web/chunks/dR-12TPY.js +1 -0
  89. package/browser/web/chunks/hR4xM0EC.js +1 -0
  90. package/browser/web/chunks/hqOaBxAg.js +1 -0
  91. package/browser/web/chunks/kMKqqeG4.js +1 -0
  92. package/browser/web/chunks/kXboBcfi.js +1 -0
  93. package/browser/web/chunks/l4aQIg22.js +1 -0
  94. package/browser/web/chunks/tJC0JSU4.js +1 -0
  95. package/browser/web/chunks/wHdS_QYM.js +1 -0
  96. package/browser/web/chunks/yv8dTvNI.js +1 -0
  97. package/browser/web/chunks/yw9M3nfi.js +1 -0
  98. package/browser/web/collapsible.js +1 -0
  99. package/browser/web/color-picker.js +1 -0
  100. package/browser/web/combobox.js +2 -0
  101. package/browser/web/command.js +1 -0
  102. package/browser/web/core.js +5 -0
  103. package/browser/web/date-input.js +1 -0
  104. package/browser/web/date-picker.js +1 -0
  105. package/browser/web/dialog.js +1 -0
  106. package/browser/web/drawer.js +1 -0
  107. package/browser/web/field.js +1 -0
  108. package/browser/web/file-input.js +1 -0
  109. package/browser/web/hover-card.js +1 -0
  110. package/browser/web/image-cropper.js +1 -0
  111. package/browser/web/input.js +1 -0
  112. package/browser/web/label.js +1 -0
  113. package/browser/web/listbox.js +1 -0
  114. package/browser/web/menu.js +1 -0
  115. package/browser/web/menubar.js +1 -0
  116. package/browser/web/meter.js +1 -0
  117. package/browser/web/navigation-menu.js +1 -0
  118. package/browser/web/number-input.js +1 -0
  119. package/browser/web/otp-input.js +2 -0
  120. package/browser/web/overlays.js +1 -0
  121. package/browser/web/pagination.js +1 -0
  122. package/browser/web/password-input.js +1 -0
  123. package/browser/web/popover.js +1 -0
  124. package/browser/web/preferences.js +1 -0
  125. package/browser/web/progress.js +1 -0
  126. package/browser/web/radio.js +1 -0
  127. package/browser/web/rating.js +1 -0
  128. package/browser/web/resizable.js +1 -0
  129. package/browser/web/scroll-area.js +1 -0
  130. package/browser/web/segmented-control.js +1 -0
  131. package/browser/web/select.js +1 -0
  132. package/browser/web/separator.js +1 -0
  133. package/browser/web/slider.js +1 -0
  134. package/browser/web/switch.js +1 -0
  135. package/browser/web/tabs.js +1 -0
  136. package/browser/web/textarea.js +1 -0
  137. package/browser/web/time-input.js +1 -0
  138. package/browser/web/timeline.js +1 -0
  139. package/browser/web/toast.js +1 -0
  140. package/browser/web/toggle.js +1 -0
  141. package/browser/web/token-input.js +8 -0
  142. package/browser/web/toolbar.js +1 -0
  143. package/browser/web/tooltip.js +1 -0
  144. package/browser/web/tree.js +1 -0
  145. package/browser/web/trigger-menu.js +4 -0
  146. package/browser/web/waterfall.js +1 -0
  147. package/browser/web.css +3378 -0
  148. package/browser/web.js +3 -0
  149. package/custom-elements.json +32315 -0
  150. package/dist/accordion.d.ts +2 -0
  151. package/dist/accordion.js +57 -0
  152. package/dist/action.d.ts +16 -0
  153. package/dist/action.js +39 -0
  154. package/dist/agent.d.ts +2 -0
  155. package/dist/agent.js +7 -0
  156. package/dist/autocomplete.d.ts +2 -0
  157. package/dist/autocomplete.js +120 -0
  158. package/dist/autoload.d.ts +18 -0
  159. package/dist/autoload.js +63 -0
  160. package/dist/avatar.d.ts +2 -0
  161. package/dist/avatar.js +27 -0
  162. package/dist/browser-core.d.ts +2 -0
  163. package/dist/browser-core.js +8 -0
  164. package/dist/button.d.ts +2 -0
  165. package/dist/button.js +35 -0
  166. package/dist/calendar.d.ts +23 -0
  167. package/dist/calendar.js +429 -0
  168. package/dist/carousel.d.ts +2 -0
  169. package/dist/carousel.js +54 -0
  170. package/dist/checkbox.d.ts +4 -0
  171. package/dist/checkbox.js +75 -0
  172. package/dist/collapsible.d.ts +2 -0
  173. package/dist/collapsible.js +28 -0
  174. package/dist/color-picker.d.ts +2 -0
  175. package/dist/color-picker.js +64 -0
  176. package/dist/combobox.d.ts +2 -0
  177. package/dist/combobox.js +334 -0
  178. package/dist/command.d.ts +2 -0
  179. package/dist/command.js +122 -0
  180. package/dist/date-input.d.ts +3 -0
  181. package/dist/date-input.js +87 -0
  182. package/dist/date-picker.d.ts +2 -0
  183. package/dist/date-picker.js +174 -0
  184. package/dist/default-text.d.ts +3 -0
  185. package/dist/default-text.js +24 -0
  186. package/dist/define-element.d.ts +404 -0
  187. package/dist/define-element.js +1489 -0
  188. package/dist/dialog.d.ts +3 -0
  189. package/dist/dialog.js +94 -0
  190. package/dist/drawer.d.ts +2 -0
  191. package/dist/drawer.js +96 -0
  192. package/dist/entries/accordion.d.ts +3 -0
  193. package/dist/entries/accordion.js +7 -0
  194. package/dist/entries/alert-dialog.d.ts +3 -0
  195. package/dist/entries/alert-dialog.js +13 -0
  196. package/dist/entries/autocomplete.d.ts +3 -0
  197. package/dist/entries/autocomplete.js +7 -0
  198. package/dist/entries/avatar.d.ts +2 -0
  199. package/dist/entries/avatar.js +3 -0
  200. package/dist/entries/button.d.ts +2 -0
  201. package/dist/entries/button.js +3 -0
  202. package/dist/entries/calendar.d.ts +3 -0
  203. package/dist/entries/calendar.js +7 -0
  204. package/dist/entries/carousel.d.ts +3 -0
  205. package/dist/entries/carousel.js +7 -0
  206. package/dist/entries/checkbox.d.ts +3 -0
  207. package/dist/entries/checkbox.js +15 -0
  208. package/dist/entries/collapsible.d.ts +3 -0
  209. package/dist/entries/collapsible.js +7 -0
  210. package/dist/entries/color-picker.d.ts +3 -0
  211. package/dist/entries/color-picker.js +7 -0
  212. package/dist/entries/combobox.d.ts +3 -0
  213. package/dist/entries/combobox.js +7 -0
  214. package/dist/entries/command.d.ts +3 -0
  215. package/dist/entries/command.js +7 -0
  216. package/dist/entries/date-input.d.ts +3 -0
  217. package/dist/entries/date-input.js +7 -0
  218. package/dist/entries/date-picker.d.ts +3 -0
  219. package/dist/entries/date-picker.js +7 -0
  220. package/dist/entries/dialog.d.ts +3 -0
  221. package/dist/entries/dialog.js +7 -0
  222. package/dist/entries/drawer.d.ts +3 -0
  223. package/dist/entries/drawer.js +8 -0
  224. package/dist/entries/field.d.ts +2 -0
  225. package/dist/entries/field.js +3 -0
  226. package/dist/entries/file-input.d.ts +3 -0
  227. package/dist/entries/file-input.js +13 -0
  228. package/dist/entries/hover-card.d.ts +3 -0
  229. package/dist/entries/hover-card.js +7 -0
  230. package/dist/entries/image-cropper.d.ts +3 -0
  231. package/dist/entries/image-cropper.js +7 -0
  232. package/dist/entries/input.d.ts +3 -0
  233. package/dist/entries/input.js +8 -0
  234. package/dist/entries/label.d.ts +2 -0
  235. package/dist/entries/label.js +3 -0
  236. package/dist/entries/listbox.d.ts +3 -0
  237. package/dist/entries/listbox.js +7 -0
  238. package/dist/entries/menu.d.ts +3 -0
  239. package/dist/entries/menu.js +7 -0
  240. package/dist/entries/menubar.d.ts +3 -0
  241. package/dist/entries/menubar.js +7 -0
  242. package/dist/entries/meter.d.ts +2 -0
  243. package/dist/entries/meter.js +3 -0
  244. package/dist/entries/navigation-menu.d.ts +3 -0
  245. package/dist/entries/navigation-menu.js +7 -0
  246. package/dist/entries/number-input.d.ts +3 -0
  247. package/dist/entries/number-input.js +7 -0
  248. package/dist/entries/otp-input.d.ts +3 -0
  249. package/dist/entries/otp-input.js +7 -0
  250. package/dist/entries/overlays.d.ts +2 -0
  251. package/dist/entries/overlays.js +3 -0
  252. package/dist/entries/pagination.d.ts +3 -0
  253. package/dist/entries/pagination.js +11 -0
  254. package/dist/entries/password-input.d.ts +3 -0
  255. package/dist/entries/password-input.js +7 -0
  256. package/dist/entries/popover.d.ts +3 -0
  257. package/dist/entries/popover.js +7 -0
  258. package/dist/entries/preferences.d.ts +3 -0
  259. package/dist/entries/preferences.js +13 -0
  260. package/dist/entries/progress.d.ts +2 -0
  261. package/dist/entries/progress.js +3 -0
  262. package/dist/entries/radio.d.ts +3 -0
  263. package/dist/entries/radio.js +7 -0
  264. package/dist/entries/rating.d.ts +3 -0
  265. package/dist/entries/rating.js +8 -0
  266. package/dist/entries/resizable.d.ts +3 -0
  267. package/dist/entries/resizable.js +11 -0
  268. package/dist/entries/scroll-area.d.ts +3 -0
  269. package/dist/entries/scroll-area.js +7 -0
  270. package/dist/entries/segmented-control.d.ts +3 -0
  271. package/dist/entries/segmented-control.js +7 -0
  272. package/dist/entries/select.d.ts +3 -0
  273. package/dist/entries/select.js +7 -0
  274. package/dist/entries/separator.d.ts +2 -0
  275. package/dist/entries/separator.js +3 -0
  276. package/dist/entries/slider.d.ts +3 -0
  277. package/dist/entries/slider.js +7 -0
  278. package/dist/entries/switch.d.ts +3 -0
  279. package/dist/entries/switch.js +7 -0
  280. package/dist/entries/tabs.d.ts +3 -0
  281. package/dist/entries/tabs.js +7 -0
  282. package/dist/entries/textarea.d.ts +3 -0
  283. package/dist/entries/textarea.js +7 -0
  284. package/dist/entries/time-input.d.ts +3 -0
  285. package/dist/entries/time-input.js +7 -0
  286. package/dist/entries/timeline.d.ts +3 -0
  287. package/dist/entries/timeline.js +7 -0
  288. package/dist/entries/toast.d.ts +2 -0
  289. package/dist/entries/toast.js +3 -0
  290. package/dist/entries/toggle.d.ts +3 -0
  291. package/dist/entries/toggle.js +12 -0
  292. package/dist/entries/token-input.d.ts +3 -0
  293. package/dist/entries/token-input.js +17 -0
  294. package/dist/entries/toolbar.d.ts +3 -0
  295. package/dist/entries/toolbar.js +7 -0
  296. package/dist/entries/tooltip.d.ts +3 -0
  297. package/dist/entries/tooltip.js +7 -0
  298. package/dist/entries/tree.d.ts +3 -0
  299. package/dist/entries/tree.js +17 -0
  300. package/dist/entries/trigger-menu.d.ts +3 -0
  301. package/dist/entries/trigger-menu.js +11 -0
  302. package/dist/entries/waterfall.d.ts +3 -0
  303. package/dist/entries/waterfall.js +11 -0
  304. package/dist/events.d.ts +1658 -0
  305. package/dist/events.js +0 -0
  306. package/dist/field.d.ts +2 -0
  307. package/dist/field.js +101 -0
  308. package/dist/file-input.d.ts +2 -0
  309. package/dist/file-input.js +140 -0
  310. package/dist/forward-attributes.d.ts +2 -0
  311. package/dist/forward-attributes.js +22 -0
  312. package/dist/history.d.ts +2 -0
  313. package/dist/history.js +11 -0
  314. package/dist/hover-card.d.ts +2 -0
  315. package/dist/hover-card.js +31 -0
  316. package/dist/image-cropper.d.ts +2 -0
  317. package/dist/image-cropper.js +66 -0
  318. package/dist/index.d.ts +66 -0
  319. package/dist/index.js +269 -0
  320. package/dist/input.d.ts +3 -0
  321. package/dist/input.js +69 -0
  322. package/dist/label.d.ts +2 -0
  323. package/dist/label.js +11 -0
  324. package/dist/list.d.ts +40 -0
  325. package/dist/list.js +192 -0
  326. package/dist/listbox.d.ts +2 -0
  327. package/dist/listbox.js +84 -0
  328. package/dist/menu.d.ts +4 -0
  329. package/dist/menu.js +295 -0
  330. package/dist/menubar.d.ts +4 -0
  331. package/dist/menubar.js +30 -0
  332. package/dist/meter.d.ts +2 -0
  333. package/dist/meter.js +22 -0
  334. package/dist/navigation-menu.d.ts +2 -0
  335. package/dist/navigation-menu.js +114 -0
  336. package/dist/number-input.d.ts +2 -0
  337. package/dist/number-input.js +73 -0
  338. package/dist/observe.d.ts +2 -0
  339. package/dist/observe.js +7 -0
  340. package/dist/otp-input.d.ts +2 -0
  341. package/dist/otp-input.js +72 -0
  342. package/dist/overlays.d.ts +13 -0
  343. package/dist/overlays.js +156 -0
  344. package/dist/pagination.d.ts +2 -0
  345. package/dist/pagination.js +127 -0
  346. package/dist/password-input.d.ts +2 -0
  347. package/dist/password-input.js +57 -0
  348. package/dist/popover.d.ts +2 -0
  349. package/dist/popover.js +64 -0
  350. package/dist/popup.d.ts +40 -0
  351. package/dist/popup.js +112 -0
  352. package/dist/preferences.d.ts +2 -0
  353. package/dist/preferences.js +56 -0
  354. package/dist/progress.d.ts +2 -0
  355. package/dist/progress.js +22 -0
  356. package/dist/radio.d.ts +2 -0
  357. package/dist/radio.js +33 -0
  358. package/dist/rating.d.ts +2 -0
  359. package/dist/rating.js +64 -0
  360. package/dist/resizable.d.ts +2 -0
  361. package/dist/resizable.js +42 -0
  362. package/dist/scroll-area.d.ts +2 -0
  363. package/dist/scroll-area.js +45 -0
  364. package/dist/segmented-control.d.ts +2 -0
  365. package/dist/segmented-control.js +28 -0
  366. package/dist/select.d.ts +2 -0
  367. package/dist/select.js +239 -0
  368. package/dist/separator.d.ts +2 -0
  369. package/dist/separator.js +11 -0
  370. package/dist/slider.d.ts +2 -0
  371. package/dist/slider.js +77 -0
  372. package/dist/tabs.d.ts +2 -0
  373. package/dist/tabs.js +37 -0
  374. package/dist/timeline.d.ts +2 -0
  375. package/dist/timeline.js +27 -0
  376. package/dist/toast.d.ts +20 -0
  377. package/dist/toast.js +177 -0
  378. package/dist/toggle.d.ts +3 -0
  379. package/dist/toggle.js +64 -0
  380. package/dist/token-input.d.ts +2 -0
  381. package/dist/token-input.js +103 -0
  382. package/dist/toolbar.d.ts +2 -0
  383. package/dist/toolbar.js +36 -0
  384. package/dist/tooltip.d.ts +2 -0
  385. package/dist/tooltip.js +35 -0
  386. package/dist/tree.d.ts +2 -0
  387. package/dist/tree.js +147 -0
  388. package/dist/trigger-menu.d.ts +2 -0
  389. package/dist/trigger-menu.js +133 -0
  390. package/dist/waterfall.d.ts +2 -0
  391. package/dist/waterfall.js +140 -0
  392. package/docs/README.md +89 -0
  393. package/docs/agents.md +1523 -0
  394. package/docs/components/accordion.md +1401 -0
  395. package/docs/components/alert-dialog.md +788 -0
  396. package/docs/components/autocomplete.md +1692 -0
  397. package/docs/components/avatar.md +261 -0
  398. package/docs/components/button.md +441 -0
  399. package/docs/components/calendar.md +1536 -0
  400. package/docs/components/carousel.md +1055 -0
  401. package/docs/components/checkbox.md +1045 -0
  402. package/docs/components/collapsible.md +535 -0
  403. package/docs/components/color-picker.md +651 -0
  404. package/docs/components/combobox.md +3274 -0
  405. package/docs/components/command.md +1431 -0
  406. package/docs/components/date-input.md +563 -0
  407. package/docs/components/date-picker.md +1136 -0
  408. package/docs/components/dialog.md +1360 -0
  409. package/docs/components/drawer.md +1201 -0
  410. package/docs/components/field.md +519 -0
  411. package/docs/components/file-input.md +617 -0
  412. package/docs/components/hover-card.md +486 -0
  413. package/docs/components/image-cropper.md +489 -0
  414. package/docs/components/input.md +327 -0
  415. package/docs/components/label.md +208 -0
  416. package/docs/components/listbox.md +1015 -0
  417. package/docs/components/menu.md +1212 -0
  418. package/docs/components/menubar.md +460 -0
  419. package/docs/components/meter.md +298 -0
  420. package/docs/components/navigation-menu.md +1573 -0
  421. package/docs/components/number-input.md +1087 -0
  422. package/docs/components/otp-input.md +558 -0
  423. package/docs/components/pagination.md +639 -0
  424. package/docs/components/password-input.md +649 -0
  425. package/docs/components/popover.md +1433 -0
  426. package/docs/components/preferences.md +243 -0
  427. package/docs/components/progress.md +406 -0
  428. package/docs/components/radio.md +855 -0
  429. package/docs/components/rating.md +431 -0
  430. package/docs/components/resizable.md +866 -0
  431. package/docs/components/scroll-area.md +898 -0
  432. package/docs/components/segmented-control.md +421 -0
  433. package/docs/components/select.md +1957 -0
  434. package/docs/components/separator.md +238 -0
  435. package/docs/components/slider.md +1018 -0
  436. package/docs/components/switch.md +583 -0
  437. package/docs/components/tabs.md +948 -0
  438. package/docs/components/textarea.md +331 -0
  439. package/docs/components/time-input.md +235 -0
  440. package/docs/components/timeline.md +622 -0
  441. package/docs/components/toast.md +1223 -0
  442. package/docs/components/toggle.md +522 -0
  443. package/docs/components/token-input.md +854 -0
  444. package/docs/components/toolbar.md +815 -0
  445. package/docs/components/tooltip.md +860 -0
  446. package/docs/components/tree.md +1243 -0
  447. package/docs/components/trigger-menu.md +1110 -0
  448. package/docs/components/waterfall.md +709 -0
  449. package/docs/controllers.md +66 -0
  450. package/docs/core.md +8027 -0
  451. package/docs/custom-parts.md +91 -0
  452. package/docs/first-component.md +88 -0
  453. package/docs/forms.md +125 -0
  454. package/docs/installation.md +141 -0
  455. package/docs/introduction.md +32 -0
  456. package/docs/lists-and-menus.md +166 -0
  457. package/docs/llms.txt +1566 -0
  458. package/docs/parts-and-composition.md +57 -0
  459. package/docs/popups.md +92 -0
  460. package/docs/rendering.md +71 -0
  461. package/docs/server-rendering.md +118 -0
  462. package/docs/state-and-events.md +134 -0
  463. package/docs/styling-and-animation.md +104 -0
  464. package/docs/utilities/async-options.md +332 -0
  465. package/docs/utilities/shortcuts.md +184 -0
  466. package/html-custom-data.json +14792 -0
  467. package/package.json +302 -3
@@ -0,0 +1,1087 @@
1
+ # Number Input
2
+
3
+ Enter numbers, step through values, and format them for a locale.
4
+
5
+ Use a Number Input for quantities, prices and measurements. It keeps a numeric value while showing formatted text, and steps it with the keyboard, the step buttons, or a drag on its label.
6
+
7
+ ```ts title="basic.ts"
8
+ import "@ui-sdk/web/number-input"
9
+ import "@ui-sdk/web/field"
10
+ import { createElement, Minus, MoveHorizontal, Plus } from "lucide"
11
+ import { log } from "./log"
12
+
13
+ function mount(root: HTMLElement) {
14
+ root.innerHTML = `
15
+ <ui-field>
16
+ <ui-number-input default-value="1" min="1">
17
+ <ui-number-input-scrub-area pixels-per-step="4">
18
+ <ui-field-label>Quantity</ui-field-label>
19
+ <ui-number-input-scrub-area-cursor>
20
+ ${createElement(MoveHorizontal, { "aria-hidden": "true" }).outerHTML}
21
+ </ui-number-input-scrub-area-cursor>
22
+ </ui-number-input-scrub-area>
23
+ <ui-number-input-group>
24
+ <ui-number-input-decrement>
25
+ <button aria-label="Decrease">
26
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
27
+ </button>
28
+ </ui-number-input-decrement>
29
+ <ui-number-input-control>
30
+ <input />
31
+ </ui-number-input-control>
32
+ <ui-number-input-increment>
33
+ <button aria-label="Increase">
34
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
35
+ </button>
36
+ </ui-number-input-increment>
37
+ </ui-number-input-group>
38
+ </ui-number-input>
39
+ </ui-field>
40
+ `
41
+ root
42
+ .querySelector("ui-number-input")!
43
+ .addEventListener("valuecommit", (event) => {
44
+ log(root, `Quantity ${event.detail.value ?? "empty"}`)
45
+ })
46
+ }
47
+
48
+ export { mount }
49
+ ```
50
+
51
+ Drag the Quantity label sideways to scrub the value, or click it to focus the input. Type a number, press ArrowUp or ArrowDown, or hold a step button. The console logs the value once each change is committed.
52
+
53
+ ## Installation
54
+
55
+ ```bash
56
+ npm install @ui-sdk/web @ui-sdk/core
57
+ ```
58
+
59
+ Importing the component defines its elements. Import the field too if you use it. On the server the import does nothing.
60
+
61
+ ```ts
62
+ import "@ui-sdk/web/number-input"
63
+ import "@ui-sdk/web/field"
64
+ ```
65
+
66
+ ## Composition
67
+
68
+ Use the following composition to build a `ui-number-input`:
69
+
70
+ ```text
71
+ ui-number-input
72
+ ├── ui-number-input-scrub-area
73
+ │ └── ui-number-input-scrub-area-cursor
74
+ └── ui-number-input-group
75
+ ├── ui-number-input-decrement
76
+ │ └── button
77
+ ├── ui-number-input-control
78
+ │ └── input
79
+ └── ui-number-input-increment
80
+ └── button
81
+ ```
82
+
83
+ `ui-number-input` holds the numeric value. `ui-number-input-group` wraps the text input and the optional step buttons. `ui-number-input-control` wraps your own `<input>`, and `ui-number-input-decrement` and `ui-number-input-increment` wrap your own `<button>`s, which change the value and repeat while held. Put input styles and accessible names on those native elements. `ui-number-input-scrub-area` is optional and changes the value as you drag it.
84
+
85
+ Typing updates the value when the text is valid and within bounds. Enter or blur commits it: the text is parsed, clamped, snapped to the step, and formatted. An empty input has the value `null`.
86
+
87
+ ## Usage
88
+
89
+ Import the parts and put them together:
90
+
91
+ ```html
92
+ <script type="module">
93
+ import "@ui-sdk/web/number-input"
94
+ </script>
95
+
96
+ <ui-number-input default-value="1" min="0">
97
+ <ui-number-input-group>
98
+ <ui-number-input-decrement
99
+ ><button aria-label="Decrease">−</button></ui-number-input-decrement
100
+ >
101
+ <ui-number-input-control
102
+ ><input aria-label="Quantity"
103
+ /></ui-number-input-control>
104
+ <ui-number-input-increment
105
+ ><button aria-label="Increase">+</button></ui-number-input-increment
106
+ >
107
+ </ui-number-input-group>
108
+ </ui-number-input>
109
+ ```
110
+
111
+ ## Field
112
+
113
+ Wrap the number input in [Field](./field.md) to give it a visible label and a description.
114
+
115
+ ```ts title="field.ts"
116
+ import "@ui-sdk/web/number-input"
117
+ import "@ui-sdk/web/field"
118
+ import { createElement, Minus, Plus } from "lucide"
119
+
120
+ function mount(root: HTMLElement) {
121
+ root.innerHTML = `
122
+ <ui-field>
123
+ <ui-field-label>Tickets</ui-field-label>
124
+ <ui-number-input default-value="2" min="1">
125
+ <ui-number-input-group>
126
+ <ui-number-input-decrement>
127
+ <button aria-label="Decrease">
128
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
129
+ </button>
130
+ </ui-number-input-decrement>
131
+ <ui-number-input-control>
132
+ <input />
133
+ </ui-number-input-control>
134
+ <ui-number-input-increment>
135
+ <button aria-label="Increase">
136
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
137
+ </button>
138
+ </ui-number-input-increment>
139
+ </ui-number-input-group>
140
+ </ui-number-input>
141
+ <ui-field-description>Each ticket admits one person to the workshop.</ui-field-description>
142
+ </ui-field>
143
+ `
144
+ }
145
+
146
+ export { mount }
147
+ ```
148
+
149
+ Click Tickets to focus the input, then type or step its value. Screen readers read the description with the field.
150
+
151
+ ## Min and Max
152
+
153
+ Use `min` and `max` to set bounds. A step button disables at its bound. Typed values outside the bounds are clamped when committed.
154
+
155
+ ```ts title="min-max.ts"
156
+ import "@ui-sdk/web/number-input"
157
+ import "@ui-sdk/web/field"
158
+ import { createElement, Minus, Plus } from "lucide"
159
+
160
+ function mount(root: HTMLElement) {
161
+ root.innerHTML = `
162
+ <ui-field>
163
+ <ui-field-label>Guests</ui-field-label>
164
+ <ui-number-input default-value="2" min="1" max="8">
165
+ <ui-number-input-group>
166
+ <ui-number-input-decrement>
167
+ <button aria-label="Decrease">
168
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
169
+ </button>
170
+ </ui-number-input-decrement>
171
+ <ui-number-input-control>
172
+ <input />
173
+ </ui-number-input-control>
174
+ <ui-number-input-increment>
175
+ <button aria-label="Increase">
176
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
177
+ </button>
178
+ </ui-number-input-increment>
179
+ </ui-number-input-group>
180
+ </ui-number-input>
181
+ <ui-field-description>Tables seat between 1 and 8 guests.</ui-field-description>
182
+ </ui-field>
183
+ `
184
+ }
185
+
186
+ export { mount }
187
+ ```
188
+
189
+ Change Guests between 1 and 8. Type 20 and press Enter to clamp it to 8. At either bound, that direction's step button is disabled.
190
+
191
+ ## Step and Large Step
192
+
193
+ `step` sets the usual increment, and defaults to 1. Steps are measured from `min`, or from zero when no minimum is set. When they don't land on `max`, End and the increment button stop at the highest step below it, so stepping keeps the value on the step grid, as [Slider](./slider.md#step-and-bounds) does. Shift, Page Up and Page Down use `large-step`, ten steps by default, and Alt (Option) with an arrow key uses `small-step`, a tenth of a step. [Keyboard](#keyboard) lists every key.
194
+
195
+ ```ts title="step.ts"
196
+ import "@ui-sdk/web/number-input"
197
+ import "@ui-sdk/web/field"
198
+ import { createElement, Minus, Plus } from "lucide"
199
+
200
+ function mount(root: HTMLElement) {
201
+ root.innerHTML = `
202
+ <ui-field>
203
+ <ui-field-label>Hours</ui-field-label>
204
+ <ui-number-input default-value="1.5" min="0" max="24" step="0.25" large-step="1" small-step="0.05">
205
+ <ui-number-input-group>
206
+ <ui-number-input-decrement>
207
+ <button aria-label="Decrease">
208
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
209
+ </button>
210
+ </ui-number-input-decrement>
211
+ <ui-number-input-control>
212
+ <input />
213
+ </ui-number-input-control>
214
+ <ui-number-input-increment>
215
+ <button aria-label="Increase">
216
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
217
+ </button>
218
+ </ui-number-input-increment>
219
+ </ui-number-input-group>
220
+ </ui-number-input>
221
+ <ui-field-description>Logged in quarter hours.</ui-field-description>
222
+ </ui-field>
223
+ `
224
+ }
225
+
226
+ export { mount }
227
+ ```
228
+
229
+ Step Hours by 0.25. Hold Shift to move by 1, or hold Alt (Option) with an arrow key to move by 0.05. Home and End go to 0 and 24.
230
+
231
+ ## Format
232
+
233
+ Pass `Intl.NumberFormat` options through `format`. The input displays and parses that format while keeping a numeric value. For percentages, `0.15` is displayed as `15%`, and `step` defaults to the smallest percent the format shows: `0.01` for whole percents, `0.0001` with two fraction digits. Set `format` as a property on the element; it is an object.
234
+
235
+ ```ts title="format.ts"
236
+ import "@ui-sdk/web/number-input"
237
+ import "@ui-sdk/web/field"
238
+ import { createElement, Minus, Plus } from "lucide"
239
+
240
+ const fields = [
241
+ {
242
+ label: "Price",
243
+ defaultValue: 19.99,
244
+ step: 0.01,
245
+ format: { style: "currency", currency: "USD" },
246
+ },
247
+ {
248
+ label: "Discount",
249
+ defaultValue: 0.15,
250
+ step: 0.01,
251
+ format: { style: "percent" },
252
+ },
253
+ {
254
+ label: "Weight",
255
+ defaultValue: 2.5,
256
+ step: 0.1,
257
+ format: { style: "unit", unit: "kilogram" },
258
+ },
259
+ ] satisfies {
260
+ label: string
261
+ defaultValue: number
262
+ step: number
263
+ format: Intl.NumberFormatOptions
264
+ }[]
265
+
266
+ function mount(root: HTMLElement) {
267
+ root.innerHTML = `
268
+ <div data-slot="stack">
269
+ ${fields
270
+ .map(
271
+ (field) => `
272
+ <ui-field>
273
+ <ui-field-label>${field.label}</ui-field-label>
274
+ <ui-number-input default-value="${field.defaultValue}" step="${field.step}" min="0">
275
+ <ui-number-input-group>
276
+ <ui-number-input-decrement>
277
+ <button aria-label="Decrease">
278
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
279
+ </button>
280
+ </ui-number-input-decrement>
281
+ <ui-number-input-control>
282
+ <input />
283
+ </ui-number-input-control>
284
+ <ui-number-input-increment>
285
+ <button aria-label="Increase">
286
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
287
+ </button>
288
+ </ui-number-input-increment>
289
+ </ui-number-input-group>
290
+ </ui-number-input>
291
+ </ui-field>`
292
+ )
293
+ .join("")}
294
+ </div>
295
+ `
296
+ root
297
+ .querySelectorAll<HTMLElement & { format: Intl.NumberFormatOptions }>(
298
+ "ui-number-input"
299
+ )
300
+ .forEach((el, index) => {
301
+ el.format = fields[index].format
302
+ })
303
+ }
304
+
305
+ export { mount }
306
+ ```
307
+
308
+ Edit Price, Discount, or Weight and leave the field to format it. Try entering 5 in Price or 25% in Discount, then use the step buttons.
309
+
310
+ ## Locale
311
+
312
+ Set `locale` to choose separators, currency symbols, and grouping. Without it, the input follows the page's language or the browser's language after mounting. Server markup uses English when no locale is provided; set a locale explicitly when that first render matters.
313
+
314
+ ```ts title="locale.ts"
315
+ import "@ui-sdk/web/number-input"
316
+ import "@ui-sdk/web/field"
317
+ import { createElement, Minus, Plus } from "lucide"
318
+
319
+ const fields = [
320
+ {
321
+ label: "Preis",
322
+ locale: "de-DE",
323
+ defaultValue: 1234.5,
324
+ step: 0.01,
325
+ format: { style: "currency", currency: "EUR" },
326
+ },
327
+ {
328
+ label: "Budget",
329
+ locale: "en-IN",
330
+ defaultValue: 1234567,
331
+ step: 1000,
332
+ format: { style: "currency", currency: "INR", maximumFractionDigits: 0 },
333
+ },
334
+ ] satisfies {
335
+ label: string
336
+ locale: string
337
+ defaultValue: number
338
+ step: number
339
+ format: Intl.NumberFormatOptions
340
+ }[]
341
+
342
+ function mount(root: HTMLElement) {
343
+ root.innerHTML = `
344
+ <div data-slot="stack">
345
+ ${fields
346
+ .map(
347
+ (field) => `
348
+ <ui-field>
349
+ <ui-field-label>${field.label}</ui-field-label>
350
+ <ui-number-input default-value="${field.defaultValue}" step="${field.step}" locale="${field.locale}" min="0">
351
+ <ui-number-input-group>
352
+ <ui-number-input-decrement>
353
+ <button aria-label="Decrease">
354
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
355
+ </button>
356
+ </ui-number-input-decrement>
357
+ <ui-number-input-control>
358
+ <input />
359
+ </ui-number-input-control>
360
+ <ui-number-input-increment>
361
+ <button aria-label="Increase">
362
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
363
+ </button>
364
+ </ui-number-input-increment>
365
+ </ui-number-input-group>
366
+ </ui-number-input>
367
+ <ui-field-description>Formatted for ${field.locale}.</ui-field-description>
368
+ </ui-field>`
369
+ )
370
+ .join("")}
371
+ </div>
372
+ `
373
+ root
374
+ .querySelectorAll<HTMLElement & { format: Intl.NumberFormatOptions }>(
375
+ "ui-number-input"
376
+ )
377
+ .forEach((el, index) => {
378
+ el.format = fields[index].format
379
+ })
380
+ }
381
+
382
+ export { mount }
383
+ ```
384
+
385
+ Edit Preis using a comma for decimals, or step Budget to see Indian digit grouping. The displayed and accepted formats follow each field's locale.
386
+
387
+ ## Mouse Wheel
388
+
389
+ Set `wheel` to step with the mouse wheel while the input is focused. It is off by default.
390
+
391
+ ```ts title="wheel.ts"
392
+ import "@ui-sdk/web/number-input"
393
+ import "@ui-sdk/web/field"
394
+ import { createElement, Minus, Plus } from "lucide"
395
+
396
+ function mount(root: HTMLElement) {
397
+ root.innerHTML = `
398
+ <ui-field>
399
+ <ui-field-label>Font size</ui-field-label>
400
+ <ui-number-input default-value="16" min="8" max="72" wheel>
401
+ <ui-number-input-group>
402
+ <ui-number-input-decrement>
403
+ <button aria-label="Decrease">
404
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
405
+ </button>
406
+ </ui-number-input-decrement>
407
+ <ui-number-input-control>
408
+ <input />
409
+ </ui-number-input-control>
410
+ <ui-number-input-increment>
411
+ <button aria-label="Increase">
412
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
413
+ </button>
414
+ </ui-number-input-increment>
415
+ </ui-number-input-group>
416
+ </ui-number-input>
417
+ <ui-field-description>Focus the input, then scroll over it.</ui-field-description>
418
+ </ui-field>
419
+ `
420
+ }
421
+
422
+ export { mount }
423
+ ```
424
+
425
+ Focus Font size and scroll over it to change the value. Move focus away and scroll again; the value stays unchanged.
426
+
427
+ ## Scrub Area
428
+
429
+ `ui-number-input-scrub-area` changes the value while you drag it. The handle can be any element inside the number input: the label, as in the first example, or a letter or icon inside the group, as in a design tool. Right and up increase the value. `orientation` chooses an axis, and `pixels-per-step` controls how far the pointer moves per step. Shift and Alt (Option) use the large and small increments.
430
+
431
+ ```ts title="scrub.ts"
432
+ import "@ui-sdk/web/number-input"
433
+ import { createElement, MoveHorizontal } from "lucide"
434
+ import { log } from "./log"
435
+
436
+ const fields = [
437
+ { label: "X position", handle: "X", defaultValue: 120 },
438
+ { label: "Y position", handle: "Y", defaultValue: 48 },
439
+ { label: "Width", handle: "W", defaultValue: 320, min: 0 },
440
+ { label: "Height", handle: "H", defaultValue: 240, min: 0 },
441
+ ]
442
+
443
+ function mount(root: HTMLElement) {
444
+ const title = `frame-${crypto.randomUUID()}`
445
+ root.innerHTML = `
446
+ <div data-slot="card" role="group" aria-labelledby="${title}">
447
+ <h3 id="${title}">Frame</h3>
448
+ <div data-slot="grid">
449
+ ${fields
450
+ .map(
451
+ ({ label, handle, defaultValue, min }) => `
452
+ <ui-number-input default-value="${defaultValue}"${min === undefined ? "" : ` min="${min}"`}>
453
+ <ui-number-input-group>
454
+ <ui-number-input-scrub-area pixels-per-step="1">
455
+ <span aria-hidden="true">${handle}</span>
456
+ <ui-number-input-scrub-area-cursor>
457
+ ${createElement(MoveHorizontal, { "aria-hidden": "true" }).outerHTML}
458
+ </ui-number-input-scrub-area-cursor>
459
+ </ui-number-input-scrub-area>
460
+ <ui-number-input-control>
461
+ <input aria-label="${label}" />
462
+ </ui-number-input-control>
463
+ </ui-number-input-group>
464
+ </ui-number-input>
465
+ `
466
+ )
467
+ .join("")}
468
+ </div>
469
+ </div>
470
+ `
471
+ root.querySelectorAll("ui-number-input").forEach((field, index) => {
472
+ field.addEventListener("valuecommit", (event) => {
473
+ log(root, `${fields[index].label} ${event.detail.value ?? "empty"}`)
474
+ })
475
+ })
476
+ }
477
+
478
+ export { mount }
479
+ ```
480
+
481
+ Drag the X, Y, W or H letter sideways to change that value. Hold Shift for larger steps or Alt (Option) for smaller ones. The console logs each value when the drag ends.
482
+
483
+ Where pointer lock is available, the drag can continue past the edge of the screen. `ui-number-input-scrub-area-cursor` draws a replacement cursor while it is locked. The cursor is placed in the document body with an inline translation; center your icon with negative margins.
484
+
485
+ To place a scrub area outside its number input, such as on an icon in a toolbar, set the scrub area's `for` attribute to the number input's `id`. More than one scrub area can control the same field.
486
+
487
+ ## Disabled
488
+
489
+ Set `disabled` on the component or its enclosing fieldset to prevent focus, changes, and form submission.
490
+
491
+ ```html
492
+ <ui-field>
493
+ <ui-field-label>Quantity</ui-field-label>
494
+ <ui-number-input default-value="3" disabled>
495
+ <ui-number-input-group>
496
+ <ui-number-input-decrement
497
+ ><button aria-label="Decrease">−</button></ui-number-input-decrement
498
+ >
499
+ <ui-number-input-control><input /></ui-number-input-control>
500
+ <ui-number-input-increment
501
+ ><button aria-label="Increase">+</button></ui-number-input-increment
502
+ >
503
+ </ui-number-input-group>
504
+ </ui-number-input>
505
+ <ui-field-description
506
+ >Sold out. You can't change the quantity.</ui-field-description
507
+ >
508
+ </ui-field>
509
+ ```
510
+
511
+ Set `disabled` on one step button, on its element like `<ui-number-input-increment disabled>` or on the `<button>` inside it, to turn off only that direction. The other button still steps.
512
+
513
+ ## Read-Only
514
+
515
+ Set `read-only` to keep the value focusable and included in form data while preventing edits and steps.
516
+
517
+ ```html
518
+ <ui-field>
519
+ <ui-field-label>Seats included</ui-field-label>
520
+ <ui-number-input default-value="5" read-only>
521
+ <ui-number-input-group>
522
+ <ui-number-input-decrement
523
+ ><button aria-label="Decrease">−</button></ui-number-input-decrement
524
+ >
525
+ <ui-number-input-control><input /></ui-number-input-control>
526
+ <ui-number-input-increment
527
+ ><button aria-label="Increase">+</button></ui-number-input-increment
528
+ >
529
+ </ui-number-input-group>
530
+ </ui-number-input>
531
+ <ui-field-description>Set by your plan.</ui-field-description>
532
+ </ui-field>
533
+ ```
534
+
535
+ ## Validation
536
+
537
+ Use `required` to block an empty value. Field associates its error with the input and marks it invalid after an unsuccessful submit. Here, choosing **Continue** while Age is empty shows "Enter your age."
538
+
539
+ ```html
540
+ <form>
541
+ <ui-field>
542
+ <ui-field-label>Age</ui-field-label>
543
+ <ui-number-input name="age" required min="18" max="120">
544
+ <ui-number-input-group>
545
+ <ui-number-input-decrement
546
+ ><button aria-label="Decrease">−</button></ui-number-input-decrement
547
+ >
548
+ <ui-number-input-control><input /></ui-number-input-control>
549
+ <ui-number-input-increment
550
+ ><button aria-label="Increase">+</button></ui-number-input-increment
551
+ >
552
+ </ui-number-input-group>
553
+ </ui-number-input>
554
+ <ui-field-error match="valueMissing">Enter your age.</ui-field-error>
555
+ </ui-field>
556
+ <button type="submit">Continue</button>
557
+ </form>
558
+ ```
559
+
560
+ ## Controlled
561
+
562
+ Set the element's `value` property, and listen for `valuechange`. `valuechange` reports valid changes while an interaction is in progress. `valuecommit` fires when typing is committed, a step key or button is released, or a scrub ends. Use the commit event for work that should happen after the interaction.
563
+
564
+ ```ts title="controlled.ts"
565
+ import "@ui-sdk/web/number-input"
566
+ import "@ui-sdk/web/field"
567
+ import { createElement, Minus, Plus } from "lucide"
568
+ import { log } from "./log"
569
+
570
+ function mount(root: HTMLElement) {
571
+ root.innerHTML = `
572
+ <div data-slot="stack">
573
+ <div data-slot="controls">
574
+ <button type="button" data-value="4">Reset to 4</button>
575
+ <button type="button" data-value="">Clear</button>
576
+ </div>
577
+ <ui-field>
578
+ <ui-field-label>Servings</ui-field-label>
579
+ <ui-number-input default-value="4" min="1" max="12">
580
+ <ui-number-input-group>
581
+ <ui-number-input-decrement>
582
+ <button aria-label="Decrease">
583
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
584
+ </button>
585
+ </ui-number-input-decrement>
586
+ <ui-number-input-control>
587
+ <input />
588
+ </ui-number-input-control>
589
+ <ui-number-input-increment>
590
+ <button aria-label="Increase">
591
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
592
+ </button>
593
+ </ui-number-input-increment>
594
+ </ui-number-input-group>
595
+ </ui-number-input>
596
+ </ui-field>
597
+ </div>
598
+ `
599
+ const field = root.querySelector("ui-number-input")!
600
+ field.addEventListener("valuechange", (event) => {
601
+ log(root, `Value ${event.detail.value ?? "empty"} (${event.detail.reason})`)
602
+ })
603
+ field.addEventListener("valuecommit", (event) => {
604
+ log(
605
+ root,
606
+ `Committed ${event.detail.value ?? "empty"} (${event.detail.reason})`
607
+ )
608
+ })
609
+ root.querySelectorAll<HTMLButtonElement>("[data-value]").forEach((button) => {
610
+ button.addEventListener("click", () => {
611
+ field.value = button.dataset.value ? Number(button.dataset.value) : null
612
+ })
613
+ })
614
+ }
615
+
616
+ export { mount }
617
+ ```
618
+
619
+ Hold a Servings step button. The console logs each value as it changes, then one Committed line when you let go. Clear sets the value to null, and Reset to 4 restores it.
620
+
621
+ See [State and Events](../state-and-events.md) for the shared state conventions and [Reasons](#reasons) for the events specific to Number Input.
622
+
623
+ ## Controller
624
+
625
+ Use the element's `api` property to change or focus the input from other controls. `increment()` and `decrement()` step the value; pass `{ large: true }` for a large step. `setValue()`, `getValue()`, and `focus()` cover direct updates and inspection.
626
+
627
+ ```ts title="controller.ts"
628
+ import "@ui-sdk/web/number-input"
629
+ import "@ui-sdk/web/field"
630
+ import { createElement, Minus, Plus } from "lucide"
631
+ import { log } from "./log"
632
+
633
+ function mount(root: HTMLElement) {
634
+ root.innerHTML = `
635
+ <div data-slot="stack">
636
+ <div data-slot="controls">
637
+ <button type="button" data-action="out">Zoom Out</button>
638
+ <button type="button" data-action="in">Zoom In</button>
639
+ <button type="button" data-action="reset">Actual Size</button>
640
+ <button type="button" data-action="read">Read</button>
641
+ <button type="button" data-action="focus">Focus</button>
642
+ </div>
643
+ <ui-field>
644
+ <ui-field-label>Zoom</ui-field-label>
645
+ <ui-number-input default-value="100" min="10" max="400" step="10">
646
+ <ui-number-input-group>
647
+ <ui-number-input-decrement>
648
+ <button aria-label="Decrease">
649
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
650
+ </button>
651
+ </ui-number-input-decrement>
652
+ <ui-number-input-control>
653
+ <input />
654
+ </ui-number-input-control>
655
+ <ui-number-input-increment>
656
+ <button aria-label="Increase">
657
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
658
+ </button>
659
+ </ui-number-input-increment>
660
+ </ui-number-input-group>
661
+ </ui-number-input>
662
+ </ui-field>
663
+ </div>
664
+ `
665
+ const field = root.querySelector("ui-number-input")!
666
+ field.format = { style: "unit", unit: "percent" }
667
+ field.addEventListener("valuechange", (event) => {
668
+ log(root, `Zoom ${event.detail.value ?? "empty"}% (${event.detail.reason})`)
669
+ })
670
+ const zoom = field.api
671
+ const actions: Record<string, () => void> = {
672
+ out: () => zoom.decrement({ large: true }),
673
+ in: () => zoom.increment({ large: true }),
674
+ reset: () => zoom.setValue(100),
675
+ read: () => log(root, `Zoom is ${zoom.getValue()}%`),
676
+ focus: () => zoom.focus(),
677
+ }
678
+ root
679
+ .querySelectorAll<HTMLButtonElement>("[data-action]")
680
+ .forEach((button) => {
681
+ button.addEventListener("click", () => actions[button.dataset.action!]())
682
+ })
683
+ }
684
+
685
+ export { mount }
686
+ ```
687
+
688
+ Press Zoom In and Zoom Out to move by 100%, then Actual Size to restore 100%. Read logs the current value, and Focus moves into the field.
689
+
690
+ [Controllers](../controllers.md) covers creating and sharing a controller.
691
+
692
+ ## Form
693
+
694
+ The element is form-associated and submits the plain numeric value under `name`. Formatting does not change form data: a displayed amount of $4.50 still submits `4.5`. Empty values submit an empty string.
695
+
696
+ ```ts title="form.ts"
697
+ import "@ui-sdk/web/number-input"
698
+ import "@ui-sdk/web/field"
699
+ import { createElement, Minus, Plus } from "lucide"
700
+ import { log } from "./log"
701
+
702
+ let count = 0
703
+
704
+ function mount(root: HTMLElement) {
705
+ const formId = `order-${++count}`
706
+ root.innerHTML = `
707
+ <div data-slot="stack">
708
+ <form data-slot="stack" id="${formId}">
709
+ <ui-field>
710
+ <ui-field-label>Quantity</ui-field-label>
711
+ <ui-number-input name="quantity" default-value="2" min="1">
712
+ <ui-number-input-group>
713
+ <ui-number-input-decrement>
714
+ <button aria-label="Decrease">
715
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
716
+ </button>
717
+ </ui-number-input-decrement>
718
+ <ui-number-input-control>
719
+ <input />
720
+ </ui-number-input-control>
721
+ <ui-number-input-increment>
722
+ <button aria-label="Increase">
723
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
724
+ </button>
725
+ </ui-number-input-increment>
726
+ </ui-number-input-group>
727
+ </ui-number-input>
728
+ </ui-field>
729
+ <ui-field>
730
+ <ui-field-label>Tip</ui-field-label>
731
+ <ui-number-input name="tip" default-value="4.5" min="0" step="0.5">
732
+ <ui-number-input-group>
733
+ <ui-number-input-decrement>
734
+ <button aria-label="Decrease">
735
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
736
+ </button>
737
+ </ui-number-input-decrement>
738
+ <ui-number-input-control>
739
+ <input />
740
+ </ui-number-input-control>
741
+ <ui-number-input-increment>
742
+ <button aria-label="Increase">
743
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
744
+ </button>
745
+ </ui-number-input-increment>
746
+ </ui-number-input-group>
747
+ </ui-number-input>
748
+ </ui-field>
749
+ </form>
750
+ <ui-field>
751
+ <ui-field-label>Gift cards</ui-field-label>
752
+ <ui-number-input name="gift-cards" form="${formId}" default-value="0" min="0">
753
+ <ui-number-input-group>
754
+ <ui-number-input-decrement>
755
+ <button aria-label="Decrease">
756
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
757
+ </button>
758
+ </ui-number-input-decrement>
759
+ <ui-number-input-control>
760
+ <input />
761
+ </ui-number-input-control>
762
+ <ui-number-input-increment>
763
+ <button aria-label="Increase">
764
+ ${createElement(Plus, { "aria-hidden": "true" }).outerHTML}
765
+ </button>
766
+ </ui-number-input-increment>
767
+ </ui-number-input-group>
768
+ </ui-number-input>
769
+ </ui-field>
770
+ <div data-slot="row">
771
+ <button type="reset" form="${formId}">Reset</button>
772
+ <button type="submit" form="${formId}" data-variant="solid">Save</button>
773
+ </div>
774
+ </div>
775
+ `
776
+ const [, tip] = root.querySelectorAll("ui-number-input")
777
+ tip.format = { style: "currency", currency: "USD" }
778
+ const form = root.querySelector("form")!
779
+ form.addEventListener("submit", (event) => {
780
+ event.preventDefault()
781
+ const data = [...new FormData(form)].map(
782
+ ([name, value]) => `${name}=${value}`
783
+ )
784
+ log(root, `Submitted ${data.join(", ")}`)
785
+ })
786
+ }
787
+
788
+ export { mount }
789
+ ```
790
+
791
+ Change Quantity or Tip and press Save: the console logs the plain numeric values. Gift cards belongs to the same form through its form ID. Reset restores the defaults.
792
+
793
+ Set `form` to associate the input with a form elsewhere in the page. A form reset restores the default value and reports the `reset` reason.
794
+
795
+ ## Custom Rendering
796
+
797
+ Elements have no render functions. The input and step buttons are your own elements, so change them directly, and read state from their data attributes or the `valuechange` event. Here a `valuechange` listener gives the decrement button a tooltip, and shows Max on the increment button once the value reaches its bound.
798
+
799
+ ```ts title="render.ts"
800
+ import "@ui-sdk/web/number-input"
801
+ import "@ui-sdk/web/field"
802
+ import { createElement, Minus, Plus } from "lucide"
803
+
804
+ function mount(root: HTMLElement) {
805
+ root.innerHTML = `
806
+ <ui-field>
807
+ <ui-field-label>Temperature</ui-field-label>
808
+ <ui-number-input default-value="-2" min="-20" max="40">
809
+ <ui-number-input-group>
810
+ <ui-number-input-decrement>
811
+ <button aria-label="Colder">
812
+ ${createElement(Minus, { "aria-hidden": "true" }).outerHTML}
813
+ </button>
814
+ </ui-number-input-decrement>
815
+ <ui-number-input-control>
816
+ <input />
817
+ </ui-number-input-control>
818
+ <ui-number-input-increment>
819
+ <button aria-label="Warmer"></button>
820
+ </ui-number-input-increment>
821
+ </ui-number-input-group>
822
+ </ui-number-input>
823
+ </ui-field>
824
+ `
825
+ const field = root.querySelector("ui-number-input")!
826
+ field.format = { style: "unit", unit: "celsius" }
827
+ const colder = root.querySelector<HTMLButtonElement>('[aria-label="Colder"]')!
828
+ const warmer = root.querySelector<HTMLButtonElement>('[aria-label="Warmer"]')!
829
+ const plus = createElement(Plus, { "aria-hidden": "true" }).outerHTML
830
+ const show = (value: number | null) => {
831
+ colder.title = value === -20 ? "As cold as it goes" : "Colder"
832
+ warmer.innerHTML = value === 40 ? "<span>Max</span>" : plus
833
+ }
834
+ field.addEventListener("valuechange", (event) => show(event.detail.value))
835
+ show(field.value)
836
+ }
837
+
838
+ export { mount }
839
+ ```
840
+
841
+ Press End in Temperature to jump to 40°C. The increment button reads Max; hover the decrement button to see its tooltip.
842
+
843
+ Each part's state is listed under [State](#state).
844
+
845
+ ## Styling
846
+
847
+ Style the group as the field's box, and the input and step buttons inside it. `data-disabled` on a step button also means it has reached its bound, and `data-scrubbing` marks every part while a drag changes the value. Inside Field, the native input receives validation attributes such as `data-invalid`. Use logical spacing so the layout follows the surrounding direction.
848
+
849
+ Each part has a `data-slot`, such as `number-input-group`, and its state attributes are listed under [Data Attributes](#data-attributes).
850
+
851
+ ## Data Attributes
852
+
853
+ ### `<ui-number-input>`
854
+ `[data-slot="number-input"]`
855
+
856
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
857
+ - `data-readonly`: Present when read-only.
858
+ - `data-required`: Present when required.
859
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
860
+
861
+ ### `<ui-number-input-scrub-area>`
862
+ `[data-slot="number-input-scrub-area"]`
863
+
864
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
865
+ - `data-readonly`: Present when read-only.
866
+ - `data-required`: Present when required.
867
+ - `data-orientation` (`horizontal`, `vertical`): Which way to drag.
868
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
869
+
870
+ ### `<ui-number-input-scrub-area-cursor>`
871
+ `[data-slot="number-input-scrub-area-cursor"]`
872
+
873
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
874
+ - `data-readonly`: Present when read-only.
875
+ - `data-required`: Present when required.
876
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
877
+
878
+ ### `<ui-number-input-group>`
879
+ `[data-slot="number-input-group"]`
880
+
881
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
882
+ - `data-readonly`: Present when read-only.
883
+ - `data-required`: Present when required.
884
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
885
+
886
+ ### `<ui-number-input-decrement>`
887
+ `[data-slot="number-input-decrement"]`
888
+
889
+ - `data-disabled`: Present when the button can't step: it or the field is disabled, the field is read-only, or the value is at `min`.
890
+ - `data-readonly`: Present when read-only.
891
+ - `data-required`: Present when required.
892
+ - `data-at-bound`
893
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
894
+
895
+ ### `<ui-number-input-control>`
896
+ `[data-slot="number-input-control"]`
897
+
898
+ - `data-disabled`: Present when disabled, by its own prop or a `<fieldset>`.
899
+ - `data-readonly`: Present when read-only.
900
+ - `data-required`: Present when required.
901
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
902
+
903
+ ### `<ui-number-input-increment>`
904
+ `[data-slot="number-input-increment"]`
905
+
906
+ - `data-disabled`: Present when the button can't step: it or the field is disabled, the field is read-only, or the value is at `max`.
907
+ - `data-readonly`: Present when read-only.
908
+ - `data-required`: Present when required.
909
+ - `data-scrubbing`: Present while a drag on the scrub area changes the value.
910
+
911
+ ## API Reference
912
+
913
+ Props are attributes in kebab case, like `default-value`, `large-step` and `read-only`. `format` and `controller` are properties. Callbacks are DOM events.
914
+
915
+ ### NumberInput
916
+
917
+ - `value?` (`number | null`): The value, or null for an empty field. Pass it to control the field.
918
+ - `defaultValue?` (`number | null`): The value at first, when uncontrolled. A form reset returns to it.
919
+ - `valuechange event`: Runs when the value changes: as a typed number becomes valid, and on every step. `details.cancel()` stops it.
920
+ - `valuecommit event`: Runs once an interaction that changed the value ends: typing is committed, a key or a button is released, or a scrub ends. For saving.
921
+ - `min?` (`number`): The lowest value. Typed values below it are raised to it when committed.
922
+ - `max?` (`number`): The highest value. Typed values above it are lowered to it when committed.
923
+ - `step?` (`number`): How far one step moves. Committed values snap to steps from `min`, or from 0 without one.
924
+ - `largeStep?` (`number`): How far Page Up, Page Down, and Shift with an arrow key, a button, the wheel or a scrub move.
925
+ - `smallStep?` (`number`): How far Alt (Option on a Mac) with an arrow key, the wheel or a scrub moves, for fine changes. Values it reaches snap to it rather than to `step`.
926
+ - `format?` (`Intl.NumberFormatOptions`): How the value shows: `Intl.NumberFormat` options, like `{ style: "currency", currency: "USD" }`. Typing accepts the same format in the locale, and percents are fractions: 50% is 0.5.
927
+ - `locale?` (`string`): The locale for formatting and reading typed numbers. Defaults to the page's `lang`, or the browser's language without one, once the page is interactive. Server markup uses English until then, so it never disagrees with the client.
928
+ - `wheel?` (`boolean`): Lets the mouse wheel step the value while the input has focus.
929
+ - `disabled?` (`boolean`): Disables it like a native control: it can't be focused or changed, and isn't submitted.
930
+ - `readOnly?` (`boolean`): Keeps it focusable and submitted, but stops changes.
931
+ - `required?` (`boolean`): Blocks form submission while it's empty, with the browser's own message.
932
+ - `name?` (`string`): The name it submits under. The number is submitted as is, like `1234.5`, never formatted.
933
+ - `form?` (`string`): The id of a form to belong to, when it isn't inside that form.
934
+ - `controller?` (`object`): Steps it from anywhere. Create one with `createNumberInputController()`.
935
+ - `debug?` (`boolean`): Logs every state change with its reason to the console, in development builds.
936
+
937
+ ### NumberInputScrubArea
938
+
939
+ - `orientation?` (`"horizontal" | "vertical"`): Which way to drag: right and up increase.
940
+ - `pixelsPerStep?` (`number`): How far the pointer moves for one step, in CSS pixels.
941
+ - `controller?` (`object`): Connects a scrub area outside the number input, like an icon in a toolbar, to the number input with this controller. Inside it, it connects on its own. Web components use `for="<number input id>"` instead.
942
+
943
+ ### NumberInputIncrement and NumberInputDecrement
944
+
945
+ - `disabled?` (`boolean`): Turns off this button alone. Pressing, clicking or holding it doesn't step.
946
+
947
+ ### State
948
+
949
+ The same state shows as data attributes on each element.
950
+
951
+ - `value` (`number | null`): The value, or null when empty.
952
+ - `disabled` (`boolean`): Whether it's disabled, by itself or by a `<fieldset>`.
953
+ - `readOnly` (`boolean`): Whether it's read-only: it stays focusable and submitted, but typing, steps and scrubs don't change it.
954
+ - `required` (`boolean`): Whether a form won't submit while it's empty.
955
+ - `scrubbing` (`boolean`): Whether a scrub is moving the value.
956
+
957
+ The buttons' state, `NumberInputButtonState`, says whether that button can step, and whether the value is at its bound, apart from a disabled field:
958
+
959
+ - `value` (`number | null`): The value, or null when empty.
960
+ - `disabled` (`boolean`): Whether it's disabled, by itself or by a `<fieldset>`. Whether the button can't step, because it or the field is disabled, the field is read-only, or the value is at its bound.
961
+ - `readOnly` (`boolean`): Whether it's read-only: it stays focusable and submitted, but typing, steps and scrubs don't change it.
962
+ - `required` (`boolean`): Whether a form won't submit while it's empty.
963
+ - `scrubbing` (`boolean`): Whether a scrub is moving the value.
964
+ - `atBound` (`boolean`): Whether the value is at the bound this button steps toward, like the maximum for the increment.
965
+
966
+ ### Reasons
967
+
968
+ `event.detail.reason` says why the value changed.
969
+
970
+ - `input-change` (`Event`): Typing, pasting or dropping changed a text field's value; in a combobox it can also open or close it, or Enter keeps the typed text as the value.
971
+ - `input-commit` (`KeyboardEvent | FocusEvent`): Enter, or leaving a number input, committed what was typed: a number input parses, clamps and snaps it to the step, and an autocomplete closes its suggestions on it.
972
+ - `step-key` (`KeyboardEvent`): Arrow, Page Up, Page Down, Home or End stepped a slider or a number input.
973
+ - `increment-press` (`MouseEvent | PointerEvent`): A number input's increment button was pressed, or held.
974
+ - `decrement-press` (`MouseEvent | PointerEvent`): A number input's decrement button was pressed, or held.
975
+ - `wheel` (`WheelEvent`): The mouse wheel stepped a focused number input.
976
+ - `scrub` (`PointerEvent`): A drag on a number input's scrub area.
977
+ - `reset` (`none`): A form reset.
978
+ - `restore` (`none`): The browser restored state, like after going back, or a saved value was read back from storage.
979
+ - `imperative` (`none`): A controller or `api` call, or a property set on a web component.
980
+
981
+ ### Controller
982
+
983
+ The element's `api` property has the same methods. The `value` property reads and sets the value.
984
+
985
+ Every write method takes an optional `{ reason, event }`, fires the change event like a press, and returns `false` when the change is canceled.
986
+
987
+ - `getValue()` (`() => number | null`): The value, or null when the field is empty.
988
+ - `setValue(value, options?)` (`(value: number | null, options?: NumberInputChangeOptions | undefined) => boolean`): Sets the value, as a user would. It's clamped and snapped to the step.
989
+ - `increment(options?)` (`(options?: (NumberInputChangeOptions & { large?: boolean | undefined; }) | undefined) => boolean`): Steps the value up, by `largeStep` with `{ large: true }`.
990
+ - `decrement(options?)` (`(options?: (NumberInputChangeOptions & { large?: boolean | undefined; }) | undefined) => boolean`): Steps the value down, by `largeStep` with `{ large: true }`.
991
+ - `focus()` (`() => void`): Moves focus to the input.
992
+
993
+ ### Events
994
+
995
+ Events fire on their own element and don't bubble, with the value in `event.detail.value` and the `reason` and `event` beside it. Calling `preventDefault()` on `valuechange` cancels the change.
996
+
997
+ | Member | On | Description |
998
+ | --------------------------- | ----------------- | --------------------------------------------------------------------- |
999
+ | `value` property, attribute | `ui-number-input` | The value, or null when empty. An empty attribute is empty |
1000
+ | `format` property | `ui-number-input` | `Intl.NumberFormat` options |
1001
+ | `api` property | `ui-number-input` | The [controller](#controller) |
1002
+ | `valuechange` event | `ui-number-input` | `detail: { value, reason, event, source }`, as the value changes |
1003
+ | `valuecommit` event | `ui-number-input` | `detail: { value, reason, event, source }`, once the interaction ends |
1004
+
1005
+ ## Accessibility
1006
+
1007
+ Follows the keyboard model of the [WAI-ARIA spinbutton pattern](https://www.w3.org/WAI/ARIA/apg/patterns/spinbutton/) on a native text input, so ordinary text editing works too. Give the input a FieldLabel or `aria-label`, and name both step buttons. Each button points to the input with `aria-controls`.
1008
+
1009
+ ### Keyboard
1010
+
1011
+ | Input | Increment |
1012
+ | --------------------------------------------------- | -------------------------------------------------- |
1013
+ | ArrowUp, ArrowDown, or a step button | `step`, which defaults to 1. |
1014
+ | Shift with an arrow or button; Page Up or Page Down | `large-step`, which defaults to ten steps. |
1015
+ | Alt (Option) with an arrow key | `small-step`, which defaults to a tenth of a step. |
1016
+ | Home or End | The minimum or maximum, when set. |
1017
+
1018
+ Enter commits typed text. The step buttons stay out of the Tab order, because the focused input gives the same steps through its arrow keys.
1019
+
1020
+ ### Tested Behavior
1021
+
1022
+ Every line below is an automated test.
1023
+
1024
+ **Keyboard**
1025
+
1026
+ - Starts with its default value, formatted.
1027
+ - ArrowUp and ArrowDown step the value.
1028
+ - Shift with an arrow key steps by the large step.
1029
+ - Home and End go to the minimum and the maximum.
1030
+ - Stepping stops at the bounds.
1031
+ - Steps keep cents exact.
1032
+ - The buttons aren't in the tab order.
1033
+
1034
+ **Typing**
1035
+
1036
+ - Typing a number changes the value.
1037
+ - A typed value above the maximum is lowered when focus leaves.
1038
+ - Typed text in the format is read and formatted when focus leaves.
1039
+ - Typing sends change only once the text is committed.
1040
+ - Letters can't be typed.
1041
+ - Arrow keys step from what's typed.
1042
+ - Text that isn't a number goes back to the value when focus leaves.
1043
+ - The value commits when focus leaves.
1044
+
1045
+ **Buttons**
1046
+
1047
+ - The increment and decrement buttons step the value.
1048
+ - A button at its bound is disabled and marked data-at-bound.
1049
+ - Holding a button repeats the step.
1050
+ - A press commits when the button is released.
1051
+ - A disabled button is marked disabled.
1052
+ - A disabled button doesn't step when clicked or held, and the other still does.
1053
+ - <button disabled> inside a step button element stays disabled past a bound. Web components only.
1054
+ - A button disabled while it's held stops repeating.
1055
+
1056
+ **State and forms**
1057
+
1058
+ - Submits the number as is, not formatted.
1059
+ - A disabled field can't be focused or changed.
1060
+ - A read-only field can be focused, but not changed.
1061
+ - A required empty field blocks the form.
1062
+ - The mouse wheel does nothing unless turned on.
1063
+ - The mouse wheel steps a focused field when turned on.
1064
+ - A canceled change keeps the value.
1065
+ - A controlled field follows its value.
1066
+ - Reads and writes the locale's format.
1067
+
1068
+ Runs in React, Vue and web components.
1069
+
1070
+ Beyond the pattern:
1071
+
1072
+ - Page Up and Page Down step by the large step.
1073
+ - A form reset returns every field to its default.
1074
+ - A typed value snaps to the step when focus leaves.
1075
+ - Enter commits and formats what's typed.
1076
+ - Holding an arrow key commits once, when the key comes up.
1077
+ - A field with cents asks for a decimal keypad.
1078
+ - A scrub steps by distance, whatever the speed.
1079
+ - A scrub that doesn't move commits nothing.
1080
+
1081
+ Runs in React, Vue and web components.
1082
+
1083
+ Screen readers hear the same thing in each tested framework, recorded by a virtual screen reader:
1084
+
1085
+ - Announcements while moving to a number input with a keyboard.
1086
+
1087
+ Runs in React, Vue and web components.