@sakoa/ui 0.1.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 (345) hide show
  1. package/README.md +171 -0
  2. package/dist/App.d.ts +2 -0
  3. package/dist/cli/index.js +9243 -0
  4. package/dist/components/DemoSection.d.ts +30 -0
  5. package/dist/components/SApiKeyboard.d.ts +22 -0
  6. package/dist/components/SApiSection.d.ts +21 -0
  7. package/dist/components/SApiTable.d.ts +46 -0
  8. package/dist/components/STableOfContents.d.ts +2 -0
  9. package/dist/components/ui/SAlert.d.ts +76 -0
  10. package/dist/components/ui/SBadge.d.ts +56 -0
  11. package/dist/components/ui/SButton.d.ts +67 -0
  12. package/dist/components/ui/SCheckbox.d.ts +64 -0
  13. package/dist/components/ui/SChip.d.ts +43 -0
  14. package/dist/components/ui/SDatePicker.d.ts +77 -0
  15. package/dist/components/ui/SGlassButton.d.ts +70 -0
  16. package/dist/components/ui/SIcon.d.ts +29 -0
  17. package/dist/components/ui/SInput.d.ts +129 -0
  18. package/dist/components/ui/SKbd.d.ts +24 -0
  19. package/dist/components/ui/SKbdShortcut.d.ts +14 -0
  20. package/dist/components/ui/SSelect.d.ts +148 -0
  21. package/dist/components/ui/SSkeleton.d.ts +37 -0
  22. package/dist/components/ui/SSwitch.d.ts +61 -0
  23. package/dist/components/ui/STooltip.d.ts +82 -0
  24. package/dist/components/ui/accordion/SAccordionContent.d.ts +23 -0
  25. package/dist/components/ui/accordion/SAccordionItem.d.ts +70 -0
  26. package/dist/components/ui/accordion/SAccordionTrigger.d.ts +37 -0
  27. package/dist/components/ui/accordion/index.d.ts +4 -0
  28. package/dist/components/ui/avatar/SAvatar.d.ts +36 -0
  29. package/dist/components/ui/avatar/SAvatarFallback.d.ts +26 -0
  30. package/dist/components/ui/avatar/SAvatarGroup.d.ts +30 -0
  31. package/dist/components/ui/avatar/SAvatarImage.d.ts +23 -0
  32. package/dist/components/ui/avatar/index.d.ts +4 -0
  33. package/dist/components/ui/breadcrumb/SBreadcrumb.d.ts +22 -0
  34. package/dist/components/ui/breadcrumb/SBreadcrumbEllipsis.d.ts +17 -0
  35. package/dist/components/ui/breadcrumb/SBreadcrumbItem.d.ts +17 -0
  36. package/dist/components/ui/breadcrumb/SBreadcrumbLink.d.ts +26 -0
  37. package/dist/components/ui/breadcrumb/SBreadcrumbList.d.ts +17 -0
  38. package/dist/components/ui/breadcrumb/SBreadcrumbPage.d.ts +17 -0
  39. package/dist/components/ui/breadcrumb/SBreadcrumbSeparator.d.ts +17 -0
  40. package/dist/components/ui/breadcrumb/index.d.ts +7 -0
  41. package/dist/components/ui/card/SCard.d.ts +103 -0
  42. package/dist/components/ui/card/SCardActions.d.ts +44 -0
  43. package/dist/components/ui/card/SCardContent.d.ts +35 -0
  44. package/dist/components/ui/card/SCardFooter.d.ts +38 -0
  45. package/dist/components/ui/card/SCardHeader.d.ts +53 -0
  46. package/dist/components/ui/card/SCardMedia.d.ts +83 -0
  47. package/dist/components/ui/card/SGlassCard.d.ts +103 -0
  48. package/dist/components/ui/card/SMorphingCardContent.d.ts +18 -0
  49. package/dist/components/ui/card/index.d.ts +24 -0
  50. package/dist/components/ui/carousel/SCarousel.d.ts +166 -0
  51. package/dist/components/ui/carousel/index.d.ts +2 -0
  52. package/dist/components/ui/color-picker/SColorPickerAlphaSlider.d.ts +4 -0
  53. package/dist/components/ui/color-picker/SColorPickerCopy.d.ts +19 -0
  54. package/dist/components/ui/color-picker/SColorPickerEyeDropper.d.ts +17 -0
  55. package/dist/components/ui/color-picker/SColorPickerHueSlider.d.ts +4 -0
  56. package/dist/components/ui/color-picker/SColorPickerInputs.d.ts +2 -0
  57. package/dist/components/ui/color-picker/SColorPickerPresets.d.ts +9 -0
  58. package/dist/components/ui/color-picker/SColorPickerPreview.d.ts +2 -0
  59. package/dist/components/ui/color-picker/SColorPickerRecent.d.ts +7 -0
  60. package/dist/components/ui/color-picker/SColorPickerSpectrum.d.ts +4 -0
  61. package/dist/components/ui/color-picker/index.d.ts +11 -0
  62. package/dist/components/ui/drawer/index.d.ts +11 -0
  63. package/dist/components/ui/dropdown/SDropdownDivider.d.ts +8 -0
  64. package/dist/components/ui/dropdown/SDropdownGroup.d.ts +25 -0
  65. package/dist/components/ui/dropdown/SDropdownItem.d.ts +56 -0
  66. package/dist/components/ui/dropdown/index.d.ts +4 -0
  67. package/dist/components/ui/form/SForm.d.ts +38 -0
  68. package/dist/components/ui/form/SFormField.d.ts +31 -0
  69. package/dist/components/ui/form/index.d.ts +5 -0
  70. package/dist/components/ui/modal/index.d.ts +19 -0
  71. package/dist/components/ui/option/SOption.d.ts +32 -0
  72. package/dist/components/ui/option/SOptionGroup.d.ts +28 -0
  73. package/dist/components/ui/option/index.d.ts +2 -0
  74. package/dist/components/ui/otp/SOTP.d.ts +122 -0
  75. package/dist/components/ui/otp/SOTPGroup.d.ts +23 -0
  76. package/dist/components/ui/otp/SOTPSeparator.d.ts +17 -0
  77. package/dist/components/ui/otp/SOTPSlot.d.ts +49 -0
  78. package/dist/components/ui/otp/index.d.ts +7 -0
  79. package/dist/components/ui/otp/types.d.ts +26 -0
  80. package/dist/components/ui/otp/useOTPContext.d.ts +42 -0
  81. package/dist/components/ui/pagination/SPagination.d.ts +151 -0
  82. package/dist/components/ui/pagination/index.d.ts +2 -0
  83. package/dist/components/ui/progress/SProgress.d.ts +62 -0
  84. package/dist/components/ui/progress/SProgressRange.d.ts +91 -0
  85. package/dist/components/ui/progress/index.d.ts +4 -0
  86. package/dist/components/ui/radio/SRadio.d.ts +58 -0
  87. package/dist/components/ui/radio/SRadioGroup.d.ts +52 -0
  88. package/dist/components/ui/radio/index.d.ts +2 -0
  89. package/dist/components/ui/stepper/SStepper.d.ts +83 -0
  90. package/dist/components/ui/stepper/SStepperContent.d.ts +24 -0
  91. package/dist/components/ui/stepper/SStepperDescription.d.ts +20 -0
  92. package/dist/components/ui/stepper/SStepperIndicator.d.ts +37 -0
  93. package/dist/components/ui/stepper/SStepperItem.d.ts +37 -0
  94. package/dist/components/ui/stepper/SStepperSeparator.d.ts +5 -0
  95. package/dist/components/ui/stepper/SStepperTitle.d.ts +20 -0
  96. package/dist/components/ui/stepper/SStepperTrigger.d.ts +22 -0
  97. package/dist/components/ui/stepper/index.d.ts +11 -0
  98. package/dist/components/ui/table/STableBody.d.ts +27 -0
  99. package/dist/components/ui/table/STableCell.d.ts +55 -0
  100. package/dist/components/ui/table/STableColumn.d.ts +87 -0
  101. package/dist/components/ui/table/STableEmpty.d.ts +54 -0
  102. package/dist/components/ui/table/STableHeader.d.ts +25 -0
  103. package/dist/components/ui/table/STableRow.d.ts +38 -0
  104. package/dist/components/ui/table/STableSkeleton.d.ts +29 -0
  105. package/dist/components/ui/table/index.d.ts +98 -0
  106. package/dist/components/ui/table/useDataTable.d.ts +80 -0
  107. package/dist/components/ui/tabs/STabPane.d.ts +31 -0
  108. package/dist/components/ui/tabs/STabsContent.d.ts +21 -0
  109. package/dist/components/ui/tabs/STabsIndicator.d.ts +9 -0
  110. package/dist/components/ui/tabs/STabsTrigger.d.ts +28 -0
  111. package/dist/components/ui/tabs/index.d.ts +6 -0
  112. package/dist/components/ui/toast/SToast.d.ts +49 -0
  113. package/dist/components/ui/toast/SToastContainer.d.ts +21 -0
  114. package/dist/components/ui/toast/index.d.ts +2 -0
  115. package/dist/composables/useAsync.d.ts +134 -0
  116. package/dist/composables/useClickOutside.d.ts +69 -0
  117. package/dist/composables/useClipboard.d.ts +46 -0
  118. package/dist/composables/useDebounce.d.ts +150 -0
  119. package/dist/composables/useDialog.d.ts +118 -0
  120. package/dist/composables/useForm.d.ts +204 -0
  121. package/dist/composables/useHotkey.d.ts +128 -0
  122. package/dist/composables/useIntersectionObserver.d.ts +156 -0
  123. package/dist/composables/useLocalStorage.d.ts +120 -0
  124. package/dist/composables/useMediaQuery.d.ts +115 -0
  125. package/dist/composables/useTheme.d.ts +8 -0
  126. package/dist/composables/useToast.d.ts +1619 -0
  127. package/dist/index.d.ts +71 -0
  128. package/dist/layouts/UILayout.d.ts +2 -0
  129. package/dist/lib/utils.d.ts +2 -0
  130. package/dist/main.d.ts +0 -0
  131. package/dist/router.d.ts +2 -0
  132. package/dist/saka-ui.css +1 -0
  133. package/dist/saka-ui.js +18513 -0
  134. package/dist/saka-ui.umd.cjs +38 -0
  135. package/dist/views/docs/CustomizationView.d.ts +2 -0
  136. package/dist/views/docs/FormValidationView.d.ts +2 -0
  137. package/dist/views/docs/StylingGuideView.d.ts +2 -0
  138. package/dist/views/docs/UseAsyncView.d.ts +2 -0
  139. package/dist/views/docs/UseClickOutsideView.d.ts +124 -0
  140. package/dist/views/docs/UseClipboardView.d.ts +4 -0
  141. package/dist/views/docs/UseDebounceView.d.ts +2 -0
  142. package/dist/views/docs/UseHotkeyView.d.ts +205 -0
  143. package/dist/views/docs/UseIntersectionObserverView.d.ts +5 -0
  144. package/dist/views/docs/UseLocalStorageView.d.ts +2 -0
  145. package/dist/views/docs/UseMediaQueryView.d.ts +2 -0
  146. package/dist/views/docs/UseThemeView.d.ts +2 -0
  147. package/dist/views/examples/AuthFormView.d.ts +2 -0
  148. package/dist/views/examples/CreditCardFormView.d.ts +6 -0
  149. package/dist/views/examples/FormFieldExampleView.d.ts +2 -0
  150. package/dist/views/examples/ProjectFormView.d.ts +2 -0
  151. package/dist/views/ui/AccordionView.d.ts +2 -0
  152. package/dist/views/ui/AlertView.d.ts +2 -0
  153. package/dist/views/ui/AvatarView.d.ts +2 -0
  154. package/dist/views/ui/BadgeView.d.ts +2 -0
  155. package/dist/views/ui/BreadcrumbView.d.ts +2 -0
  156. package/dist/views/ui/ButtonView.d.ts +2 -0
  157. package/dist/views/ui/CardView.d.ts +2 -0
  158. package/dist/views/ui/CarouselView.d.ts +274 -0
  159. package/dist/views/ui/CheckboxView.d.ts +2 -0
  160. package/dist/views/ui/ChipView.d.ts +2 -0
  161. package/dist/views/ui/ColorPickerView.d.ts +2 -0
  162. package/dist/views/ui/DatePickerView.d.ts +2 -0
  163. package/dist/views/ui/DialogView.d.ts +2 -0
  164. package/dist/views/ui/DrawerView.d.ts +2 -0
  165. package/dist/views/ui/DropdownView.d.ts +2 -0
  166. package/dist/views/ui/GlassButtonView.d.ts +2 -0
  167. package/dist/views/ui/GlassCardView.d.ts +2 -0
  168. package/dist/views/ui/HomeView.d.ts +2 -0
  169. package/dist/views/ui/IconsView.d.ts +2 -0
  170. package/dist/views/ui/InputView.d.ts +2 -0
  171. package/dist/views/ui/KbdView.d.ts +2 -0
  172. package/dist/views/ui/ModalView.d.ts +2 -0
  173. package/dist/views/ui/MorphingCardView.d.ts +2 -0
  174. package/dist/views/ui/MorphingModalView.d.ts +2 -0
  175. package/dist/views/ui/OTPView.d.ts +206 -0
  176. package/dist/views/ui/PaginationView.d.ts +2 -0
  177. package/dist/views/ui/ProgressView.d.ts +2 -0
  178. package/dist/views/ui/RadioView.d.ts +2 -0
  179. package/dist/views/ui/SelectView.d.ts +2 -0
  180. package/dist/views/ui/SkeletonView.d.ts +2 -0
  181. package/dist/views/ui/StepperView.d.ts +2 -0
  182. package/dist/views/ui/SwitchView.d.ts +2 -0
  183. package/dist/views/ui/TableView.d.ts +2 -0
  184. package/dist/views/ui/TabsView.d.ts +2 -0
  185. package/dist/views/ui/ToastView.d.ts +2 -0
  186. package/dist/views/ui/TooltipView.d.ts +2 -0
  187. package/dist/vite.svg +1 -0
  188. package/package.json +64 -0
  189. package/registry/components/accordion.json +19 -0
  190. package/registry/components/alert.json +17 -0
  191. package/registry/components/avatar.json +18 -0
  192. package/registry/components/badge.json +14 -0
  193. package/registry/components/breadcrumb.json +24 -0
  194. package/registry/components/button.json +17 -0
  195. package/registry/components/card.json +23 -0
  196. package/registry/components/carousel.json +19 -0
  197. package/registry/components/checkbox.json +17 -0
  198. package/registry/components/chip.json +17 -0
  199. package/registry/components/color-picker.json +24 -0
  200. package/registry/components/date-picker.json +17 -0
  201. package/registry/components/drawer.json +26 -0
  202. package/registry/components/dropdown.json +21 -0
  203. package/registry/components/form.json +16 -0
  204. package/registry/components/glass-button.json +17 -0
  205. package/registry/components/icon.json +17 -0
  206. package/registry/components/input.json +17 -0
  207. package/registry/components/kbd.json +16 -0
  208. package/registry/components/modal.json +32 -0
  209. package/registry/components/option.json +16 -0
  210. package/registry/components/otp.json +23 -0
  211. package/registry/components/pagination.json +18 -0
  212. package/registry/components/progress.json +16 -0
  213. package/registry/components/radio.json +19 -0
  214. package/registry/components/select.json +17 -0
  215. package/registry/components/skeleton.json +14 -0
  216. package/registry/components/switch.json +17 -0
  217. package/registry/components/table.json +26 -0
  218. package/registry/components/tabs.json +19 -0
  219. package/registry/components/toast.json +19 -0
  220. package/registry/components/tooltip.json +14 -0
  221. package/registry/index.json +4 -0
  222. package/registry/source/components/ui/SAlert.vue +388 -0
  223. package/registry/source/components/ui/SBadge.vue +243 -0
  224. package/registry/source/components/ui/SButton.vue +387 -0
  225. package/registry/source/components/ui/SCheckbox.vue +310 -0
  226. package/registry/source/components/ui/SChip.vue +130 -0
  227. package/registry/source/components/ui/SDatePicker.vue +1290 -0
  228. package/registry/source/components/ui/SGlassButton.vue +547 -0
  229. package/registry/source/components/ui/SIcon.vue +78 -0
  230. package/registry/source/components/ui/SInput.vue +1054 -0
  231. package/registry/source/components/ui/SKbd.vue +96 -0
  232. package/registry/source/components/ui/SKbdShortcut.vue +36 -0
  233. package/registry/source/components/ui/SSelect.vue +1290 -0
  234. package/registry/source/components/ui/SSkeleton.vue +185 -0
  235. package/registry/source/components/ui/SSwitch.vue +275 -0
  236. package/registry/source/components/ui/STooltip.vue +491 -0
  237. package/registry/source/components/ui/accordion/SAccordion.vue +248 -0
  238. package/registry/source/components/ui/accordion/SAccordionItem.vue +353 -0
  239. package/registry/source/components/ui/accordion/index.ts +5 -0
  240. package/registry/source/components/ui/avatar/SAvatar.vue +169 -0
  241. package/registry/source/components/ui/avatar/SAvatarFallback.vue +66 -0
  242. package/registry/source/components/ui/avatar/SAvatarGroup.vue +69 -0
  243. package/registry/source/components/ui/avatar/SAvatarImage.vue +92 -0
  244. package/registry/source/components/ui/avatar/index.ts +5 -0
  245. package/registry/source/components/ui/breadcrumb/SBreadcrumb.vue +23 -0
  246. package/registry/source/components/ui/breadcrumb/SBreadcrumbEllipsis.vue +17 -0
  247. package/registry/source/components/ui/breadcrumb/SBreadcrumbItem.vue +14 -0
  248. package/registry/source/components/ui/breadcrumb/SBreadcrumbLink.vue +46 -0
  249. package/registry/source/components/ui/breadcrumb/SBreadcrumbList.vue +17 -0
  250. package/registry/source/components/ui/breadcrumb/SBreadcrumbPage.vue +15 -0
  251. package/registry/source/components/ui/breadcrumb/SBreadcrumbSeparator.vue +18 -0
  252. package/registry/source/components/ui/breadcrumb/index.ts +7 -0
  253. package/registry/source/components/ui/card/SCard.vue +517 -0
  254. package/registry/source/components/ui/card/SCardActions.vue +129 -0
  255. package/registry/source/components/ui/card/SCardContent.vue +117 -0
  256. package/registry/source/components/ui/card/SCardFooter.vue +103 -0
  257. package/registry/source/components/ui/card/SCardHeader.vue +163 -0
  258. package/registry/source/components/ui/card/SCardMedia.vue +312 -0
  259. package/registry/source/components/ui/card/index.ts +34 -0
  260. package/registry/source/components/ui/carousel/SCarousel.vue +1069 -0
  261. package/registry/source/components/ui/carousel/SCarouselSlide.vue +107 -0
  262. package/registry/source/components/ui/carousel/index.ts +3 -0
  263. package/registry/source/components/ui/color-picker/SColorPicker.vue +772 -0
  264. package/registry/source/components/ui/color-picker/SColorPickerAlphaSlider.vue +158 -0
  265. package/registry/source/components/ui/color-picker/SColorPickerCopy.vue +76 -0
  266. package/registry/source/components/ui/color-picker/SColorPickerEyeDropper.vue +68 -0
  267. package/registry/source/components/ui/color-picker/SColorPickerHueSlider.vue +138 -0
  268. package/registry/source/components/ui/color-picker/SColorPickerInputs.vue +227 -0
  269. package/registry/source/components/ui/color-picker/SColorPickerPresets.vue +87 -0
  270. package/registry/source/components/ui/color-picker/SColorPickerPreview.vue +46 -0
  271. package/registry/source/components/ui/color-picker/SColorPickerRecent.vue +74 -0
  272. package/registry/source/components/ui/color-picker/SColorPickerSpectrum.vue +149 -0
  273. package/registry/source/components/ui/color-picker/index.ts +11 -0
  274. package/registry/source/components/ui/drawer/SDrawer.vue +797 -0
  275. package/registry/source/components/ui/drawer/SDrawerClose.vue +64 -0
  276. package/registry/source/components/ui/drawer/SDrawerContent.vue +81 -0
  277. package/registry/source/components/ui/drawer/SDrawerDescription.vue +40 -0
  278. package/registry/source/components/ui/drawer/SDrawerFooter.vue +97 -0
  279. package/registry/source/components/ui/drawer/SDrawerHandle.vue +79 -0
  280. package/registry/source/components/ui/drawer/SDrawerHeader.vue +117 -0
  281. package/registry/source/components/ui/drawer/SDrawerTitle.vue +40 -0
  282. package/registry/source/components/ui/drawer/SDrawerTrigger.vue +51 -0
  283. package/registry/source/components/ui/drawer/index.ts +20 -0
  284. package/registry/source/components/ui/dropdown/SDropdown.vue +843 -0
  285. package/registry/source/components/ui/dropdown/SDropdownDivider.vue +23 -0
  286. package/registry/source/components/ui/dropdown/SDropdownGroup.vue +53 -0
  287. package/registry/source/components/ui/dropdown/SDropdownItem.vue +179 -0
  288. package/registry/source/components/ui/dropdown/index.ts +5 -0
  289. package/registry/source/components/ui/form/SForm.vue +84 -0
  290. package/registry/source/components/ui/form/SFormField.vue +78 -0
  291. package/registry/source/components/ui/form/index.ts +8 -0
  292. package/registry/source/components/ui/modal/SModal.vue +648 -0
  293. package/registry/source/components/ui/modal/SModalClose.vue +49 -0
  294. package/registry/source/components/ui/modal/SModalContent.vue +74 -0
  295. package/registry/source/components/ui/modal/SModalDescription.vue +39 -0
  296. package/registry/source/components/ui/modal/SModalFooter.vue +84 -0
  297. package/registry/source/components/ui/modal/SModalHeader.vue +107 -0
  298. package/registry/source/components/ui/modal/SModalTitle.vue +39 -0
  299. package/registry/source/components/ui/modal/SModalTrigger.vue +61 -0
  300. package/registry/source/components/ui/modal/SMorphingModal.vue +429 -0
  301. package/registry/source/components/ui/modal/SMorphingModalClose.vue +42 -0
  302. package/registry/source/components/ui/modal/SMorphingModalDescription.vue +49 -0
  303. package/registry/source/components/ui/modal/SMorphingModalImage.vue +44 -0
  304. package/registry/source/components/ui/modal/SMorphingModalSubtitle.vue +29 -0
  305. package/registry/source/components/ui/modal/SMorphingModalTitle.vue +34 -0
  306. package/registry/source/components/ui/modal/SMorphingModalTrigger.vue +95 -0
  307. package/registry/source/components/ui/modal/index.ts +32 -0
  308. package/registry/source/components/ui/option/SOption.vue +180 -0
  309. package/registry/source/components/ui/option/SOptionGroup.vue +77 -0
  310. package/registry/source/components/ui/option/index.ts +3 -0
  311. package/registry/source/components/ui/otp/SOTP.vue +843 -0
  312. package/registry/source/components/ui/otp/SOTPGroup.vue +29 -0
  313. package/registry/source/components/ui/otp/SOTPSeparator.vue +15 -0
  314. package/registry/source/components/ui/otp/SOTPSlot.vue +462 -0
  315. package/registry/source/components/ui/otp/index.ts +7 -0
  316. package/registry/source/components/ui/otp/types.ts +27 -0
  317. package/registry/source/components/ui/otp/useOTPContext.ts +62 -0
  318. package/registry/source/components/ui/pagination/SPagination.vue +923 -0
  319. package/registry/source/components/ui/pagination/index.ts +8 -0
  320. package/registry/source/components/ui/progress/SProgress.vue +635 -0
  321. package/registry/source/components/ui/progress/SProgressRange.vue +715 -0
  322. package/registry/source/components/ui/progress/index.ts +4 -0
  323. package/registry/source/components/ui/radio/SRadio.vue +407 -0
  324. package/registry/source/components/ui/radio/SRadioGroup.vue +200 -0
  325. package/registry/source/components/ui/radio/index.ts +3 -0
  326. package/registry/source/components/ui/table/SDataTable.vue +828 -0
  327. package/registry/source/components/ui/table/STableBody.vue +70 -0
  328. package/registry/source/components/ui/table/STableCell.vue +147 -0
  329. package/registry/source/components/ui/table/STableColumn.vue +120 -0
  330. package/registry/source/components/ui/table/STableEmpty.vue +159 -0
  331. package/registry/source/components/ui/table/STableHeader.vue +132 -0
  332. package/registry/source/components/ui/table/STableRow.vue +106 -0
  333. package/registry/source/components/ui/table/STableSkeleton.vue +208 -0
  334. package/registry/source/components/ui/table/index.ts +126 -0
  335. package/registry/source/components/ui/table/useDataTable.ts +519 -0
  336. package/registry/source/components/ui/tabs/STabPane.vue +130 -0
  337. package/registry/source/components/ui/tabs/STabs.vue +467 -0
  338. package/registry/source/components/ui/tabs/index.ts +7 -0
  339. package/registry/source/components/ui/toast/SToast.vue +261 -0
  340. package/registry/source/components/ui/toast/SToastContainer.vue +209 -0
  341. package/registry/source/components/ui/toast/index.ts +2 -0
  342. package/registry/source/composables/useForm.ts +960 -0
  343. package/registry/source/composables/useTheme.ts +86 -0
  344. package/registry/source/composables/useToast.ts +440 -0
  345. package/registry/source/lib/utils.ts +6 -0
@@ -0,0 +1,960 @@
1
+ import { ref, computed, watch, onMounted, type Ref, type ComputedRef, type WatchStopHandle } from 'vue'
2
+
3
+ // ============================================================================
4
+ // Types
5
+ // ============================================================================
6
+
7
+ export type ValidationRule<T = unknown> = (value: T, formData?: Record<string, unknown>) => string | true
8
+ export type AsyncValidationRule<T = unknown> = (value: T, formData?: Record<string, unknown>) => Promise<string | true>
9
+
10
+ export interface FieldConfig<T = unknown> {
11
+ rules?: (ValidationRule<T> | AsyncValidationRule<T>)[]
12
+ validateOn?: 'input' | 'blur' | 'submit'
13
+ /** Debounce delay in ms for async validations (default: 300) */
14
+ debounce?: number
15
+ /** Field dependencies - revalidate this field when these fields change */
16
+ deps?: string[]
17
+ /** Whether to persist this field (default: true). Set to false to exclude from persistence */
18
+ persist?: boolean
19
+ }
20
+
21
+ export interface FieldState<T = unknown> {
22
+ value: Ref<T>
23
+ error: Ref<string>
24
+ touched: Ref<boolean>
25
+ dirty: Ref<boolean>
26
+ valid: ComputedRef<boolean>
27
+ validate: () => Promise<boolean>
28
+ reset: () => void
29
+ }
30
+
31
+ /** Mapped type for type-safe field access */
32
+ export type TypedFields<TValues extends Record<string, unknown>> = {
33
+ [K in keyof TValues]: FieldState<TValues[K]>
34
+ }
35
+
36
+ /** Persistence options for form state */
37
+ export interface PersistOptions {
38
+ /** Storage key for the form data */
39
+ key: string
40
+ /** Storage type: 'localStorage' or 'sessionStorage' (default: 'localStorage') */
41
+ storage?: 'localStorage' | 'sessionStorage'
42
+ /** Debounce delay for saving in ms (default: 500) */
43
+ debounce?: number
44
+ }
45
+
46
+ /** Zod-like schema interface for type inference */
47
+ export interface ZodLikeSchema<T = unknown> {
48
+ safeParse: (data: unknown) => { success: true; data: T } | { success: false; error: { errors: Array<{ path: (string | number)[]; message: string }> } }
49
+ shape?: Record<string, unknown>
50
+ }
51
+
52
+ /** Form state returned by useForm composable */
53
+ export interface FormState<TValues extends Record<string, unknown> = Record<string, unknown>> {
54
+ /** Type-safe field access */
55
+ fields: TypedFields<TValues>
56
+ errors: ComputedRef<Record<string, string>>
57
+ valid: ComputedRef<boolean>
58
+ dirty: ComputedRef<boolean>
59
+ touched: ComputedRef<boolean>
60
+ validate: () => Promise<boolean>
61
+ reset: (values?: Partial<TValues>) => void
62
+ getValues: () => TValues
63
+ // Submission state
64
+ isSubmitting: Ref<boolean>
65
+ submitError: Ref<string | null>
66
+ submitCount: Ref<number>
67
+ isSubmitSuccessful: Ref<boolean>
68
+ // Value management
69
+ setValue: <K extends keyof TValues>(name: K, value: TValues[K]) => void
70
+ setValues: (values: Partial<TValues>) => void
71
+ getFieldValue: <K extends keyof TValues>(name: K) => TValues[K]
72
+ // Error management
73
+ setError: (name: keyof TValues, error: string) => void
74
+ clearError: (name: keyof TValues) => void
75
+ clearErrors: () => void
76
+ // Field state management
77
+ resetField: (name: keyof TValues) => void
78
+ setFieldTouched: (name: keyof TValues, touched?: boolean) => void
79
+ setFieldDirty: (name: keyof TValues, dirty?: boolean) => void
80
+ // Submission
81
+ handleSubmit: <TResult = void>(
82
+ onSubmit: (values: TValues) => Promise<TResult> | TResult,
83
+ onError?: (error: unknown) => void
84
+ ) => () => Promise<TResult | undefined>
85
+ // Watchers
86
+ watchForm: (callback: (values: TValues) => void) => WatchStopHandle
87
+ watchField: <K extends keyof TValues>(name: K, callback: (value: TValues[K], oldValue: TValues[K]) => void) => WatchStopHandle
88
+ // Persistence
89
+ clearPersisted: () => void
90
+ }
91
+
92
+ /** Options for useForm composable */
93
+ export interface UseFormOptions<TValues extends Record<string, unknown> = Record<string, unknown>> {
94
+ /** Initial values for form fields */
95
+ initialValues?: Partial<TValues>
96
+ /** Field configurations with validation rules */
97
+ fields?: { [K in keyof TValues]?: FieldConfig<TValues[K]> }
98
+ /** Zod schema for validation (alternative to fields.rules) */
99
+ schema?: ZodLikeSchema<TValues>
100
+ /** Global debounce delay for async validations (default: 300ms) */
101
+ debounceDelay?: number
102
+ /** Validation mode: 'onChange' | 'onBlur' | 'onSubmit' | 'all' (default: 'onChange') */
103
+ mode?: 'onChange' | 'onBlur' | 'onSubmit' | 'all'
104
+ /** Persistence options for auto-saving form state */
105
+ persist?: PersistOptions
106
+ }
107
+
108
+ // ============================================================================
109
+ // Built-in Validators
110
+ // ============================================================================
111
+
112
+ /** Required field validator */
113
+ export const required = (msg = 'This field is required'): ValidationRule =>
114
+ (value) => {
115
+ if (value === null || value === undefined || value === '' || (Array.isArray(value) && value.length === 0)) {
116
+ return msg
117
+ }
118
+ return true
119
+ }
120
+
121
+ /** Minimum length validator */
122
+ export const minLength = (min: number, msg?: string): ValidationRule =>
123
+ (value) => {
124
+ const str = String(value ?? '')
125
+ if (str.length < min) {
126
+ return msg ?? `Minimum ${min} characters`
127
+ }
128
+ return true
129
+ }
130
+
131
+ /** Maximum length validator */
132
+ export const maxLength = (max: number, msg?: string): ValidationRule =>
133
+ (value) => {
134
+ const str = String(value ?? '')
135
+ if (str.length > max) {
136
+ return msg ?? `Maximum ${max} characters`
137
+ }
138
+ return true
139
+ }
140
+
141
+ /** Minimum value validator (for numbers) */
142
+ export const min = (minVal: number, msg?: string): ValidationRule =>
143
+ (value) => {
144
+ const num = Number(value)
145
+ if (isNaN(num) || num < minVal) {
146
+ return msg ?? `Minimum value is ${minVal}`
147
+ }
148
+ return true
149
+ }
150
+
151
+ /** Maximum value validator (for numbers) */
152
+ export const max = (maxVal: number, msg?: string): ValidationRule =>
153
+ (value) => {
154
+ const num = Number(value)
155
+ if (isNaN(num) || num > maxVal) {
156
+ return msg ?? `Maximum value is ${maxVal}`
157
+ }
158
+ return true
159
+ }
160
+
161
+ /** Email format validator */
162
+ export const email = (msg = 'Invalid email address'): ValidationRule =>
163
+ (value) => {
164
+ const str = String(value ?? '')
165
+ if (!str) return true // Skip if empty (use required for required fields)
166
+ if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(str)) {
167
+ return msg
168
+ }
169
+ return true
170
+ }
171
+
172
+ /** URL format validator */
173
+ export const url = (msg = 'Invalid URL'): ValidationRule =>
174
+ (value) => {
175
+ const str = String(value ?? '')
176
+ if (!str) return true
177
+ try {
178
+ new URL(str)
179
+ return true
180
+ } catch {
181
+ return msg
182
+ }
183
+ }
184
+
185
+ /** Regex pattern validator */
186
+ export const pattern = (regex: RegExp, msg = 'Invalid format'): ValidationRule =>
187
+ (value) => {
188
+ const str = String(value ?? '')
189
+ if (!str) return true
190
+ if (!regex.test(str)) {
191
+ return msg
192
+ }
193
+ return true
194
+ }
195
+
196
+ /** Match another field validator */
197
+ export const sameAs = (fieldName: string, msg?: string): ValidationRule =>
198
+ (value, formData) => {
199
+ if (!formData) return true
200
+ if (value !== formData[fieldName]) {
201
+ return msg ?? `Must match ${fieldName}`
202
+ }
203
+ return true
204
+ }
205
+
206
+ /** Contain uppercase letter */
207
+ export const hasUppercase = (msg = 'Must contain an uppercase letter'): ValidationRule =>
208
+ (value) => {
209
+ const str = String(value ?? '')
210
+ if (!str) return true
211
+ if (!/[A-Z]/.test(str)) return msg
212
+ return true
213
+ }
214
+
215
+ /** Contain lowercase letter */
216
+ export const hasLowercase = (msg = 'Must contain a lowercase letter'): ValidationRule =>
217
+ (value) => {
218
+ const str = String(value ?? '')
219
+ if (!str) return true
220
+ if (!/[a-z]/.test(str)) return msg
221
+ return true
222
+ }
223
+
224
+ /** Contain digit */
225
+ export const hasDigit = (msg = 'Must contain a number'): ValidationRule =>
226
+ (value) => {
227
+ const str = String(value ?? '')
228
+ if (!str) return true
229
+ if (!/\d/.test(str)) return msg
230
+ return true
231
+ }
232
+
233
+ /** Contain special character */
234
+ export const hasSpecial = (msg = 'Must contain a special character'): ValidationRule =>
235
+ (value) => {
236
+ const str = String(value ?? '')
237
+ if (!str) return true
238
+ if (!/[!@#$%^&*(),.?":{}|<>]/.test(str)) return msg
239
+ return true
240
+ }
241
+
242
+ /** Alphanumeric only */
243
+ export const alphanumeric = (msg = 'Only letters and numbers allowed'): ValidationRule =>
244
+ (value) => {
245
+ const str = String(value ?? '')
246
+ if (!str) return true
247
+ if (!/^[a-zA-Z0-9]+$/.test(str)) return msg
248
+ return true
249
+ }
250
+
251
+ /** Numeric only */
252
+ export const numeric = (msg = 'Only numbers allowed'): ValidationRule =>
253
+ (value) => {
254
+ const str = String(value ?? '')
255
+ if (!str) return true
256
+ if (!/^\d+$/.test(str)) return msg
257
+ return true
258
+ }
259
+
260
+ // ============================================================================
261
+ // Custom Validators
262
+ // ============================================================================
263
+
264
+ /**
265
+ * Create a custom validator from a boolean function
266
+ * @example custom((v) => v.length > 5, 'Too short')
267
+ * @example custom(async (v) => await checkUsername(v), 'Username taken')
268
+ */
269
+ export const custom = (
270
+ fn: (value: unknown, formData?: Record<string, unknown>) => boolean | Promise<boolean>,
271
+ msg = 'Validation failed'
272
+ ): ValidationRule | AsyncValidationRule =>
273
+ async (value, formData) => {
274
+ const result = await fn(value, formData)
275
+ return result ? true : msg
276
+ }
277
+
278
+ /** Password strength levels */
279
+ export enum PasswordStrength {
280
+ /** 6+ chars */
281
+ WEAK = 'weak',
282
+ /** 8+ chars, 1 uppercase, 1 number */
283
+ MEDIUM = 'medium',
284
+ /** 10+ chars, 1 uppercase, 1 lowercase, 1 number, 1 special */
285
+ STRONG = 'strong',
286
+ /** 12+ chars, 2 uppercase, 2 lowercase, 2 numbers, 1 special */
287
+ VERY_STRONG = 'very_strong'
288
+ }
289
+
290
+ /** Password strength validator with configurable levels */
291
+ export const passwordStrength = (
292
+ strength: PasswordStrength = PasswordStrength.MEDIUM,
293
+ msg?: string
294
+ ): ValidationRule =>
295
+ (value) => {
296
+ const str = String(value ?? '')
297
+ if (!str) return true // Use required() for required check
298
+
299
+ const checks = {
300
+ [PasswordStrength.WEAK]: {
301
+ minLength: 6,
302
+ test: () => true,
303
+ defaultMsg: 'Password must be at least 6 characters'
304
+ },
305
+ [PasswordStrength.MEDIUM]: {
306
+ minLength: 8,
307
+ test: () => /[A-Z]/.test(str) && /\d/.test(str),
308
+ defaultMsg: 'Password needs 8+ chars, 1 uppercase, 1 number'
309
+ },
310
+ [PasswordStrength.STRONG]: {
311
+ minLength: 10,
312
+ test: () =>
313
+ /[A-Z]/.test(str) &&
314
+ /[a-z]/.test(str) &&
315
+ /\d/.test(str) &&
316
+ /[!@#$%^&*(),.?":{}|<>]/.test(str),
317
+ defaultMsg: 'Password needs 10+ chars, upper, lower, number, special'
318
+ },
319
+ [PasswordStrength.VERY_STRONG]: {
320
+ minLength: 12,
321
+ test: () => {
322
+ const uppers = (str.match(/[A-Z]/g) || []).length >= 2
323
+ const lowers = (str.match(/[a-z]/g) || []).length >= 2
324
+ const digits = (str.match(/\d/g) || []).length >= 2
325
+ const special = /[!@#$%^&*(),.?":{}|<>]/.test(str)
326
+ return uppers && lowers && digits && special
327
+ },
328
+ defaultMsg: 'Password needs 12+ chars, 2 upper, 2 lower, 2 numbers, 1 special'
329
+ }
330
+ }
331
+
332
+ const check = checks[strength]
333
+ if (str.length < check.minLength) {
334
+ return msg || check.defaultMsg
335
+ }
336
+ if (!check.test()) {
337
+ return msg || check.defaultMsg
338
+ }
339
+ return true
340
+ }
341
+
342
+ /** Check if value is different from another field */
343
+ export const different = (fieldName: string, msg?: string): ValidationRule =>
344
+ (value, formData) => {
345
+ if (!formData) return true
346
+ if (value === formData[fieldName]) {
347
+ return msg ?? `Must be different from ${fieldName}`
348
+ }
349
+ return true
350
+ }
351
+
352
+ /** Check if value is in a list of allowed values */
353
+ export const oneOf = <T>(allowed: T[], msg?: string): ValidationRule =>
354
+ (value) => {
355
+ if (!allowed.includes(value as T)) {
356
+ return msg ?? `Must be one of: ${allowed.join(', ')}`
357
+ }
358
+ return true
359
+ }
360
+
361
+ /** Check if value is NOT in a list of disallowed values */
362
+ export const notOneOf = <T>(disallowed: T[], msg?: string): ValidationRule =>
363
+ (value) => {
364
+ if (disallowed.includes(value as T)) {
365
+ return msg ?? `Cannot be: ${disallowed.join(', ')}`
366
+ }
367
+ return true
368
+ }
369
+
370
+
371
+ // ============================================================================
372
+ // Debounce utility
373
+ // ============================================================================
374
+
375
+ function debounce<T extends (...args: unknown[]) => unknown>(
376
+ fn: T,
377
+ delay: number
378
+ ): (...args: Parameters<T>) => void {
379
+ let timeoutId: ReturnType<typeof setTimeout> | null = null
380
+ return (...args: Parameters<T>) => {
381
+ if (timeoutId) clearTimeout(timeoutId)
382
+ timeoutId = setTimeout(() => fn(...args), delay)
383
+ }
384
+ }
385
+
386
+ // ============================================================================
387
+ // Storage utilities
388
+ // ============================================================================
389
+
390
+ function getStorage(type: 'localStorage' | 'sessionStorage'): Storage | null {
391
+ try {
392
+ return type === 'localStorage' ? localStorage : sessionStorage
393
+ } catch {
394
+ return null
395
+ }
396
+ }
397
+
398
+ function loadFromStorage<T>(key: string, storage: Storage | null): T | null {
399
+ if (!storage) return null
400
+ try {
401
+ const data = storage.getItem(key)
402
+ return data ? JSON.parse(data) : null
403
+ } catch {
404
+ return null
405
+ }
406
+ }
407
+
408
+ function saveToStorage(key: string, data: unknown, storage: Storage | null): void {
409
+ if (!storage) return
410
+ try {
411
+ storage.setItem(key, JSON.stringify(data))
412
+ } catch {
413
+ // Storage full or unavailable
414
+ }
415
+ }
416
+
417
+ function removeFromStorage(key: string, storage: Storage | null): void {
418
+ if (!storage) return
419
+ try {
420
+ storage.removeItem(key)
421
+ } catch {
422
+ // Storage unavailable
423
+ }
424
+ }
425
+
426
+ // ============================================================================
427
+ // Zod validation helper
428
+ // ============================================================================
429
+
430
+ function validateWithZod<TValues>(
431
+ schema: ZodLikeSchema<TValues>,
432
+ values: Record<string, unknown>,
433
+ fieldName?: string
434
+ ): Record<string, string> {
435
+ const result = schema.safeParse(values)
436
+ const errors: Record<string, string> = {}
437
+
438
+ if (!result.success) {
439
+ for (const err of result.error.errors) {
440
+ const path = err.path[0]?.toString() ?? ''
441
+ if (!fieldName || path === fieldName) {
442
+ if (!errors[path]) {
443
+ errors[path] = err.message
444
+ }
445
+ }
446
+ }
447
+ }
448
+
449
+ return errors
450
+ }
451
+
452
+ // ============================================================================
453
+ // Main Composable: useForm
454
+ // ============================================================================
455
+
456
+ /**
457
+ * Comprehensive form management composable with validation, submission handling,
458
+ * and reactive state management.
459
+ *
460
+ * @example
461
+ * const form = useForm({
462
+ * initialValues: { email: '', password: '' },
463
+ * fields: {
464
+ * email: { rules: [required(), email()] },
465
+ * password: { rules: [required(), minLength(8)] }
466
+ * }
467
+ * })
468
+ *
469
+ * // With Zod schema
470
+ * const form = useForm({
471
+ * schema: z.object({
472
+ * email: z.string().email(),
473
+ * password: z.string().min(8)
474
+ * })
475
+ * })
476
+ *
477
+ * const onSubmit = form.handleSubmit(async (values) => {
478
+ * await api.login(values)
479
+ * })
480
+ */
481
+ export function useForm<TValues extends Record<string, unknown> = Record<string, unknown>>(
482
+ options: UseFormOptions<TValues>
483
+ ): FormState<TValues> {
484
+ const {
485
+ initialValues = {},
486
+ fields: fieldsConfig = {},
487
+ schema,
488
+ debounceDelay = 300,
489
+ mode = 'onChange',
490
+ persist
491
+ } = options
492
+
493
+ // Internal field storage with proper typing
494
+ const fields: Record<string, FieldState<unknown>> = {}
495
+ const initialValuesStore: Record<string, unknown> = { ...initialValues }
496
+ const fieldDependencies: Record<string, string[]> = {}
497
+
498
+ // Storage for persistence
499
+ const storage = persist ? getStorage(persist.storage ?? 'localStorage') : null
500
+ const persistDebounce = persist?.debounce ?? 500
501
+
502
+ // Form-level state
503
+ const isSubmitting = ref(false)
504
+ const submitError = ref<string | null>(null)
505
+ const submitCount = ref(0)
506
+ const isSubmitSuccessful = ref(false)
507
+
508
+ // Get all values as object
509
+ const getValues = (): TValues => {
510
+ const values: Record<string, unknown> = {}
511
+ for (const [name, field] of Object.entries(fields)) {
512
+ values[name] = field.value.value
513
+ }
514
+ return values as TValues
515
+ }
516
+
517
+ // Get persistable values (excluding fields with persist: false)
518
+ const getPersistableValues = (): Record<string, unknown> => {
519
+ const values: Record<string, unknown> = {}
520
+ const configMap = fieldsConfig as Record<string, FieldConfig | undefined>
521
+ for (const [name, field] of Object.entries(fields)) {
522
+ const config = configMap[name]
523
+ if (config?.persist !== false) {
524
+ values[name] = field.value.value
525
+ }
526
+ }
527
+ return values
528
+ }
529
+
530
+ // Debounced save to storage
531
+ const debouncedSave = persist
532
+ ? debounce(() => {
533
+ saveToStorage(persist.key, getPersistableValues(), storage)
534
+ }, persistDebounce)
535
+ : () => {}
536
+
537
+ // Load persisted values
538
+ const loadPersistedValues = (): Record<string, unknown> | null => {
539
+ if (!persist) return null
540
+ return loadFromStorage<Record<string, unknown>>(persist.key, storage)
541
+ }
542
+
543
+ // Get field names from schema or fieldsConfig
544
+ const getFieldNames = (): string[] => {
545
+ if (schema?.shape) {
546
+ return Object.keys(schema.shape)
547
+ }
548
+ // Combine keys from fieldsConfig and initialValues
549
+ const keys = new Set([
550
+ ...Object.keys(fieldsConfig),
551
+ ...Object.keys(initialValues)
552
+ ])
553
+ return Array.from(keys)
554
+ }
555
+
556
+ // Initialize each field
557
+ const fieldNames = getFieldNames()
558
+ const persistedValues = loadPersistedValues()
559
+ const configMap = fieldsConfig as Record<string, FieldConfig | undefined>
560
+
561
+ for (const name of fieldNames) {
562
+ const config = configMap[name]
563
+
564
+ // Use persisted value, then initial value, then empty string
565
+ const persistedValue = persistedValues?.[name]
566
+ const fieldInitialValue = persistedValue !== undefined
567
+ ? persistedValue
568
+ : (initialValuesStore[name] ?? '')
569
+
570
+ const value = ref<unknown>(fieldInitialValue)
571
+ const error = ref('')
572
+ const touched = ref(false)
573
+ const dirty = ref(false)
574
+ const initialValue = ref<unknown>(initialValuesStore[name] ?? '')
575
+
576
+ const valid = computed(() => !error.value)
577
+
578
+ // Store dependencies for this field
579
+ if (config?.deps) {
580
+ fieldDependencies[name] = config.deps
581
+ }
582
+
583
+ const validate = async (): Promise<boolean> => {
584
+ const formData = getValues()
585
+
586
+ // Validate with Zod schema if provided
587
+ if (schema) {
588
+ const zodErrors = validateWithZod(schema, formData as Record<string, unknown>, name)
589
+ if (zodErrors[name]) {
590
+ error.value = zodErrors[name]
591
+ return false
592
+ }
593
+ }
594
+
595
+ // Validate with custom rules
596
+ const rules = config?.rules ?? []
597
+ for (const rule of rules) {
598
+ const result = await rule(value.value, formData as Record<string, unknown>)
599
+ if (result !== true) {
600
+ error.value = result
601
+ return false
602
+ }
603
+ }
604
+
605
+ error.value = ''
606
+ return true
607
+ }
608
+
609
+ // Create debounced validate for async operations
610
+ const fieldDebounce = config?.debounce ?? debounceDelay
611
+ const debouncedValidate = debounce(validate, fieldDebounce)
612
+
613
+ const reset = () => {
614
+ value.value = initialValue.value
615
+ error.value = ''
616
+ touched.value = false
617
+ dirty.value = false
618
+ }
619
+
620
+ // Determine validation trigger based on config and mode
621
+ const validateOn = config?.validateOn ?? (mode === 'onChange' ? 'input' : mode === 'onBlur' ? 'blur' : 'submit')
622
+
623
+ // Watch for changes to trigger validation
624
+ watch(value, async (newVal, oldVal) => {
625
+ if (newVal !== oldVal) {
626
+ dirty.value = newVal !== initialValue.value
627
+ }
628
+
629
+ // Save to storage if persistence is enabled
630
+ if (persist && config?.persist !== false) {
631
+ debouncedSave()
632
+ }
633
+
634
+ // Only validate if touched or dirty
635
+ if (touched.value || dirty.value) {
636
+ if (validateOn === 'input') {
637
+ // Check if any rule is async
638
+ const hasAsyncRules = (config?.rules ?? []).some(
639
+ rule => rule.constructor.name === 'AsyncFunction'
640
+ )
641
+ if (hasAsyncRules) {
642
+ debouncedValidate()
643
+ } else {
644
+ await validate()
645
+ }
646
+ }
647
+ }
648
+
649
+ // Trigger revalidation of dependent fields
650
+ for (const [depFieldName, deps] of Object.entries(fieldDependencies)) {
651
+ if (deps.includes(name) && fields[depFieldName]) {
652
+ const depField = fields[depFieldName]
653
+ if (depField.touched.value || depField.dirty.value) {
654
+ depField.validate()
655
+ }
656
+ }
657
+ }
658
+ })
659
+
660
+ fields[name] = {
661
+ value,
662
+ error,
663
+ touched,
664
+ dirty,
665
+ valid,
666
+ validate,
667
+ reset
668
+ }
669
+ }
670
+
671
+ // Computed: all errors
672
+ const errors = computed(() => {
673
+ const errs: Record<string, string> = {}
674
+ for (const [name, field] of Object.entries(fields)) {
675
+ errs[name] = field.error.value
676
+ }
677
+ return errs
678
+ })
679
+
680
+ // Computed: form is valid
681
+ const valid = computed(() => {
682
+ return Object.values(fields).every(field => field.valid.value)
683
+ })
684
+
685
+ // Computed: form is dirty (compared to initial values)
686
+ const dirty = computed(() => {
687
+ return Object.entries(fields).some(([name, field]) => {
688
+ const initial = initialValuesStore[name] ?? ''
689
+ return field.value.value !== initial
690
+ })
691
+ })
692
+
693
+ // Computed: form is touched
694
+ const touched = computed(() => {
695
+ return Object.values(fields).some(field => field.touched.value)
696
+ })
697
+
698
+ // Validate all fields
699
+ const validate = async (): Promise<boolean> => {
700
+ const results = await Promise.all(
701
+ Object.values(fields).map(field => field.validate())
702
+ )
703
+ return results.every(Boolean)
704
+ }
705
+
706
+ // Reset all fields (optionally with new values)
707
+ const reset = (values?: Partial<TValues>) => {
708
+ if (values) {
709
+ // Update initial values and reset to them
710
+ Object.assign(initialValuesStore, values)
711
+ for (const [name, val] of Object.entries(values)) {
712
+ if (fields[name]) {
713
+ (fields[name].value as Ref<unknown>).value = val
714
+ fields[name].error.value = ''
715
+ fields[name].touched.value = false
716
+ fields[name].dirty.value = false
717
+ }
718
+ }
719
+ } else {
720
+ Object.values(fields).forEach(field => field.reset())
721
+ }
722
+ submitError.value = null
723
+ isSubmitSuccessful.value = false
724
+ }
725
+
726
+ // Reset a single field
727
+ const resetField = (name: keyof TValues) => {
728
+ if (fields[name as string]) {
729
+ fields[name as string].reset()
730
+ }
731
+ }
732
+
733
+ // Set a single field value
734
+ const setValue = <K extends keyof TValues>(name: K, value: TValues[K]) => {
735
+ if (fields[name as string]) {
736
+ (fields[name as string].value as Ref<unknown>).value = value
737
+ }
738
+ }
739
+
740
+ // Set multiple field values
741
+ const setValues = (values: Partial<TValues>) => {
742
+ for (const [name, val] of Object.entries(values)) {
743
+ if (fields[name]) {
744
+ (fields[name].value as Ref<unknown>).value = val
745
+ }
746
+ }
747
+ }
748
+
749
+ // Get a single field value
750
+ const getFieldValue = <K extends keyof TValues>(name: K): TValues[K] => {
751
+ return fields[name as string]?.value.value as TValues[K]
752
+ }
753
+
754
+ // Set a field error manually
755
+ const setError = (name: keyof TValues, errorMsg: string) => {
756
+ if (fields[name as string]) {
757
+ fields[name as string].error.value = errorMsg
758
+ }
759
+ }
760
+
761
+ // Clear a field error
762
+ const clearError = (name: keyof TValues) => {
763
+ if (fields[name as string]) {
764
+ fields[name as string].error.value = ''
765
+ }
766
+ }
767
+
768
+ // Clear all errors
769
+ const clearErrors = () => {
770
+ Object.values(fields).forEach(field => {
771
+ field.error.value = ''
772
+ })
773
+ submitError.value = null
774
+ }
775
+
776
+ // Set field touched state
777
+ const setFieldTouched = (name: keyof TValues, touchedState = true) => {
778
+ if (fields[name as string]) {
779
+ fields[name as string].touched.value = touchedState
780
+ }
781
+ }
782
+
783
+ // Set field dirty state
784
+ const setFieldDirty = (name: keyof TValues, dirtyState = true) => {
785
+ if (fields[name as string]) {
786
+ fields[name as string].dirty.value = dirtyState
787
+ }
788
+ }
789
+
790
+ // Clear persisted data
791
+ const clearPersisted = () => {
792
+ if (persist) {
793
+ removeFromStorage(persist.key, storage)
794
+ }
795
+ }
796
+
797
+ // Handle form submission
798
+ const handleSubmit = <TResult = void>(
799
+ onSubmit: (values: TValues) => Promise<TResult> | TResult,
800
+ onError?: (error: unknown) => void
801
+ ): (() => Promise<TResult | undefined>) => {
802
+ return async () => {
803
+ submitCount.value++
804
+ isSubmitting.value = true
805
+ submitError.value = null
806
+ isSubmitSuccessful.value = false
807
+
808
+ try {
809
+ // Validate all fields first
810
+ const isValid = await validate()
811
+ if (!isValid) {
812
+ isSubmitting.value = false
813
+ return undefined
814
+ }
815
+
816
+ // Execute submit handler
817
+ const values = getValues()
818
+ const result = await onSubmit(values)
819
+ isSubmitSuccessful.value = true
820
+ return result
821
+ } catch (err) {
822
+ submitError.value = err instanceof Error ? err.message : String(err)
823
+ if (onError) {
824
+ onError(err)
825
+ }
826
+ return undefined
827
+ } finally {
828
+ isSubmitting.value = false
829
+ }
830
+ }
831
+ }
832
+
833
+ // Watch entire form for changes
834
+ const watchForm = (callback: (values: TValues) => void): WatchStopHandle => {
835
+ const fieldRefs = Object.values(fields).map(f => f.value)
836
+ return watch(
837
+ fieldRefs,
838
+ () => callback(getValues()),
839
+ { deep: true }
840
+ )
841
+ }
842
+
843
+ // Watch a specific field
844
+ const watchField = <K extends keyof TValues>(
845
+ name: K,
846
+ callback: (value: TValues[K], oldValue: TValues[K]) => void
847
+ ): WatchStopHandle => {
848
+ const field = fields[name as string]
849
+ if (!field) {
850
+ console.warn(`[useForm] Field "${String(name)}" not found`)
851
+ return () => {}
852
+ }
853
+ return watch(
854
+ field.value,
855
+ (newVal, oldVal) => callback(newVal as TValues[K], oldVal as TValues[K])
856
+ )
857
+ }
858
+
859
+ // Mount hook for persistence initialization
860
+ if (persist) {
861
+ onMounted(() => {
862
+ // Values are already loaded in field initialization
863
+ // This hook can be used for any additional setup
864
+ })
865
+ }
866
+
867
+ return {
868
+ fields: fields as TypedFields<TValues>,
869
+ errors,
870
+ valid,
871
+ dirty,
872
+ touched,
873
+ validate,
874
+ reset,
875
+ getValues,
876
+ // Submission state
877
+ isSubmitting,
878
+ submitError,
879
+ submitCount,
880
+ isSubmitSuccessful,
881
+ // Value management
882
+ setValue,
883
+ setValues,
884
+ getFieldValue,
885
+ // Error management
886
+ setError,
887
+ clearError,
888
+ clearErrors,
889
+ // Field state management
890
+ resetField,
891
+ setFieldTouched,
892
+ setFieldDirty,
893
+ // Submission
894
+ handleSubmit,
895
+ // Watchers
896
+ watchForm,
897
+ watchField,
898
+ // Persistence
899
+ clearPersisted
900
+ }
901
+ }
902
+
903
+ // ============================================================================
904
+ // Simple helper for quick field binding
905
+ // ============================================================================
906
+
907
+ export interface SimpleFieldBinding {
908
+ modelValue: unknown
909
+ error: string
910
+ 'onUpdate:modelValue': (value: unknown) => void
911
+ onBlur: () => void
912
+ }
913
+
914
+ /** Create bindings for a field to use with v-bind on SInput */
915
+ export function createFieldBindings(field: FieldState): SimpleFieldBinding {
916
+ return {
917
+ modelValue: field.value.value,
918
+ error: field.error.value,
919
+ 'onUpdate:modelValue': (value: unknown) => {
920
+ field.value.value = value
921
+ },
922
+ onBlur: () => {
923
+ field.touched.value = true
924
+ field.validate()
925
+ }
926
+ }
927
+ }
928
+
929
+ /**
930
+ * Helper to get field bindings for use with FormField scoped slots
931
+ * Returns reactive bindings that automatically sync with form validation
932
+ */
933
+ export function useFormFieldBindings(field: FieldState | null) {
934
+ if (!field) {
935
+ return {
936
+ modelValue: computed(() => undefined),
937
+ error: computed(() => ''),
938
+ 'onUpdate:modelValue': () => {},
939
+ onBlur: () => {},
940
+ onInput: () => {}
941
+ }
942
+ }
943
+
944
+ return {
945
+ modelValue: field.value,
946
+ error: field.error,
947
+ 'onUpdate:modelValue': (value: unknown) => {
948
+ field.value.value = value
949
+ },
950
+ onBlur: () => {
951
+ field.touched.value = true
952
+ field.validate()
953
+ },
954
+ onInput: () => {
955
+ // Validation happens automatically via watchers
956
+ }
957
+ }
958
+ }
959
+
960
+ export default useForm