@rangka/client 0.0.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 (1037) hide show
  1. package/.claude/skills/add-widget/SKILL.md +101 -0
  2. package/.turbo/turbo-build.log +29 -0
  3. package/CLAUDE.md +236 -0
  4. package/LICENSE +21 -0
  5. package/README.md +68 -0
  6. package/components.json +25 -0
  7. package/dist/App.d.ts +2 -0
  8. package/dist/App.d.ts.map +1 -0
  9. package/dist/App.js +24 -0
  10. package/dist/App.js.map +1 -0
  11. package/dist/api/auth.d.ts +12 -0
  12. package/dist/api/auth.d.ts.map +1 -0
  13. package/dist/api/auth.js +25 -0
  14. package/dist/api/auth.js.map +1 -0
  15. package/dist/api/boot.d.ts +3 -0
  16. package/dist/api/boot.d.ts.map +1 -0
  17. package/dist/api/boot.js +9 -0
  18. package/dist/api/boot.js.map +1 -0
  19. package/dist/api/client.d.ts +2 -0
  20. package/dist/api/client.d.ts.map +1 -0
  21. package/dist/api/client.js +22 -0
  22. package/dist/api/client.js.map +1 -0
  23. package/dist/api/paths.d.ts +2 -0
  24. package/dist/api/paths.d.ts.map +1 -0
  25. package/dist/api/paths.js +4 -0
  26. package/dist/api/paths.js.map +1 -0
  27. package/dist/api/token.d.ts +4 -0
  28. package/dist/api/token.d.ts.map +1 -0
  29. package/dist/api/token.js +11 -0
  30. package/dist/api/token.js.map +1 -0
  31. package/dist/auth/LoginForm.d.ts +11 -0
  32. package/dist/auth/LoginForm.d.ts.map +1 -0
  33. package/dist/auth/LoginForm.js +17 -0
  34. package/dist/auth/LoginForm.js.map +1 -0
  35. package/dist/auth/SessionExpired.d.ts +5 -0
  36. package/dist/auth/SessionExpired.d.ts.map +1 -0
  37. package/dist/auth/SessionExpired.js +7 -0
  38. package/dist/auth/SessionExpired.js.map +1 -0
  39. package/dist/auth/SetupForm.d.ts +11 -0
  40. package/dist/auth/SetupForm.d.ts.map +1 -0
  41. package/dist/auth/SetupForm.js +17 -0
  42. package/dist/auth/SetupForm.js.map +1 -0
  43. package/dist/boot/BootGate.d.ts +5 -0
  44. package/dist/boot/BootGate.d.ts.map +1 -0
  45. package/dist/boot/BootGate.js +25 -0
  46. package/dist/boot/BootGate.js.map +1 -0
  47. package/dist/boot/BootProvider.d.ts +17 -0
  48. package/dist/boot/BootProvider.d.ts.map +1 -0
  49. package/dist/boot/BootProvider.js +16 -0
  50. package/dist/boot/BootProvider.js.map +1 -0
  51. package/dist/boot/types.d.ts +18 -0
  52. package/dist/boot/types.d.ts.map +1 -0
  53. package/dist/boot/types.js +2 -0
  54. package/dist/boot/types.js.map +1 -0
  55. package/dist/boot/useBoot.d.ts +12 -0
  56. package/dist/boot/useBoot.d.ts.map +1 -0
  57. package/dist/boot/useBoot.js +79 -0
  58. package/dist/boot/useBoot.js.map +1 -0
  59. package/dist/components/Icon.d.ts +6 -0
  60. package/dist/components/Icon.d.ts.map +1 -0
  61. package/dist/components/Icon.js +13 -0
  62. package/dist/components/Icon.js.map +1 -0
  63. package/dist/components/ui/accordion.d.ts +8 -0
  64. package/dist/components/ui/accordion.d.ts.map +1 -0
  65. package/dist/components/ui/accordion.js +18 -0
  66. package/dist/components/ui/accordion.js.map +1 -0
  67. package/dist/components/ui/alert-dialog.d.ts +19 -0
  68. package/dist/components/ui/alert-dialog.d.ts.map +1 -0
  69. package/dist/components/ui/alert-dialog.js +42 -0
  70. package/dist/components/ui/alert-dialog.js.map +1 -0
  71. package/dist/components/ui/alert.d.ts +11 -0
  72. package/dist/components/ui/alert.d.ts.map +1 -0
  73. package/dist/components/ui/alert.js +28 -0
  74. package/dist/components/ui/alert.js.map +1 -0
  75. package/dist/components/ui/aspect-ratio.d.ts +4 -0
  76. package/dist/components/ui/aspect-ratio.d.ts.map +1 -0
  77. package/dist/components/ui/aspect-ratio.js +8 -0
  78. package/dist/components/ui/aspect-ratio.js.map +1 -0
  79. package/dist/components/ui/avatar.d.ts +12 -0
  80. package/dist/components/ui/avatar.d.ts.map +1 -0
  81. package/dist/components/ui/avatar.js +23 -0
  82. package/dist/components/ui/avatar.js.map +1 -0
  83. package/dist/components/ui/badge.d.ts +10 -0
  84. package/dist/components/ui/badge.d.ts.map +1 -0
  85. package/dist/components/ui/badge.js +25 -0
  86. package/dist/components/ui/badge.js.map +1 -0
  87. package/dist/components/ui/breadcrumb.d.ts +12 -0
  88. package/dist/components/ui/breadcrumb.d.ts.map +1 -0
  89. package/dist/components/ui/breadcrumb.js +28 -0
  90. package/dist/components/ui/breadcrumb.js.map +1 -0
  91. package/dist/components/ui/button-group.d.ts +12 -0
  92. package/dist/components/ui/button-group.d.ts.map +1 -0
  93. package/dist/components/ui/button-group.js +28 -0
  94. package/dist/components/ui/button-group.js.map +1 -0
  95. package/dist/components/ui/button.d.ts +11 -0
  96. package/dist/components/ui/button.d.ts.map +1 -0
  97. package/dist/components/ui/button.js +36 -0
  98. package/dist/components/ui/button.js.map +1 -0
  99. package/dist/components/ui/calendar.d.ts +11 -0
  100. package/dist/components/ui/calendar.d.ts.map +1 -0
  101. package/dist/components/ui/calendar.js +78 -0
  102. package/dist/components/ui/calendar.js.map +1 -0
  103. package/dist/components/ui/card.d.ts +12 -0
  104. package/dist/components/ui/card.d.ts.map +1 -0
  105. package/dist/components/ui/card.js +25 -0
  106. package/dist/components/ui/card.js.map +1 -0
  107. package/dist/components/ui/carousel.d.ts +29 -0
  108. package/dist/components/ui/carousel.d.ts.map +1 -0
  109. package/dist/components/ui/carousel.js +91 -0
  110. package/dist/components/ui/carousel.js.map +1 -0
  111. package/dist/components/ui/chart.d.ts +45 -0
  112. package/dist/components/ui/chart.d.ts.map +1 -0
  113. package/dist/components/ui/chart.js +119 -0
  114. package/dist/components/ui/chart.js.map +1 -0
  115. package/dist/components/ui/checkbox.d.ts +5 -0
  116. package/dist/components/ui/checkbox.d.ts.map +1 -0
  117. package/dist/components/ui/checkbox.js +9 -0
  118. package/dist/components/ui/checkbox.js.map +1 -0
  119. package/dist/components/ui/collapsible.d.ts +6 -0
  120. package/dist/components/ui/collapsible.d.ts.map +1 -0
  121. package/dist/components/ui/collapsible.js +14 -0
  122. package/dist/components/ui/collapsible.js.map +1 -0
  123. package/dist/components/ui/combobox.d.ts +25 -0
  124. package/dist/components/ui/combobox.d.ts.map +1 -0
  125. package/dist/components/ui/combobox.js +59 -0
  126. package/dist/components/ui/combobox.js.map +1 -0
  127. package/dist/components/ui/command.d.ts +19 -0
  128. package/dist/components/ui/command.d.ts.map +1 -0
  129. package/dist/components/ui/command.js +35 -0
  130. package/dist/components/ui/command.js.map +1 -0
  131. package/dist/components/ui/context-menu.d.ts +32 -0
  132. package/dist/components/ui/context-menu.d.ts.map +1 -0
  133. package/dist/components/ui/context-menu.js +51 -0
  134. package/dist/components/ui/context-menu.js.map +1 -0
  135. package/dist/components/ui/dialog.d.ts +18 -0
  136. package/dist/components/ui/dialog.d.ts.map +1 -0
  137. package/dist/components/ui/dialog.js +38 -0
  138. package/dist/components/ui/dialog.js.map +1 -0
  139. package/dist/components/ui/direction.d.ts +8 -0
  140. package/dist/components/ui/direction.d.ts.map +1 -0
  141. package/dist/components/ui/direction.js +9 -0
  142. package/dist/components/ui/direction.js.map +1 -0
  143. package/dist/components/ui/drawer.d.ts +14 -0
  144. package/dist/components/ui/drawer.d.ts.map +1 -0
  145. package/dist/components/ui/drawer.js +35 -0
  146. package/dist/components/ui/drawer.js.map +1 -0
  147. package/dist/components/ui/dropdown-menu.d.ts +30 -0
  148. package/dist/components/ui/dropdown-menu.d.ts.map +1 -0
  149. package/dist/components/ui/dropdown-menu.js +52 -0
  150. package/dist/components/ui/dropdown-menu.js.map +1 -0
  151. package/dist/components/ui/empty.d.ts +12 -0
  152. package/dist/components/ui/empty.d.ts.map +1 -0
  153. package/dist/components/ui/empty.js +34 -0
  154. package/dist/components/ui/empty.js.map +1 -0
  155. package/dist/components/ui/field.d.ts +25 -0
  156. package/dist/components/ui/field.d.ts.map +1 -0
  157. package/dist/components/ui/field.js +67 -0
  158. package/dist/components/ui/field.js.map +1 -0
  159. package/dist/components/ui/hover-card.d.ts +7 -0
  160. package/dist/components/ui/hover-card.d.ts.map +1 -0
  161. package/dist/components/ui/hover-card.js +14 -0
  162. package/dist/components/ui/hover-card.js.map +1 -0
  163. package/dist/components/ui/input-group.d.ts +17 -0
  164. package/dist/components/ui/input-group.d.ts.map +1 -0
  165. package/dist/components/ui/input-group.js +57 -0
  166. package/dist/components/ui/input-group.js.map +1 -0
  167. package/dist/components/ui/input-otp.d.ts +12 -0
  168. package/dist/components/ui/input-otp.d.ts.map +1 -0
  169. package/dist/components/ui/input-otp.js +22 -0
  170. package/dist/components/ui/input-otp.js.map +1 -0
  171. package/dist/components/ui/input.d.ts +4 -0
  172. package/dist/components/ui/input.d.ts.map +1 -0
  173. package/dist/components/ui/input.js +7 -0
  174. package/dist/components/ui/input.js.map +1 -0
  175. package/dist/components/ui/item.d.ts +24 -0
  176. package/dist/components/ui/item.d.ts.map +1 -0
  177. package/dist/components/ui/item.js +68 -0
  178. package/dist/components/ui/item.js.map +1 -0
  179. package/dist/components/ui/kbd.d.ts +4 -0
  180. package/dist/components/ui/kbd.d.ts.map +1 -0
  181. package/dist/components/ui/kbd.js +10 -0
  182. package/dist/components/ui/kbd.js.map +1 -0
  183. package/dist/components/ui/label.d.ts +5 -0
  184. package/dist/components/ui/label.d.ts.map +1 -0
  185. package/dist/components/ui/label.js +8 -0
  186. package/dist/components/ui/label.js.map +1 -0
  187. package/dist/components/ui/menubar.d.ts +31 -0
  188. package/dist/components/ui/menubar.d.ts.map +1 -0
  189. package/dist/components/ui/menubar.js +55 -0
  190. package/dist/components/ui/menubar.js.map +1 -0
  191. package/dist/components/ui/native-select.d.ts +9 -0
  192. package/dist/components/ui/native-select.d.ts.map +1 -0
  193. package/dist/components/ui/native-select.js +14 -0
  194. package/dist/components/ui/native-select.js.map +1 -0
  195. package/dist/components/ui/navigation-menu.d.ts +15 -0
  196. package/dist/components/ui/navigation-menu.d.ts.map +1 -0
  197. package/dist/components/ui/navigation-menu.js +32 -0
  198. package/dist/components/ui/navigation-menu.js.map +1 -0
  199. package/dist/components/ui/pagination.d.ts +18 -0
  200. package/dist/components/ui/pagination.d.ts.map +1 -0
  201. package/dist/components/ui/pagination.js +27 -0
  202. package/dist/components/ui/pagination.js.map +1 -0
  203. package/dist/components/ui/popover.d.ts +11 -0
  204. package/dist/components/ui/popover.d.ts.map +1 -0
  205. package/dist/components/ui/popover.js +26 -0
  206. package/dist/components/ui/popover.js.map +1 -0
  207. package/dist/components/ui/progress.d.ts +5 -0
  208. package/dist/components/ui/progress.d.ts.map +1 -0
  209. package/dist/components/ui/progress.js +9 -0
  210. package/dist/components/ui/progress.js.map +1 -0
  211. package/dist/components/ui/radio-group.d.ts +6 -0
  212. package/dist/components/ui/radio-group.d.ts.map +1 -0
  213. package/dist/components/ui/radio-group.js +11 -0
  214. package/dist/components/ui/radio-group.js.map +1 -0
  215. package/dist/components/ui/resizable.d.ts +8 -0
  216. package/dist/components/ui/resizable.d.ts.map +1 -0
  217. package/dist/components/ui/resizable.js +15 -0
  218. package/dist/components/ui/resizable.js.map +1 -0
  219. package/dist/components/ui/scroll-area.d.ts +6 -0
  220. package/dist/components/ui/scroll-area.d.ts.map +1 -0
  221. package/dist/components/ui/scroll-area.js +11 -0
  222. package/dist/components/ui/scroll-area.js.map +1 -0
  223. package/dist/components/ui/select.d.ts +16 -0
  224. package/dist/components/ui/select.d.ts.map +1 -0
  225. package/dist/components/ui/select.js +38 -0
  226. package/dist/components/ui/select.js.map +1 -0
  227. package/dist/components/ui/separator.d.ts +5 -0
  228. package/dist/components/ui/separator.d.ts.map +1 -0
  229. package/dist/components/ui/separator.js +8 -0
  230. package/dist/components/ui/separator.js.map +1 -0
  231. package/dist/components/ui/sheet.d.ts +15 -0
  232. package/dist/components/ui/sheet.d.ts.map +1 -0
  233. package/dist/components/ui/sheet.js +37 -0
  234. package/dist/components/ui/sheet.js.map +1 -0
  235. package/dist/components/ui/sidebar.d.ts +70 -0
  236. package/dist/components/ui/sidebar.d.ts.map +1 -0
  237. package/dist/components/ui/sidebar.js +205 -0
  238. package/dist/components/ui/sidebar.js.map +1 -0
  239. package/dist/components/ui/skeleton.d.ts +3 -0
  240. package/dist/components/ui/skeleton.d.ts.map +1 -0
  241. package/dist/components/ui/skeleton.js +7 -0
  242. package/dist/components/ui/skeleton.js.map +1 -0
  243. package/dist/components/ui/slider.d.ts +5 -0
  244. package/dist/components/ui/slider.d.ts.map +1 -0
  245. package/dist/components/ui/slider.js +11 -0
  246. package/dist/components/ui/slider.js.map +1 -0
  247. package/dist/components/ui/sonner.d.ts +4 -0
  248. package/dist/components/ui/sonner.d.ts.map +1 -0
  249. package/dist/components/ui/sonner.js +25 -0
  250. package/dist/components/ui/sonner.js.map +1 -0
  251. package/dist/components/ui/spinner.d.ts +3 -0
  252. package/dist/components/ui/spinner.d.ts.map +1 -0
  253. package/dist/components/ui/spinner.js +8 -0
  254. package/dist/components/ui/spinner.js.map +1 -0
  255. package/dist/components/ui/switch.d.ts +7 -0
  256. package/dist/components/ui/switch.d.ts.map +1 -0
  257. package/dist/components/ui/switch.js +9 -0
  258. package/dist/components/ui/switch.js.map +1 -0
  259. package/dist/components/ui/table.d.ts +11 -0
  260. package/dist/components/ui/table.d.ts.map +1 -0
  261. package/dist/components/ui/table.js +28 -0
  262. package/dist/components/ui/table.js.map +1 -0
  263. package/dist/components/ui/tabs.d.ts +12 -0
  264. package/dist/components/ui/tabs.d.ts.map +1 -0
  265. package/dist/components/ui/tabs.js +30 -0
  266. package/dist/components/ui/tabs.js.map +1 -0
  267. package/dist/components/ui/textarea.d.ts +4 -0
  268. package/dist/components/ui/textarea.d.ts.map +1 -0
  269. package/dist/components/ui/textarea.js +7 -0
  270. package/dist/components/ui/textarea.js.map +1 -0
  271. package/dist/components/ui/toggle-group.d.ts +11 -0
  272. package/dist/components/ui/toggle-group.d.ts.map +1 -0
  273. package/dist/components/ui/toggle-group.js +24 -0
  274. package/dist/components/ui/toggle-group.js.map +1 -0
  275. package/dist/components/ui/toggle.d.ts +10 -0
  276. package/dist/components/ui/toggle.d.ts.map +1 -0
  277. package/dist/components/ui/toggle.js +26 -0
  278. package/dist/components/ui/toggle.js.map +1 -0
  279. package/dist/components/ui/tooltip.d.ts +8 -0
  280. package/dist/components/ui/tooltip.d.ts.map +1 -0
  281. package/dist/components/ui/tooltip.js +18 -0
  282. package/dist/components/ui/tooltip.js.map +1 -0
  283. package/dist/context/MetaContext.d.ts +13 -0
  284. package/dist/context/MetaContext.d.ts.map +1 -0
  285. package/dist/context/MetaContext.js +14 -0
  286. package/dist/context/MetaContext.js.map +1 -0
  287. package/dist/context/ModuleContext.d.ts +14 -0
  288. package/dist/context/ModuleContext.d.ts.map +1 -0
  289. package/dist/context/ModuleContext.js +40 -0
  290. package/dist/context/ModuleContext.js.map +1 -0
  291. package/dist/context/PermissionsContext.d.ts +14 -0
  292. package/dist/context/PermissionsContext.d.ts.map +1 -0
  293. package/dist/context/PermissionsContext.js +20 -0
  294. package/dist/context/PermissionsContext.js.map +1 -0
  295. package/dist/context/ShellProviders.d.ts +7 -0
  296. package/dist/context/ShellProviders.d.ts.map +1 -0
  297. package/dist/context/ShellProviders.js +16 -0
  298. package/dist/context/ShellProviders.js.map +1 -0
  299. package/dist/context/UserContext.d.ts +8 -0
  300. package/dist/context/UserContext.d.ts.map +1 -0
  301. package/dist/context/UserContext.js +14 -0
  302. package/dist/context/UserContext.js.map +1 -0
  303. package/dist/data/QueryProvider.d.ts +7 -0
  304. package/dist/data/QueryProvider.d.ts.map +1 -0
  305. package/dist/data/QueryProvider.js +6 -0
  306. package/dist/data/QueryProvider.js.map +1 -0
  307. package/dist/data/queryClient.d.ts +3 -0
  308. package/dist/data/queryClient.d.ts.map +1 -0
  309. package/dist/data/queryClient.js +18 -0
  310. package/dist/data/queryClient.js.map +1 -0
  311. package/dist/data/useModelMeta.d.ts +6 -0
  312. package/dist/data/useModelMeta.d.ts.map +1 -0
  313. package/dist/data/useModelMeta.js +12 -0
  314. package/dist/data/useModelMeta.js.map +1 -0
  315. package/dist/data/useMutation.d.ts +8 -0
  316. package/dist/data/useMutation.d.ts.map +1 -0
  317. package/dist/data/useMutation.js +51 -0
  318. package/dist/data/useMutation.js.map +1 -0
  319. package/dist/data/useRecord.d.ts +6 -0
  320. package/dist/data/useRecord.d.ts.map +1 -0
  321. package/dist/data/useRecord.js +21 -0
  322. package/dist/data/useRecord.js.map +1 -0
  323. package/dist/data/useSource.d.ts +27 -0
  324. package/dist/data/useSource.d.ts.map +1 -0
  325. package/dist/data/useSource.js +82 -0
  326. package/dist/data/useSource.js.map +1 -0
  327. package/dist/hooks/use-mobile.d.ts +2 -0
  328. package/dist/hooks/use-mobile.d.ts.map +1 -0
  329. package/dist/hooks/use-mobile.js +16 -0
  330. package/dist/hooks/use-mobile.js.map +1 -0
  331. package/dist/index.d.ts +12 -0
  332. package/dist/index.d.ts.map +1 -0
  333. package/dist/index.js +12 -0
  334. package/dist/index.js.map +1 -0
  335. package/dist/lib/utils.d.ts +3 -0
  336. package/dist/lib/utils.d.ts.map +1 -0
  337. package/dist/lib/utils.js +6 -0
  338. package/dist/lib/utils.js.map +1 -0
  339. package/dist/main.d.ts +2 -0
  340. package/dist/main.d.ts.map +1 -0
  341. package/dist/main.js +12 -0
  342. package/dist/main.js.map +1 -0
  343. package/dist/router/NotFound.d.ts +2 -0
  344. package/dist/router/NotFound.d.ts.map +1 -0
  345. package/dist/router/NotFound.js +5 -0
  346. package/dist/router/NotFound.js.map +1 -0
  347. package/dist/router/RouterProvider.d.ts +5 -0
  348. package/dist/router/RouterProvider.d.ts.map +1 -0
  349. package/dist/router/RouterProvider.js +7 -0
  350. package/dist/router/RouterProvider.js.map +1 -0
  351. package/dist/router/buildRouteTree.d.ts +3 -0
  352. package/dist/router/buildRouteTree.d.ts.map +1 -0
  353. package/dist/router/buildRouteTree.js +43 -0
  354. package/dist/router/buildRouteTree.js.map +1 -0
  355. package/dist/router/createShellRouter.d.ts +4 -0
  356. package/dist/router/createShellRouter.d.ts.map +1 -0
  357. package/dist/router/createShellRouter.js +8 -0
  358. package/dist/router/createShellRouter.js.map +1 -0
  359. package/dist/router/hooks.d.ts +10 -0
  360. package/dist/router/hooks.d.ts.map +1 -0
  361. package/dist/router/hooks.js +31 -0
  362. package/dist/router/hooks.js.map +1 -0
  363. package/dist/shell/assets/index--35CAvcP.js +8715 -0
  364. package/dist/shell/assets/index-COLmoPYo.css +1 -0
  365. package/dist/shell/assets/index-Ck_eod9F.js +1 -0
  366. package/dist/shell/assets/inter-cyrillic-ext-wght-normal-BOeWTOD4.woff2 +0 -0
  367. package/dist/shell/assets/inter-cyrillic-wght-normal-DqGufNeO.woff2 +0 -0
  368. package/dist/shell/assets/inter-greek-ext-wght-normal-DlzME5K_.woff2 +0 -0
  369. package/dist/shell/assets/inter-greek-wght-normal-CkhJZR-_.woff2 +0 -0
  370. package/dist/shell/assets/inter-latin-ext-wght-normal-DO1Apj_S.woff2 +0 -0
  371. package/dist/shell/assets/inter-latin-wght-normal-Dx4kXJAl.woff2 +0 -0
  372. package/dist/shell/assets/inter-vietnamese-wght-normal-CBcvBZtf.woff2 +0 -0
  373. package/dist/shell/index.html +13 -0
  374. package/dist/studio/bridge.d.ts +2 -0
  375. package/dist/studio/bridge.d.ts.map +1 -0
  376. package/dist/studio/bridge.js +112 -0
  377. package/dist/studio/bridge.js.map +1 -0
  378. package/dist/studio/index.d.ts +2 -0
  379. package/dist/studio/index.d.ts.map +1 -0
  380. package/dist/studio/index.js +3 -0
  381. package/dist/studio/index.js.map +1 -0
  382. package/dist/studio/overlay.d.ts +11 -0
  383. package/dist/studio/overlay.d.ts.map +1 -0
  384. package/dist/studio/overlay.js +41 -0
  385. package/dist/studio/overlay.js.map +1 -0
  386. package/dist/studio/types.d.ts +42 -0
  387. package/dist/studio/types.d.ts.map +1 -0
  388. package/dist/studio/types.js +2 -0
  389. package/dist/studio/types.js.map +1 -0
  390. package/dist/studio/walker.d.ts +6 -0
  391. package/dist/studio/walker.d.ts.map +1 -0
  392. package/dist/studio/walker.js +43 -0
  393. package/dist/studio/walker.js.map +1 -0
  394. package/dist/widgets/action/dispatcher.d.ts +31 -0
  395. package/dist/widgets/action/dispatcher.d.ts.map +1 -0
  396. package/dist/widgets/action/dispatcher.js +224 -0
  397. package/dist/widgets/action/dispatcher.js.map +1 -0
  398. package/dist/widgets/action/index.d.ts +3 -0
  399. package/dist/widgets/action/index.d.ts.map +1 -0
  400. package/dist/widgets/action/index.js +2 -0
  401. package/dist/widgets/action/index.js.map +1 -0
  402. package/dist/widgets/binding/index.d.ts +3 -0
  403. package/dist/widgets/binding/index.d.ts.map +1 -0
  404. package/dist/widgets/binding/index.js +2 -0
  405. package/dist/widgets/binding/index.js.map +1 -0
  406. package/dist/widgets/binding/resolver.d.ts +23 -0
  407. package/dist/widgets/binding/resolver.d.ts.map +1 -0
  408. package/dist/widgets/binding/resolver.js +33 -0
  409. package/dist/widgets/binding/resolver.js.map +1 -0
  410. package/dist/widgets/components/AttachmentWidget.d.ts +29 -0
  411. package/dist/widgets/components/AttachmentWidget.d.ts.map +1 -0
  412. package/dist/widgets/components/AttachmentWidget.js +53 -0
  413. package/dist/widgets/components/AttachmentWidget.js.map +1 -0
  414. package/dist/widgets/components/AttachmentsWidget.d.ts +33 -0
  415. package/dist/widgets/components/AttachmentsWidget.d.ts.map +1 -0
  416. package/dist/widgets/components/AttachmentsWidget.js +52 -0
  417. package/dist/widgets/components/AttachmentsWidget.js.map +1 -0
  418. package/dist/widgets/components/BadgeWidget.d.ts +29 -0
  419. package/dist/widgets/components/BadgeWidget.d.ts.map +1 -0
  420. package/dist/widgets/components/BadgeWidget.js +31 -0
  421. package/dist/widgets/components/BadgeWidget.js.map +1 -0
  422. package/dist/widgets/components/ButtonWidget.d.ts +38 -0
  423. package/dist/widgets/components/ButtonWidget.d.ts.map +1 -0
  424. package/dist/widgets/components/ButtonWidget.js +29 -0
  425. package/dist/widgets/components/ButtonWidget.js.map +1 -0
  426. package/dist/widgets/components/CardWidget.d.ts +33 -0
  427. package/dist/widgets/components/CardWidget.d.ts.map +1 -0
  428. package/dist/widgets/components/CardWidget.js +28 -0
  429. package/dist/widgets/components/CardWidget.js.map +1 -0
  430. package/dist/widgets/components/CheckboxWidget.d.ts +23 -0
  431. package/dist/widgets/components/CheckboxWidget.d.ts.map +1 -0
  432. package/dist/widgets/components/CheckboxWidget.js +24 -0
  433. package/dist/widgets/components/CheckboxWidget.js.map +1 -0
  434. package/dist/widgets/components/CodeWidget.d.ts +30 -0
  435. package/dist/widgets/components/CodeWidget.d.ts.map +1 -0
  436. package/dist/widgets/components/CodeWidget.js +27 -0
  437. package/dist/widgets/components/CodeWidget.js.map +1 -0
  438. package/dist/widgets/components/ColumnWidget.d.ts +34 -0
  439. package/dist/widgets/components/ColumnWidget.d.ts.map +1 -0
  440. package/dist/widgets/components/ColumnWidget.js +20 -0
  441. package/dist/widgets/components/ColumnWidget.js.map +1 -0
  442. package/dist/widgets/components/ComputedWidget.d.ts +24 -0
  443. package/dist/widgets/components/ComputedWidget.d.ts.map +1 -0
  444. package/dist/widgets/components/ComputedWidget.js +39 -0
  445. package/dist/widgets/components/ComputedWidget.js.map +1 -0
  446. package/dist/widgets/components/DataWidget.d.ts +22 -0
  447. package/dist/widgets/components/DataWidget.d.ts.map +1 -0
  448. package/dist/widgets/components/DataWidget.js +99 -0
  449. package/dist/widgets/components/DataWidget.js.map +1 -0
  450. package/dist/widgets/components/DatePickerWidget.d.ts +23 -0
  451. package/dist/widgets/components/DatePickerWidget.d.ts.map +1 -0
  452. package/dist/widgets/components/DatePickerWidget.js +38 -0
  453. package/dist/widgets/components/DatePickerWidget.js.map +1 -0
  454. package/dist/widgets/components/DatetimeWidget.d.ts +23 -0
  455. package/dist/widgets/components/DatetimeWidget.d.ts.map +1 -0
  456. package/dist/widgets/components/DatetimeWidget.js +68 -0
  457. package/dist/widgets/components/DatetimeWidget.js.map +1 -0
  458. package/dist/widgets/components/DividerWidget.d.ts +24 -0
  459. package/dist/widgets/components/DividerWidget.d.ts.map +1 -0
  460. package/dist/widgets/components/DividerWidget.js +33 -0
  461. package/dist/widgets/components/DividerWidget.js.map +1 -0
  462. package/dist/widgets/components/DrawerWidget.d.ts +28 -0
  463. package/dist/widgets/components/DrawerWidget.d.ts.map +1 -0
  464. package/dist/widgets/components/DrawerWidget.js +36 -0
  465. package/dist/widgets/components/DrawerWidget.js.map +1 -0
  466. package/dist/widgets/components/DynamicLinkWidget.d.ts +29 -0
  467. package/dist/widgets/components/DynamicLinkWidget.d.ts.map +1 -0
  468. package/dist/widgets/components/DynamicLinkWidget.js +51 -0
  469. package/dist/widgets/components/DynamicLinkWidget.js.map +1 -0
  470. package/dist/widgets/components/GridWidget.d.ts +53 -0
  471. package/dist/widgets/components/GridWidget.d.ts.map +1 -0
  472. package/dist/widgets/components/GridWidget.js +111 -0
  473. package/dist/widgets/components/GridWidget.js.map +1 -0
  474. package/dist/widgets/components/GroupWidget.d.ts +50 -0
  475. package/dist/widgets/components/GroupWidget.d.ts.map +1 -0
  476. package/dist/widgets/components/GroupWidget.js +85 -0
  477. package/dist/widgets/components/GroupWidget.js.map +1 -0
  478. package/dist/widgets/components/IconWidget.d.ts +27 -0
  479. package/dist/widgets/components/IconWidget.d.ts.map +1 -0
  480. package/dist/widgets/components/IconWidget.js +22 -0
  481. package/dist/widgets/components/IconWidget.js.map +1 -0
  482. package/dist/widgets/components/ImageWidget.d.ts +28 -0
  483. package/dist/widgets/components/ImageWidget.d.ts.map +1 -0
  484. package/dist/widgets/components/ImageWidget.js +25 -0
  485. package/dist/widgets/components/ImageWidget.js.map +1 -0
  486. package/dist/widgets/components/InputWidget.d.ts +51 -0
  487. package/dist/widgets/components/InputWidget.d.ts.map +1 -0
  488. package/dist/widgets/components/InputWidget.js +46 -0
  489. package/dist/widgets/components/InputWidget.js.map +1 -0
  490. package/dist/widgets/components/JsonWidget.d.ts +27 -0
  491. package/dist/widgets/components/JsonWidget.d.ts.map +1 -0
  492. package/dist/widgets/components/JsonWidget.js +54 -0
  493. package/dist/widgets/components/JsonWidget.js.map +1 -0
  494. package/dist/widgets/components/LinkWidget.d.ts +26 -0
  495. package/dist/widgets/components/LinkWidget.d.ts.map +1 -0
  496. package/dist/widgets/components/LinkWidget.js +42 -0
  497. package/dist/widgets/components/LinkWidget.js.map +1 -0
  498. package/dist/widgets/components/ManyToManyWidget.d.ts +26 -0
  499. package/dist/widgets/components/ManyToManyWidget.d.ts.map +1 -0
  500. package/dist/widgets/components/ManyToManyWidget.js +54 -0
  501. package/dist/widgets/components/ManyToManyWidget.js.map +1 -0
  502. package/dist/widgets/components/ModalWidget.d.ts +28 -0
  503. package/dist/widgets/components/ModalWidget.d.ts.map +1 -0
  504. package/dist/widgets/components/ModalWidget.js +36 -0
  505. package/dist/widgets/components/ModalWidget.js.map +1 -0
  506. package/dist/widgets/components/MoneyWidget.d.ts +31 -0
  507. package/dist/widgets/components/MoneyWidget.d.ts.map +1 -0
  508. package/dist/widgets/components/MoneyWidget.js +52 -0
  509. package/dist/widgets/components/MoneyWidget.js.map +1 -0
  510. package/dist/widgets/components/RepeatWidget.d.ts +30 -0
  511. package/dist/widgets/components/RepeatWidget.d.ts.map +1 -0
  512. package/dist/widgets/components/RepeatWidget.js +50 -0
  513. package/dist/widgets/components/RepeatWidget.js.map +1 -0
  514. package/dist/widgets/components/ScrollAreaWidget.d.ts +27 -0
  515. package/dist/widgets/components/ScrollAreaWidget.d.ts.map +1 -0
  516. package/dist/widgets/components/ScrollAreaWidget.js +27 -0
  517. package/dist/widgets/components/ScrollAreaWidget.js.map +1 -0
  518. package/dist/widgets/components/SectionWidget.d.ts +36 -0
  519. package/dist/widgets/components/SectionWidget.d.ts.map +1 -0
  520. package/dist/widgets/components/SectionWidget.js +46 -0
  521. package/dist/widgets/components/SectionWidget.js.map +1 -0
  522. package/dist/widgets/components/SelectWidget.d.ts +30 -0
  523. package/dist/widgets/components/SelectWidget.d.ts.map +1 -0
  524. package/dist/widgets/components/SelectWidget.js +30 -0
  525. package/dist/widgets/components/SelectWidget.js.map +1 -0
  526. package/dist/widgets/components/SequenceWidget.d.ts +19 -0
  527. package/dist/widgets/components/SequenceWidget.d.ts.map +1 -0
  528. package/dist/widgets/components/SequenceWidget.js +20 -0
  529. package/dist/widgets/components/SequenceWidget.js.map +1 -0
  530. package/dist/widgets/components/SpacerWidget.d.ts +21 -0
  531. package/dist/widgets/components/SpacerWidget.d.ts.map +1 -0
  532. package/dist/widgets/components/SpacerWidget.js +25 -0
  533. package/dist/widgets/components/SpacerWidget.js.map +1 -0
  534. package/dist/widgets/components/SplitWidget.d.ts +32 -0
  535. package/dist/widgets/components/SplitWidget.d.ts.map +1 -0
  536. package/dist/widgets/components/SplitWidget.js +41 -0
  537. package/dist/widgets/components/SplitWidget.js.map +1 -0
  538. package/dist/widgets/components/StackWidget.d.ts +24 -0
  539. package/dist/widgets/components/StackWidget.d.ts.map +1 -0
  540. package/dist/widgets/components/StackWidget.js +29 -0
  541. package/dist/widgets/components/StackWidget.js.map +1 -0
  542. package/dist/widgets/components/TableWidget.d.ts +36 -0
  543. package/dist/widgets/components/TableWidget.d.ts.map +1 -0
  544. package/dist/widgets/components/TableWidget.js +154 -0
  545. package/dist/widgets/components/TableWidget.js.map +1 -0
  546. package/dist/widgets/components/TextWidget.d.ts +21 -0
  547. package/dist/widgets/components/TextWidget.d.ts.map +1 -0
  548. package/dist/widgets/components/TextWidget.js +38 -0
  549. package/dist/widgets/components/TextWidget.js.map +1 -0
  550. package/dist/widgets/components/TextareaWidget.d.ts +34 -0
  551. package/dist/widgets/components/TextareaWidget.d.ts.map +1 -0
  552. package/dist/widgets/components/TextareaWidget.js +31 -0
  553. package/dist/widgets/components/TextareaWidget.js.map +1 -0
  554. package/dist/widgets/components/TreeWidget.d.ts +26 -0
  555. package/dist/widgets/components/TreeWidget.d.ts.map +1 -0
  556. package/dist/widgets/components/TreeWidget.js +46 -0
  557. package/dist/widgets/components/TreeWidget.js.map +1 -0
  558. package/dist/widgets/components/index.d.ts +31 -0
  559. package/dist/widgets/components/index.d.ts.map +1 -0
  560. package/dist/widgets/components/index.js +31 -0
  561. package/dist/widgets/components/index.js.map +1 -0
  562. package/dist/widgets/components/register.d.ts +2 -0
  563. package/dist/widgets/components/register.d.ts.map +1 -0
  564. package/dist/widgets/components/register.js +85 -0
  565. package/dist/widgets/components/register.js.map +1 -0
  566. package/dist/widgets/components/table/CellRenderers.d.ts +11 -0
  567. package/dist/widgets/components/table/CellRenderers.d.ts.map +1 -0
  568. package/dist/widgets/components/table/CellRenderers.js +71 -0
  569. package/dist/widgets/components/table/CellRenderers.js.map +1 -0
  570. package/dist/widgets/components/table/TablePagination.d.ts +10 -0
  571. package/dist/widgets/components/table/TablePagination.d.ts.map +1 -0
  572. package/dist/widgets/components/table/TablePagination.js +11 -0
  573. package/dist/widgets/components/table/TablePagination.js.map +1 -0
  574. package/dist/widgets/components/table/TableToolbar.d.ts +17 -0
  575. package/dist/widgets/components/table/TableToolbar.d.ts.map +1 -0
  576. package/dist/widgets/components/table/TableToolbar.js +115 -0
  577. package/dist/widgets/components/table/TableToolbar.js.map +1 -0
  578. package/dist/widgets/components/table/filter-operators.d.ts +26 -0
  579. package/dist/widgets/components/table/filter-operators.d.ts.map +1 -0
  580. package/dist/widgets/components/table/filter-operators.js +111 -0
  581. package/dist/widgets/components/table/filter-operators.js.map +1 -0
  582. package/dist/widgets/components/table/index.d.ts +6 -0
  583. package/dist/widgets/components/table/index.d.ts.map +1 -0
  584. package/dist/widgets/components/table/index.js +5 -0
  585. package/dist/widgets/components/table/index.js.map +1 -0
  586. package/dist/widgets/condition/evaluator.d.ts +5 -0
  587. package/dist/widgets/condition/evaluator.d.ts.map +1 -0
  588. package/dist/widgets/condition/evaluator.js +49 -0
  589. package/dist/widgets/condition/evaluator.js.map +1 -0
  590. package/dist/widgets/condition/index.d.ts +2 -0
  591. package/dist/widgets/condition/index.d.ts.map +1 -0
  592. package/dist/widgets/condition/index.js +2 -0
  593. package/dist/widgets/condition/index.js.map +1 -0
  594. package/dist/widgets/context/builder.d.ts +7 -0
  595. package/dist/widgets/context/builder.d.ts.map +1 -0
  596. package/dist/widgets/context/builder.js +81 -0
  597. package/dist/widgets/context/builder.js.map +1 -0
  598. package/dist/widgets/context/index.d.ts +4 -0
  599. package/dist/widgets/context/index.d.ts.map +1 -0
  600. package/dist/widgets/context/index.js +3 -0
  601. package/dist/widgets/context/index.js.map +1 -0
  602. package/dist/widgets/context/types.d.ts +13 -0
  603. package/dist/widgets/context/types.d.ts.map +1 -0
  604. package/dist/widgets/context/types.js +21 -0
  605. package/dist/widgets/context/types.js.map +1 -0
  606. package/dist/widgets/data/index.d.ts +5 -0
  607. package/dist/widgets/data/index.d.ts.map +1 -0
  608. package/dist/widgets/data/index.js +3 -0
  609. package/dist/widgets/data/index.js.map +1 -0
  610. package/dist/widgets/data/useModelQuery.d.ts +21 -0
  611. package/dist/widgets/data/useModelQuery.d.ts.map +1 -0
  612. package/dist/widgets/data/useModelQuery.js +87 -0
  613. package/dist/widgets/data/useModelQuery.js.map +1 -0
  614. package/dist/widgets/data/useModelRecord.d.ts +12 -0
  615. package/dist/widgets/data/useModelRecord.d.ts.map +1 -0
  616. package/dist/widgets/data/useModelRecord.js +24 -0
  617. package/dist/widgets/data/useModelRecord.js.map +1 -0
  618. package/dist/widgets/expression/evaluator.d.ts +4 -0
  619. package/dist/widgets/expression/evaluator.d.ts.map +1 -0
  620. package/dist/widgets/expression/evaluator.js +84 -0
  621. package/dist/widgets/expression/evaluator.js.map +1 -0
  622. package/dist/widgets/expression/functions.d.ts +5 -0
  623. package/dist/widgets/expression/functions.d.ts.map +1 -0
  624. package/dist/widgets/expression/functions.js +128 -0
  625. package/dist/widgets/expression/functions.js.map +1 -0
  626. package/dist/widgets/expression/index.d.ts +6 -0
  627. package/dist/widgets/expression/index.d.ts.map +1 -0
  628. package/dist/widgets/expression/index.js +4 -0
  629. package/dist/widgets/expression/index.js.map +1 -0
  630. package/dist/widgets/expression/parser.d.ts +3 -0
  631. package/dist/widgets/expression/parser.d.ts.map +1 -0
  632. package/dist/widgets/expression/parser.js +202 -0
  633. package/dist/widgets/expression/parser.js.map +1 -0
  634. package/dist/widgets/expression/types.d.ts +27 -0
  635. package/dist/widgets/expression/types.d.ts.map +1 -0
  636. package/dist/widgets/expression/types.js +2 -0
  637. package/dist/widgets/expression/types.js.map +1 -0
  638. package/dist/widgets/form/FormContext.d.ts +20 -0
  639. package/dist/widgets/form/FormContext.d.ts.map +1 -0
  640. package/dist/widgets/form/FormContext.js +7 -0
  641. package/dist/widgets/form/FormContext.js.map +1 -0
  642. package/dist/widgets/form/FormProvider.d.ts +10 -0
  643. package/dist/widgets/form/FormProvider.d.ts.map +1 -0
  644. package/dist/widgets/form/FormProvider.js +64 -0
  645. package/dist/widgets/form/FormProvider.js.map +1 -0
  646. package/dist/widgets/form/FormWidget.d.ts +15 -0
  647. package/dist/widgets/form/FormWidget.d.ts.map +1 -0
  648. package/dist/widgets/form/FormWidget.js +28 -0
  649. package/dist/widgets/form/FormWidget.js.map +1 -0
  650. package/dist/widgets/form/index.d.ts +5 -0
  651. package/dist/widgets/form/index.d.ts.map +1 -0
  652. package/dist/widgets/form/index.js +4 -0
  653. package/dist/widgets/form/index.js.map +1 -0
  654. package/dist/widgets/form/useFormState.d.ts +22 -0
  655. package/dist/widgets/form/useFormState.d.ts.map +1 -0
  656. package/dist/widgets/form/useFormState.js +89 -0
  657. package/dist/widgets/form/useFormState.js.map +1 -0
  658. package/dist/widgets/form/useFormSubmit.d.ts +16 -0
  659. package/dist/widgets/form/useFormSubmit.d.ts.map +1 -0
  660. package/dist/widgets/form/useFormSubmit.js +66 -0
  661. package/dist/widgets/form/useFormSubmit.js.map +1 -0
  662. package/dist/widgets/form/useFormValidation.d.ts +6 -0
  663. package/dist/widgets/form/useFormValidation.d.ts.map +1 -0
  664. package/dist/widgets/form/useFormValidation.js +48 -0
  665. package/dist/widgets/form/useFormValidation.js.map +1 -0
  666. package/dist/widgets/hooks/index.d.ts +9 -0
  667. package/dist/widgets/hooks/index.d.ts.map +1 -0
  668. package/dist/widgets/hooks/index.js +8 -0
  669. package/dist/widgets/hooks/index.js.map +1 -0
  670. package/dist/widgets/hooks/useAction.d.ts +7 -0
  671. package/dist/widgets/hooks/useAction.d.ts.map +1 -0
  672. package/dist/widgets/hooks/useAction.js +73 -0
  673. package/dist/widgets/hooks/useAction.js.map +1 -0
  674. package/dist/widgets/hooks/useBind.d.ts +4 -0
  675. package/dist/widgets/hooks/useBind.d.ts.map +1 -0
  676. package/dist/widgets/hooks/useBind.js +27 -0
  677. package/dist/widgets/hooks/useBind.js.map +1 -0
  678. package/dist/widgets/hooks/useCondition.d.ts +3 -0
  679. package/dist/widgets/hooks/useCondition.d.ts.map +1 -0
  680. package/dist/widgets/hooks/useCondition.js +21 -0
  681. package/dist/widgets/hooks/useCondition.js.map +1 -0
  682. package/dist/widgets/hooks/useDataQuery.d.ts +19 -0
  683. package/dist/widgets/hooks/useDataQuery.d.ts.map +1 -0
  684. package/dist/widgets/hooks/useDataQuery.js +32 -0
  685. package/dist/widgets/hooks/useDataQuery.js.map +1 -0
  686. package/dist/widgets/hooks/useExpression.d.ts +2 -0
  687. package/dist/widgets/hooks/useExpression.d.ts.map +1 -0
  688. package/dist/widgets/hooks/useExpression.js +15 -0
  689. package/dist/widgets/hooks/useExpression.js.map +1 -0
  690. package/dist/widgets/hooks/usePageState.d.ts +5 -0
  691. package/dist/widgets/hooks/usePageState.d.ts.map +1 -0
  692. package/dist/widgets/hooks/usePageState.js +17 -0
  693. package/dist/widgets/hooks/usePageState.js.map +1 -0
  694. package/dist/widgets/hooks/useSurfaceContext.d.ts +5 -0
  695. package/dist/widgets/hooks/useSurfaceContext.d.ts.map +1 -0
  696. package/dist/widgets/hooks/useSurfaceContext.js +7 -0
  697. package/dist/widgets/hooks/useSurfaceContext.js.map +1 -0
  698. package/dist/widgets/hooks/useWidgetContext.d.ts +4 -0
  699. package/dist/widgets/hooks/useWidgetContext.d.ts.map +1 -0
  700. package/dist/widgets/hooks/useWidgetContext.js +11 -0
  701. package/dist/widgets/hooks/useWidgetContext.js.map +1 -0
  702. package/dist/widgets/index.d.ts +44 -0
  703. package/dist/widgets/index.d.ts.map +1 -0
  704. package/dist/widgets/index.js +31 -0
  705. package/dist/widgets/index.js.map +1 -0
  706. package/dist/widgets/lib/layout-props.d.ts +8 -0
  707. package/dist/widgets/lib/layout-props.d.ts.map +1 -0
  708. package/dist/widgets/lib/layout-props.js +134 -0
  709. package/dist/widgets/lib/layout-props.js.map +1 -0
  710. package/dist/widgets/reactivity/index.d.ts +5 -0
  711. package/dist/widgets/reactivity/index.d.ts.map +1 -0
  712. package/dist/widgets/reactivity/index.js +3 -0
  713. package/dist/widgets/reactivity/index.js.map +1 -0
  714. package/dist/widgets/reactivity/tracker.d.ts +8 -0
  715. package/dist/widgets/reactivity/tracker.d.ts.map +1 -0
  716. package/dist/widgets/reactivity/tracker.js +115 -0
  717. package/dist/widgets/reactivity/tracker.js.map +1 -0
  718. package/dist/widgets/reactivity/variables.d.ts +71 -0
  719. package/dist/widgets/reactivity/variables.d.ts.map +1 -0
  720. package/dist/widgets/reactivity/variables.js +175 -0
  721. package/dist/widgets/reactivity/variables.js.map +1 -0
  722. package/dist/widgets/registry.d.ts +14 -0
  723. package/dist/widgets/registry.d.ts.map +1 -0
  724. package/dist/widgets/registry.js +24 -0
  725. package/dist/widgets/registry.js.map +1 -0
  726. package/dist/widgets/renderer/SlotRenderer.d.ts +16 -0
  727. package/dist/widgets/renderer/SlotRenderer.d.ts.map +1 -0
  728. package/dist/widgets/renderer/SlotRenderer.js +13 -0
  729. package/dist/widgets/renderer/SlotRenderer.js.map +1 -0
  730. package/dist/widgets/renderer/WidgetRenderer.d.ts +12 -0
  731. package/dist/widgets/renderer/WidgetRenderer.d.ts.map +1 -0
  732. package/dist/widgets/renderer/WidgetRenderer.js +136 -0
  733. package/dist/widgets/renderer/WidgetRenderer.js.map +1 -0
  734. package/dist/widgets/renderer/index.d.ts +5 -0
  735. package/dist/widgets/renderer/index.d.ts.map +1 -0
  736. package/dist/widgets/renderer/index.js +3 -0
  737. package/dist/widgets/renderer/index.js.map +1 -0
  738. package/dist/widgets/shell/WidgetSlotRenderer.d.ts +14 -0
  739. package/dist/widgets/shell/WidgetSlotRenderer.d.ts.map +1 -0
  740. package/dist/widgets/shell/WidgetSlotRenderer.js +27 -0
  741. package/dist/widgets/shell/WidgetSlotRenderer.js.map +1 -0
  742. package/dist/widgets/shell/index.d.ts +5 -0
  743. package/dist/widgets/shell/index.d.ts.map +1 -0
  744. package/dist/widgets/shell/index.js +3 -0
  745. package/dist/widgets/shell/index.js.map +1 -0
  746. package/dist/widgets/shell/useActionHandlers.d.ts +7 -0
  747. package/dist/widgets/shell/useActionHandlers.d.ts.map +1 -0
  748. package/dist/widgets/shell/useActionHandlers.js +123 -0
  749. package/dist/widgets/shell/useActionHandlers.js.map +1 -0
  750. package/dist/widgets/state/index.d.ts +3 -0
  751. package/dist/widgets/state/index.d.ts.map +1 -0
  752. package/dist/widgets/state/index.js +2 -0
  753. package/dist/widgets/state/index.js.map +1 -0
  754. package/dist/widgets/state/store.d.ts +20 -0
  755. package/dist/widgets/state/store.d.ts.map +1 -0
  756. package/dist/widgets/state/store.js +84 -0
  757. package/dist/widgets/state/store.js.map +1 -0
  758. package/dist/widgets/types.d.ts +28 -0
  759. package/dist/widgets/types.d.ts.map +1 -0
  760. package/dist/widgets/types.js +2 -0
  761. package/dist/widgets/types.js.map +1 -0
  762. package/dist/widgets/validation/index.d.ts +3 -0
  763. package/dist/widgets/validation/index.d.ts.map +1 -0
  764. package/dist/widgets/validation/index.js +2 -0
  765. package/dist/widgets/validation/index.js.map +1 -0
  766. package/dist/widgets/validation/validator.d.ts +7 -0
  767. package/dist/widgets/validation/validator.d.ts.map +1 -0
  768. package/dist/widgets/validation/validator.js +125 -0
  769. package/dist/widgets/validation/validator.js.map +1 -0
  770. package/index.html +12 -0
  771. package/package.json +58 -0
  772. package/src/App.tsx +44 -0
  773. package/src/__tests__/setup.ts +1 -0
  774. package/src/api/auth.ts +41 -0
  775. package/src/api/boot.ts +10 -0
  776. package/src/api/client.ts +26 -0
  777. package/src/api/paths.ts +3 -0
  778. package/src/api/token.ts +13 -0
  779. package/src/auth/LoginForm.tsx +67 -0
  780. package/src/auth/SessionExpired.tsx +24 -0
  781. package/src/auth/SetupForm.tsx +76 -0
  782. package/src/boot/BootGate.tsx +35 -0
  783. package/src/boot/BootProvider.tsx +28 -0
  784. package/src/boot/types.ts +9 -0
  785. package/src/boot/useBoot.ts +111 -0
  786. package/src/components/Icon.tsx +17 -0
  787. package/src/components/ui/accordion.tsx +82 -0
  788. package/src/components/ui/alert-dialog.tsx +180 -0
  789. package/src/components/ui/alert.tsx +76 -0
  790. package/src/components/ui/aspect-ratio.tsx +9 -0
  791. package/src/components/ui/avatar.tsx +94 -0
  792. package/src/components/ui/badge.tsx +45 -0
  793. package/src/components/ui/breadcrumb.tsx +104 -0
  794. package/src/components/ui/button-group.tsx +78 -0
  795. package/src/components/ui/button.tsx +65 -0
  796. package/src/components/ui/calendar.tsx +187 -0
  797. package/src/components/ui/card.tsx +85 -0
  798. package/src/components/ui/carousel.tsx +229 -0
  799. package/src/components/ui/chart.tsx +339 -0
  800. package/src/components/ui/checkbox.tsx +27 -0
  801. package/src/components/ui/collapsible.tsx +21 -0
  802. package/src/components/ui/combobox.tsx +275 -0
  803. package/src/components/ui/command.tsx +178 -0
  804. package/src/components/ui/context-menu.tsx +242 -0
  805. package/src/components/ui/dialog.tsx +146 -0
  806. package/src/components/ui/direction.tsx +20 -0
  807. package/src/components/ui/drawer.tsx +118 -0
  808. package/src/components/ui/dropdown-menu.tsx +247 -0
  809. package/src/components/ui/empty.tsx +94 -0
  810. package/src/components/ui/field.tsx +224 -0
  811. package/src/components/ui/hover-card.tsx +36 -0
  812. package/src/components/ui/input-group.tsx +142 -0
  813. package/src/components/ui/input-otp.tsx +86 -0
  814. package/src/components/ui/input.tsx +19 -0
  815. package/src/components/ui/item.tsx +182 -0
  816. package/src/components/ui/kbd.tsx +26 -0
  817. package/src/components/ui/label.tsx +19 -0
  818. package/src/components/ui/menubar.tsx +260 -0
  819. package/src/components/ui/native-select.tsx +55 -0
  820. package/src/components/ui/navigation-menu.tsx +160 -0
  821. package/src/components/ui/pagination.tsx +112 -0
  822. package/src/components/ui/popover.tsx +74 -0
  823. package/src/components/ui/progress.tsx +31 -0
  824. package/src/components/ui/radio-group.tsx +42 -0
  825. package/src/components/ui/resizable.tsx +42 -0
  826. package/src/components/ui/scroll-area.tsx +53 -0
  827. package/src/components/ui/select.tsx +185 -0
  828. package/src/components/ui/separator.tsx +26 -0
  829. package/src/components/ui/sheet.tsx +128 -0
  830. package/src/components/ui/sidebar.tsx +669 -0
  831. package/src/components/ui/skeleton.tsx +13 -0
  832. package/src/components/ui/slider.tsx +54 -0
  833. package/src/components/ui/sonner.tsx +43 -0
  834. package/src/components/ui/spinner.tsx +16 -0
  835. package/src/components/ui/switch.tsx +33 -0
  836. package/src/components/ui/table.tsx +87 -0
  837. package/src/components/ui/tabs.tsx +80 -0
  838. package/src/components/ui/textarea.tsx +18 -0
  839. package/src/components/ui/toggle-group.tsx +86 -0
  840. package/src/components/ui/toggle.tsx +44 -0
  841. package/src/components/ui/tooltip.tsx +53 -0
  842. package/src/context/MetaContext.tsx +22 -0
  843. package/src/context/ModuleContext.tsx +62 -0
  844. package/src/context/PermissionsContext.tsx +39 -0
  845. package/src/context/ShellProviders.tsx +33 -0
  846. package/src/context/UserContext.tsx +16 -0
  847. package/src/data/QueryProvider.tsx +7 -0
  848. package/src/data/queryClient.ts +18 -0
  849. package/src/data/useModelMeta.ts +17 -0
  850. package/src/data/useMutation.ts +60 -0
  851. package/src/data/useRecord.ts +29 -0
  852. package/src/data/useSource.ts +112 -0
  853. package/src/hooks/use-mobile.ts +19 -0
  854. package/src/index.css +260 -0
  855. package/src/index.ts +16 -0
  856. package/src/lib/utils.ts +6 -0
  857. package/src/main.tsx +17 -0
  858. package/src/router/NotFound.tsx +8 -0
  859. package/src/router/RouterProvider.tsx +7 -0
  860. package/src/router/buildRouteTree.tsx +63 -0
  861. package/src/router/createShellRouter.ts +9 -0
  862. package/src/router/hooks.ts +43 -0
  863. package/src/shell/CommandPalette.tsx +76 -0
  864. package/src/shell/ConfirmDialog.tsx +34 -0
  865. package/src/shell/ConfirmProvider.tsx +56 -0
  866. package/src/shell/DrawerContext.tsx +44 -0
  867. package/src/shell/HeaderActions.tsx +31 -0
  868. package/src/shell/ModuleSelectorPage.tsx +149 -0
  869. package/src/shell/PageOutlet.tsx +21 -0
  870. package/src/shell/ShellContext.tsx +45 -0
  871. package/src/shell/ShellDevTools.tsx +153 -0
  872. package/src/shell/ShellLayout.tsx +231 -0
  873. package/src/shell/Toast.tsx +58 -0
  874. package/src/shell/ToastProvider.tsx +60 -0
  875. package/src/shell/app-sidebar/AppSidebar.tsx +44 -0
  876. package/src/shell/app-sidebar/ModuleSwitcher.tsx +87 -0
  877. package/src/shell/app-sidebar/NavMain.tsx +64 -0
  878. package/src/shell/app-sidebar/NavUser.tsx +97 -0
  879. package/src/shell/app-sidebar/SearchMenu.tsx +22 -0
  880. package/src/shell/app-sidebar/index.ts +8 -0
  881. package/src/shell/app-sidebar/types.ts +38 -0
  882. package/src/shell/types.ts +6 -0
  883. package/src/shell/useBreadcrumbs.ts +42 -0
  884. package/src/studio/bridge.ts +125 -0
  885. package/src/studio/index.ts +3 -0
  886. package/src/studio/overlay.ts +47 -0
  887. package/src/studio/types.ts +32 -0
  888. package/src/studio/walker.ts +48 -0
  889. package/src/vite-env.d.ts +1 -0
  890. package/src/widgets/__tests__/action-edge-cases.test.ts +281 -0
  891. package/src/widgets/__tests__/action.test.ts +236 -0
  892. package/src/widgets/__tests__/attachment-widget.test.tsx +85 -0
  893. package/src/widgets/__tests__/attachments-widget.test.tsx +109 -0
  894. package/src/widgets/__tests__/binding.test.ts +76 -0
  895. package/src/widgets/__tests__/button-widget.test.tsx +145 -0
  896. package/src/widgets/__tests__/checkbox-widget.test.tsx +158 -0
  897. package/src/widgets/__tests__/code-widget.test.tsx +64 -0
  898. package/src/widgets/__tests__/computed-widget.test.tsx +62 -0
  899. package/src/widgets/__tests__/condition-edge-cases.test.ts +120 -0
  900. package/src/widgets/__tests__/condition.test.ts +221 -0
  901. package/src/widgets/__tests__/context.test.ts +99 -0
  902. package/src/widgets/__tests__/data-widget.test.tsx +204 -0
  903. package/src/widgets/__tests__/datepicker-widget.test.tsx +66 -0
  904. package/src/widgets/__tests__/datetime-widget.test.tsx +67 -0
  905. package/src/widgets/__tests__/drawer-widget.test.tsx +149 -0
  906. package/src/widgets/__tests__/dynamic-link-widget.test.tsx +52 -0
  907. package/src/widgets/__tests__/edge-cases.test.ts +232 -0
  908. package/src/widgets/__tests__/evaluator.test.ts +107 -0
  909. package/src/widgets/__tests__/functions.test.ts +147 -0
  910. package/src/widgets/__tests__/grid-widget.test.tsx +137 -0
  911. package/src/widgets/__tests__/hooks.test.tsx +249 -0
  912. package/src/widgets/__tests__/icon-widget.test.tsx +129 -0
  913. package/src/widgets/__tests__/input-widget.test.tsx +264 -0
  914. package/src/widgets/__tests__/integration.test.ts +116 -0
  915. package/src/widgets/__tests__/json-widget.test.tsx +70 -0
  916. package/src/widgets/__tests__/link-widget.test.tsx +92 -0
  917. package/src/widgets/__tests__/many-to-many-widget.test.tsx +93 -0
  918. package/src/widgets/__tests__/modal-widget.test.tsx +148 -0
  919. package/src/widgets/__tests__/money-widget.test.tsx +97 -0
  920. package/src/widgets/__tests__/parser.test.ts +171 -0
  921. package/src/widgets/__tests__/reactive-variables.test.ts +383 -0
  922. package/src/widgets/__tests__/renderer.test.tsx +300 -0
  923. package/src/widgets/__tests__/repeat-widget.test.tsx +229 -0
  924. package/src/widgets/__tests__/select-widget.test.tsx +231 -0
  925. package/src/widgets/__tests__/sequence-widget.test.tsx +58 -0
  926. package/src/widgets/__tests__/shell-integration.test.tsx +1343 -0
  927. package/src/widgets/__tests__/split-widget.test.tsx +133 -0
  928. package/src/widgets/__tests__/state-edge-cases.test.ts +118 -0
  929. package/src/widgets/__tests__/state.test.ts +106 -0
  930. package/src/widgets/__tests__/table-data-binding.test.tsx +482 -0
  931. package/src/widgets/__tests__/table-filter-popover.test.tsx +486 -0
  932. package/src/widgets/__tests__/table-search.test.tsx +305 -0
  933. package/src/widgets/__tests__/table-widget.test.tsx +509 -0
  934. package/src/widgets/__tests__/textarea-widget.test.tsx +105 -0
  935. package/src/widgets/__tests__/tracker-validator-edge-cases.test.ts +242 -0
  936. package/src/widgets/__tests__/tracker.test.ts +133 -0
  937. package/src/widgets/__tests__/tree-widget.test.tsx +97 -0
  938. package/src/widgets/__tests__/use-model-source.test.ts +67 -0
  939. package/src/widgets/__tests__/validator.test.ts +208 -0
  940. package/src/widgets/action/dispatcher.ts +334 -0
  941. package/src/widgets/action/index.ts +2 -0
  942. package/src/widgets/binding/index.ts +2 -0
  943. package/src/widgets/binding/resolver.ts +61 -0
  944. package/src/widgets/components/AttachmentWidget.tsx +111 -0
  945. package/src/widgets/components/AttachmentsWidget.tsx +121 -0
  946. package/src/widgets/components/BadgeWidget.tsx +35 -0
  947. package/src/widgets/components/ButtonWidget.tsx +43 -0
  948. package/src/widgets/components/CardWidget.tsx +68 -0
  949. package/src/widgets/components/CheckboxWidget.tsx +39 -0
  950. package/src/widgets/components/CodeWidget.tsx +44 -0
  951. package/src/widgets/components/ColumnWidget.tsx +22 -0
  952. package/src/widgets/components/ComputedWidget.tsx +49 -0
  953. package/src/widgets/components/DataWidget.tsx +189 -0
  954. package/src/widgets/components/DatePickerWidget.tsx +73 -0
  955. package/src/widgets/components/DatetimeWidget.tsx +160 -0
  956. package/src/widgets/components/DividerWidget.tsx +37 -0
  957. package/src/widgets/components/DrawerWidget.tsx +52 -0
  958. package/src/widgets/components/DynamicLinkWidget.tsx +130 -0
  959. package/src/widgets/components/GridWidget.tsx +134 -0
  960. package/src/widgets/components/GroupWidget.tsx +111 -0
  961. package/src/widgets/components/IconWidget.tsx +29 -0
  962. package/src/widgets/components/ImageWidget.tsx +28 -0
  963. package/src/widgets/components/InputWidget.tsx +70 -0
  964. package/src/widgets/components/JsonWidget.tsx +78 -0
  965. package/src/widgets/components/LinkWidget.tsx +99 -0
  966. package/src/widgets/components/ManyToManyWidget.tsx +125 -0
  967. package/src/widgets/components/ModalWidget.tsx +52 -0
  968. package/src/widgets/components/MoneyWidget.tsx +80 -0
  969. package/src/widgets/components/RepeatWidget.tsx +66 -0
  970. package/src/widgets/components/ScrollAreaWidget.tsx +40 -0
  971. package/src/widgets/components/SectionWidget.tsx +78 -0
  972. package/src/widgets/components/SelectWidget.tsx +63 -0
  973. package/src/widgets/components/SequenceWidget.tsx +32 -0
  974. package/src/widgets/components/SpacerWidget.tsx +29 -0
  975. package/src/widgets/components/SplitWidget.tsx +60 -0
  976. package/src/widgets/components/StackWidget.tsx +44 -0
  977. package/src/widgets/components/TableWidget.tsx +366 -0
  978. package/src/widgets/components/TextWidget.tsx +44 -0
  979. package/src/widgets/components/TextareaWidget.tsx +49 -0
  980. package/src/widgets/components/TreeWidget.tsx +109 -0
  981. package/src/widgets/components/index.ts +30 -0
  982. package/src/widgets/components/register.ts +93 -0
  983. package/src/widgets/components/table/CellRenderers.tsx +83 -0
  984. package/src/widgets/components/table/TablePagination.tsx +45 -0
  985. package/src/widgets/components/table/TableToolbar.tsx +285 -0
  986. package/src/widgets/components/table/filter-operators.ts +134 -0
  987. package/src/widgets/components/table/index.ts +11 -0
  988. package/src/widgets/condition/evaluator.ts +57 -0
  989. package/src/widgets/condition/index.ts +1 -0
  990. package/src/widgets/context/builder.ts +99 -0
  991. package/src/widgets/context/index.ts +8 -0
  992. package/src/widgets/context/types.ts +37 -0
  993. package/src/widgets/data/index.ts +5 -0
  994. package/src/widgets/data/useModelQuery.ts +116 -0
  995. package/src/widgets/data/useModelRecord.ts +37 -0
  996. package/src/widgets/expression/evaluator.ts +100 -0
  997. package/src/widgets/expression/functions.ts +131 -0
  998. package/src/widgets/expression/index.ts +13 -0
  999. package/src/widgets/expression/parser.ts +229 -0
  1000. package/src/widgets/expression/types.ts +45 -0
  1001. package/src/widgets/form/FormContext.ts +29 -0
  1002. package/src/widgets/form/FormProvider.tsx +84 -0
  1003. package/src/widgets/form/FormWidget.tsx +42 -0
  1004. package/src/widgets/form/index.ts +4 -0
  1005. package/src/widgets/form/useFormState.ts +127 -0
  1006. package/src/widgets/form/useFormSubmit.ts +90 -0
  1007. package/src/widgets/form/useFormValidation.ts +62 -0
  1008. package/src/widgets/hooks/index.ts +8 -0
  1009. package/src/widgets/hooks/useAction.ts +83 -0
  1010. package/src/widgets/hooks/useBind.ts +34 -0
  1011. package/src/widgets/hooks/useCondition.ts +21 -0
  1012. package/src/widgets/hooks/useDataQuery.ts +48 -0
  1013. package/src/widgets/hooks/useExpression.ts +14 -0
  1014. package/src/widgets/hooks/usePageState.ts +21 -0
  1015. package/src/widgets/hooks/useSurfaceContext.ts +11 -0
  1016. package/src/widgets/hooks/useWidgetContext.ts +14 -0
  1017. package/src/widgets/index.ts +80 -0
  1018. package/src/widgets/lib/layout-props.ts +135 -0
  1019. package/src/widgets/reactivity/index.ts +11 -0
  1020. package/src/widgets/reactivity/tracker.ts +139 -0
  1021. package/src/widgets/reactivity/variables.ts +213 -0
  1022. package/src/widgets/registry.ts +41 -0
  1023. package/src/widgets/renderer/SlotRenderer.tsx +47 -0
  1024. package/src/widgets/renderer/WidgetRenderer.tsx +191 -0
  1025. package/src/widgets/renderer/index.ts +4 -0
  1026. package/src/widgets/shell/WidgetSlotRenderer.tsx +73 -0
  1027. package/src/widgets/shell/index.ts +4 -0
  1028. package/src/widgets/shell/useActionHandlers.ts +170 -0
  1029. package/src/widgets/state/index.ts +2 -0
  1030. package/src/widgets/state/store.ts +96 -0
  1031. package/src/widgets/types.ts +28 -0
  1032. package/src/widgets/validation/index.ts +2 -0
  1033. package/src/widgets/validation/validator.ts +140 -0
  1034. package/tsconfig.json +27 -0
  1035. package/tsconfig.tsbuildinfo +1 -0
  1036. package/vite.config.ts +21 -0
  1037. package/vitest.config.ts +16 -0
@@ -0,0 +1,1343 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
2
+ import React from 'react';
3
+ import { render, screen, fireEvent, waitFor, cleanup } from '@testing-library/react';
4
+ import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
5
+ import type { WidgetNode } from '@rangka/shared';
6
+ import { WidgetSlotRenderer } from '../shell/WidgetSlotRenderer.js';
7
+ import { ShellAPIProvider } from '../../shell/ShellContext.js';
8
+ import { DrawerProvider } from '../../shell/DrawerContext.js';
9
+ import { registerWidget, clearWidgetRegistry } from '../registry.js';
10
+ import type { WidgetProps } from '../types.js';
11
+
12
+ const mockNavigate = vi.fn();
13
+ vi.mock('../../router/hooks.js', () => ({
14
+ useNavigate: () => mockNavigate,
15
+ useParams: () => ({}),
16
+ useRoute: () => ({ pageKey: undefined, path: '/' }),
17
+ useSearchParams: () => [new URLSearchParams(), vi.fn()],
18
+ }));
19
+
20
+ // --- Realistic API Mock ---
21
+
22
+ let mockFetchResponses: Map<string, { status: number; body: unknown }>;
23
+ let fetchCalls: Array<{ url: string; method: string; body?: string }>;
24
+
25
+ function mockApiResponse(path: string, body: unknown, status = 200) {
26
+ mockFetchResponses.set(path, { status, body });
27
+ }
28
+
29
+ function mockFetch(input: RequestInfo | URL, init?: RequestInit): Promise<Response> {
30
+ const url = typeof input === 'string' ? input : input.toString();
31
+ const pathname = url.startsWith('http') ? new URL(url).pathname + new URL(url).search : url;
32
+ const method = init?.method ?? 'GET';
33
+ const key = `${method}:${pathname}`;
34
+
35
+ fetchCalls.push({ url: pathname, method, body: init?.body as string | undefined });
36
+
37
+ // Try exact method:path match first, then just path for GET
38
+ const match =
39
+ mockFetchResponses.get(key) ??
40
+ (method === 'GET' ? mockFetchResponses.get(pathname) : undefined);
41
+
42
+ if (!match) {
43
+ // Try prefix match for URLs with query params
44
+ for (const [mockKey, mockValue] of mockFetchResponses.entries()) {
45
+ const mockMethod = mockKey.includes(':') ? mockKey.split(':')[0] : 'GET';
46
+ const mockPath = mockKey.includes(':') ? mockKey.slice(mockKey.indexOf(':') + 1) : mockKey;
47
+ if (mockMethod === method && pathname.startsWith(mockPath.split('?')[0])) {
48
+ return Promise.resolve(
49
+ new Response(JSON.stringify(mockValue.body), {
50
+ status: mockValue.status,
51
+ headers: { 'Content-Type': 'application/json' },
52
+ }),
53
+ );
54
+ }
55
+ }
56
+
57
+ return Promise.resolve(new Response(JSON.stringify({ message: 'Not found' }), { status: 404 }));
58
+ }
59
+
60
+ return Promise.resolve(
61
+ new Response(JSON.stringify(match.body), {
62
+ status: match.status,
63
+ headers: { 'Content-Type': 'application/json' },
64
+ }),
65
+ );
66
+ }
67
+
68
+ // --- Test Widgets ---
69
+
70
+ function TestButton({ props, on }: WidgetProps) {
71
+ return (
72
+ <button data-testid="test-button" onClick={() => on?.click?.()}>
73
+ {String(props?.label ?? 'Button')}
74
+ </button>
75
+ );
76
+ }
77
+
78
+ function TestInput({ bind, on }: WidgetProps) {
79
+ return (
80
+ <input
81
+ data-testid="test-input"
82
+ value={String(bind?.value ?? '')}
83
+ onChange={(e) => {
84
+ bind?.setValue?.(e.target.value);
85
+ on?.change?.(e.target.value);
86
+ }}
87
+ />
88
+ );
89
+ }
90
+
91
+ function TestDisplay({ props, bind }: WidgetProps) {
92
+ return <span data-testid="test-display">{String(bind?.value ?? props?.text ?? '')}</span>;
93
+ }
94
+
95
+ function TestGroup({ children }: WidgetProps) {
96
+ return <div data-testid="test-group">{children}</div>;
97
+ }
98
+
99
+ // --- Test Wrapper ---
100
+
101
+ function createWrapper() {
102
+ const queryClient = new QueryClient({
103
+ defaultOptions: { queries: { retry: false } },
104
+ });
105
+
106
+ function Wrapper({ children }: { children: React.ReactNode }) {
107
+ return (
108
+ <QueryClientProvider client={queryClient}>
109
+ <ShellAPIProvider>
110
+ <DrawerProvider>{children}</DrawerProvider>
111
+ </ShellAPIProvider>
112
+ </QueryClientProvider>
113
+ );
114
+ }
115
+
116
+ return { Wrapper, queryClient, cleanup: () => {} };
117
+ }
118
+
119
+ // --- Setup/Teardown ---
120
+
121
+ beforeEach(() => {
122
+ mockFetchResponses = new Map();
123
+ fetchCalls = [];
124
+ vi.stubGlobal('fetch', mockFetch);
125
+ clearWidgetRegistry();
126
+
127
+ registerWidget(
128
+ {
129
+ name: 'test-button',
130
+ label: 'Test Button',
131
+ category: 'action',
132
+ schema: {},
133
+ binding: 'none',
134
+ triggers: ['click'],
135
+ container: false,
136
+ },
137
+ TestButton,
138
+ );
139
+ registerWidget(
140
+ {
141
+ name: 'test-input',
142
+ label: 'Test Input',
143
+ category: 'input',
144
+ schema: {},
145
+ binding: 'field',
146
+ triggers: ['change'],
147
+ container: false,
148
+ },
149
+ TestInput,
150
+ );
151
+ registerWidget(
152
+ {
153
+ name: 'test-display',
154
+ label: 'Test Display',
155
+ category: 'display',
156
+ schema: {},
157
+ binding: 'expression',
158
+ triggers: [],
159
+ container: false,
160
+ },
161
+ TestDisplay,
162
+ );
163
+ registerWidget(
164
+ {
165
+ name: 'test-group',
166
+ label: 'Test Group',
167
+ category: 'layout',
168
+ schema: {},
169
+ binding: 'none',
170
+ triggers: [],
171
+ container: true,
172
+ },
173
+ TestGroup,
174
+ );
175
+ });
176
+
177
+ afterEach(() => {
178
+ cleanup();
179
+ vi.restoreAllMocks();
180
+ clearWidgetRegistry();
181
+ });
182
+
183
+ // --- Tests ---
184
+
185
+ describe('WidgetSlotRenderer — shell integration', () => {
186
+ describe('basic rendering', () => {
187
+ it('renders widget nodes with record context', () => {
188
+ const { Wrapper } = createWrapper();
189
+ const nodes: WidgetNode[] = [{ type: 'test-display', bind: { expression: '{{name}}' } }];
190
+
191
+ render(
192
+ <Wrapper>
193
+ <WidgetSlotRenderer
194
+ nodes={nodes}
195
+ record={{ name: 'John Doe', id: '1' }}
196
+ model="contacts.contact"
197
+ />
198
+ </Wrapper>,
199
+ );
200
+
201
+ expect(screen.getByTestId('test-display')).toHaveTextContent('John Doe');
202
+ });
203
+
204
+ it('renders nested container with children', () => {
205
+ const { Wrapper } = createWrapper();
206
+ const nodes: WidgetNode[] = [
207
+ {
208
+ type: 'test-group',
209
+ children: [
210
+ { type: 'test-button', props: { label: 'Save' } },
211
+ { type: 'test-button', props: { label: 'Cancel' } },
212
+ ],
213
+ },
214
+ ];
215
+
216
+ render(
217
+ <Wrapper>
218
+ <WidgetSlotRenderer nodes={nodes} />
219
+ </Wrapper>,
220
+ );
221
+
222
+ expect(screen.getByTestId('test-group')).toBeInTheDocument();
223
+ expect(screen.getAllByTestId('test-button')).toHaveLength(2);
224
+ });
225
+
226
+ it('renders multiple slots independently', () => {
227
+ const { Wrapper } = createWrapper();
228
+ const nodes: WidgetNode[] = [
229
+ { type: 'test-display', props: { text: 'Slot A' } },
230
+ { type: 'test-display', props: { text: 'Slot B' } },
231
+ ];
232
+
233
+ render(
234
+ <Wrapper>
235
+ <WidgetSlotRenderer nodes={nodes} />
236
+ </Wrapper>,
237
+ );
238
+
239
+ const displays = screen.getAllByTestId('test-display');
240
+ expect(displays).toHaveLength(2);
241
+ expect(displays[0]).toHaveTextContent('Slot A');
242
+ expect(displays[1]).toHaveTextContent('Slot B');
243
+ });
244
+
245
+ it('renders empty nodes array without crashing', () => {
246
+ const { Wrapper } = createWrapper();
247
+ const { container } = render(
248
+ <Wrapper>
249
+ <WidgetSlotRenderer nodes={[]} />
250
+ </Wrapper>,
251
+ );
252
+ expect(container).toBeTruthy();
253
+ });
254
+ });
255
+
256
+ describe('record state management', () => {
257
+ it('updates record state when setValue is called via binding', async () => {
258
+ const onRecordChange = vi.fn();
259
+ const { Wrapper } = createWrapper();
260
+ const nodes: WidgetNode[] = [
261
+ { type: 'test-input', bind: { field: 'name' } },
262
+ { type: 'test-display', bind: { expression: '{{name}}' } },
263
+ ];
264
+
265
+ render(
266
+ <Wrapper>
267
+ <WidgetSlotRenderer
268
+ nodes={nodes}
269
+ record={{ name: 'initial' }}
270
+ model="contacts.contact"
271
+ onRecordChange={onRecordChange}
272
+ />
273
+ </Wrapper>,
274
+ );
275
+
276
+ const input = screen.getByTestId('test-input');
277
+ fireEvent.change(input, { target: { value: 'updated' } });
278
+
279
+ await waitFor(() => {
280
+ expect(onRecordChange).toHaveBeenCalledWith(expect.objectContaining({ name: 'updated' }));
281
+ });
282
+ });
283
+
284
+ it('handles rapid sequential setValue calls without losing state', async () => {
285
+ const onRecordChange = vi.fn();
286
+ const { Wrapper } = createWrapper();
287
+ const nodes: WidgetNode[] = [{ type: 'test-input', bind: { field: 'field1' } }];
288
+
289
+ render(
290
+ <Wrapper>
291
+ <WidgetSlotRenderer
292
+ nodes={nodes}
293
+ record={{ field1: '' }}
294
+ model="test.model"
295
+ onRecordChange={onRecordChange}
296
+ />
297
+ </Wrapper>,
298
+ );
299
+
300
+ const input = screen.getByTestId('test-input');
301
+ fireEvent.change(input, { target: { value: 'a' } });
302
+ fireEvent.change(input, { target: { value: 'ab' } });
303
+ fireEvent.change(input, { target: { value: 'abc' } });
304
+
305
+ await waitFor(() => {
306
+ expect(onRecordChange).toHaveBeenLastCalledWith(expect.objectContaining({ field1: 'abc' }));
307
+ });
308
+ });
309
+
310
+ it('preserves other record fields when updating one field', async () => {
311
+ const onRecordChange = vi.fn();
312
+ const { Wrapper } = createWrapper();
313
+ const nodes: WidgetNode[] = [{ type: 'test-input', bind: { field: 'name' } }];
314
+
315
+ render(
316
+ <Wrapper>
317
+ <WidgetSlotRenderer
318
+ nodes={nodes}
319
+ record={{ name: 'old', email: 'test@test.com', age: 25 }}
320
+ model="contacts.contact"
321
+ onRecordChange={onRecordChange}
322
+ />
323
+ </Wrapper>,
324
+ );
325
+
326
+ const input = screen.getByTestId('test-input');
327
+ fireEvent.change(input, { target: { value: 'new' } });
328
+
329
+ await waitFor(() => {
330
+ expect(onRecordChange).toHaveBeenCalledWith({
331
+ name: 'new',
332
+ email: 'test@test.com',
333
+ age: 25,
334
+ });
335
+ });
336
+ });
337
+ });
338
+
339
+ describe('action handlers — navigate', () => {
340
+ it('dispatches navigation via navigate action', async () => {
341
+ const { Wrapper } = createWrapper();
342
+ mockNavigate.mockClear();
343
+
344
+ const nodes: WidgetNode[] = [
345
+ {
346
+ type: 'test-button',
347
+ props: { label: 'Go' },
348
+ on: { click: { type: 'navigate', path: '/orders/123' } },
349
+ },
350
+ ];
351
+
352
+ render(
353
+ <Wrapper>
354
+ <WidgetSlotRenderer nodes={nodes} record={{ id: '123' }} model="sales.order" />
355
+ </Wrapper>,
356
+ );
357
+
358
+ fireEvent.click(screen.getByTestId('test-button'));
359
+
360
+ await waitFor(() => {
361
+ expect(mockNavigate).toHaveBeenCalledWith('/orders/123');
362
+ });
363
+ });
364
+
365
+ it('resolves expressions in navigate path', async () => {
366
+ const { Wrapper } = createWrapper();
367
+ mockNavigate.mockClear();
368
+
369
+ const nodes: WidgetNode[] = [
370
+ {
371
+ type: 'test-button',
372
+ props: { label: 'View' },
373
+ on: { click: { type: 'navigate', path: '/orders/{{id}}' } },
374
+ },
375
+ ];
376
+
377
+ render(
378
+ <Wrapper>
379
+ <WidgetSlotRenderer nodes={nodes} record={{ id: '456' }} model="sales.order" />
380
+ </Wrapper>,
381
+ );
382
+
383
+ fireEvent.click(screen.getByTestId('test-button'));
384
+
385
+ await waitFor(() => {
386
+ expect(mockNavigate).toHaveBeenCalledWith('/orders/456');
387
+ });
388
+ });
389
+ });
390
+
391
+ describe('action handlers — model CRUD', () => {
392
+ it('calls model.create with resolved data', async () => {
393
+ const { Wrapper } = createWrapper();
394
+ mockApiResponse('POST:/api/sales/order', { id: 'new-1', status: 'draft' }, 201);
395
+
396
+ const nodes: WidgetNode[] = [
397
+ {
398
+ type: 'test-button',
399
+ props: { label: 'Create' },
400
+ on: {
401
+ click: {
402
+ type: 'model.create',
403
+ model: 'sales.order',
404
+ data: { status: 'draft', customer: '{{customer_id}}' },
405
+ },
406
+ },
407
+ },
408
+ ];
409
+
410
+ render(
411
+ <Wrapper>
412
+ <WidgetSlotRenderer
413
+ nodes={nodes}
414
+ record={{ customer_id: 'cust-99' }}
415
+ model="sales.order"
416
+ />
417
+ </Wrapper>,
418
+ );
419
+
420
+ fireEvent.click(screen.getByTestId('test-button'));
421
+
422
+ await waitFor(() => {
423
+ const createCall = fetchCalls.find(
424
+ (c) => c.method === 'POST' && c.url.includes('/api/sales/order'),
425
+ );
426
+ expect(createCall).toBeDefined();
427
+ const body = JSON.parse(createCall!.body!);
428
+ expect(body.customer).toBe('cust-99');
429
+ expect(body.status).toBe('draft');
430
+ });
431
+ });
432
+
433
+ it('calls model.update with correct id and data', async () => {
434
+ const { Wrapper } = createWrapper();
435
+ mockApiResponse('PUT:/api/sales/order/order-1', { id: 'order-1', status: 'confirmed' });
436
+
437
+ const nodes: WidgetNode[] = [
438
+ {
439
+ type: 'test-button',
440
+ props: { label: 'Confirm' },
441
+ on: {
442
+ click: {
443
+ type: 'model.update',
444
+ model: 'sales.order',
445
+ id: '{{id}}',
446
+ data: { status: 'confirmed' },
447
+ },
448
+ },
449
+ },
450
+ ];
451
+
452
+ render(
453
+ <Wrapper>
454
+ <WidgetSlotRenderer
455
+ nodes={nodes}
456
+ record={{ id: 'order-1', status: 'draft' }}
457
+ model="sales.order"
458
+ />
459
+ </Wrapper>,
460
+ );
461
+
462
+ fireEvent.click(screen.getByTestId('test-button'));
463
+
464
+ await waitFor(() => {
465
+ const updateCall = fetchCalls.find(
466
+ (c) => c.method === 'PUT' && c.url.includes('/api/sales/order/order-1'),
467
+ );
468
+ expect(updateCall).toBeDefined();
469
+ });
470
+ });
471
+
472
+ it('calls model.delete and invalidates queries', async () => {
473
+ const { Wrapper, queryClient } = createWrapper();
474
+ mockApiResponse('DELETE:/api/hr/payslip/pay-1', null);
475
+ const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
476
+
477
+ const nodes: WidgetNode[] = [
478
+ {
479
+ type: 'test-button',
480
+ props: { label: 'Delete' },
481
+ on: {
482
+ click: {
483
+ type: 'model.delete',
484
+ model: 'hr.payslip',
485
+ id: '{{id}}',
486
+ },
487
+ },
488
+ },
489
+ ];
490
+
491
+ render(
492
+ <Wrapper>
493
+ <WidgetSlotRenderer nodes={nodes} record={{ id: 'pay-1' }} model="hr.payslip" />
494
+ </Wrapper>,
495
+ );
496
+
497
+ fireEvent.click(screen.getByTestId('test-button'));
498
+
499
+ await waitFor(() => {
500
+ expect(invalidateSpy).toHaveBeenCalledWith(
501
+ expect.objectContaining({ queryKey: ['model', 'hr.payslip'] }),
502
+ );
503
+ });
504
+ });
505
+
506
+ it('calls model.fetch and loads into record', async () => {
507
+ const { Wrapper } = createWrapper();
508
+ mockApiResponse('/api/sales/order/fetch-1', {
509
+ id: 'fetch-1',
510
+ total: 500,
511
+ status: 'confirmed',
512
+ });
513
+
514
+ const onRecordChange = vi.fn();
515
+ const nodes: WidgetNode[] = [
516
+ {
517
+ type: 'test-button',
518
+ props: { label: 'Load' },
519
+ on: {
520
+ click: {
521
+ type: 'model.fetch',
522
+ model: 'sales.order',
523
+ id: 'fetch-1',
524
+ into: '$record',
525
+ },
526
+ },
527
+ },
528
+ ];
529
+
530
+ render(
531
+ <Wrapper>
532
+ <WidgetSlotRenderer
533
+ nodes={nodes}
534
+ record={{}}
535
+ model="sales.order"
536
+ onRecordChange={onRecordChange}
537
+ />
538
+ </Wrapper>,
539
+ );
540
+
541
+ fireEvent.click(screen.getByTestId('test-button'));
542
+
543
+ await waitFor(() => {
544
+ expect(onRecordChange).toHaveBeenCalled();
545
+ });
546
+ });
547
+
548
+ it('calls model.list with filters', async () => {
549
+ const { Wrapper } = createWrapper();
550
+ mockApiResponse('GET:/api/sales/order', {
551
+ data: [
552
+ { id: '1', status: 'draft' },
553
+ { id: '2', status: 'draft' },
554
+ ],
555
+ });
556
+
557
+ const nodes: WidgetNode[] = [
558
+ {
559
+ type: 'test-button',
560
+ props: { label: 'List' },
561
+ on: {
562
+ click: {
563
+ type: 'model.list',
564
+ model: 'sales.order',
565
+ filters: { status: 'draft' },
566
+ into: '$state.orders',
567
+ },
568
+ },
569
+ },
570
+ ];
571
+
572
+ render(
573
+ <Wrapper>
574
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="sales.order" />
575
+ </Wrapper>,
576
+ );
577
+
578
+ fireEvent.click(screen.getByTestId('test-button'));
579
+
580
+ await waitFor(() => {
581
+ const listCall = fetchCalls.find(
582
+ (c) =>
583
+ c.method === 'GET' && c.url.includes('/api/sales/order') && c.url.includes('filter'),
584
+ );
585
+ expect(listCall).toBeDefined();
586
+ });
587
+ });
588
+
589
+ it('handles model.create failure gracefully', async () => {
590
+ const { Wrapper } = createWrapper();
591
+ mockApiResponse('POST:/api/sales/order', { message: 'Validation failed' }, 400);
592
+
593
+ const nodes: WidgetNode[] = [
594
+ {
595
+ type: 'test-button',
596
+ props: { label: 'Create' },
597
+ on: {
598
+ click: {
599
+ type: 'model.create',
600
+ model: 'sales.order',
601
+ data: { status: 'invalid' },
602
+ },
603
+ },
604
+ },
605
+ ];
606
+
607
+ // Should not throw — action errors are silently caught
608
+ render(
609
+ <Wrapper>
610
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="sales.order" />
611
+ </Wrapper>,
612
+ );
613
+
614
+ // Click should not crash the renderer
615
+ fireEvent.click(screen.getByTestId('test-button'));
616
+
617
+ await waitFor(() => {
618
+ expect(screen.getByTestId('test-button')).toBeInTheDocument();
619
+ });
620
+ });
621
+ });
622
+
623
+ describe('action handlers — service calls', () => {
624
+ it('calls service endpoint with resolved params', async () => {
625
+ const { Wrapper } = createWrapper();
626
+ mockApiResponse('POST:/api/services/approve-order', { approved: true });
627
+
628
+ const nodes: WidgetNode[] = [
629
+ {
630
+ type: 'test-button',
631
+ props: { label: 'Approve' },
632
+ on: {
633
+ click: {
634
+ type: 'service',
635
+ name: 'approve-order',
636
+ params: { orderId: '{{id}}', force: true },
637
+ },
638
+ },
639
+ },
640
+ ];
641
+
642
+ render(
643
+ <Wrapper>
644
+ <WidgetSlotRenderer nodes={nodes} record={{ id: 'ord-42' }} model="sales.order" />
645
+ </Wrapper>,
646
+ );
647
+
648
+ fireEvent.click(screen.getByTestId('test-button'));
649
+
650
+ await waitFor(() => {
651
+ const serviceCall = fetchCalls.find(
652
+ (c) => c.method === 'POST' && c.url.includes('/api/services/approve-order'),
653
+ );
654
+ expect(serviceCall).toBeDefined();
655
+ const body = JSON.parse(serviceCall!.body!);
656
+ expect(body.orderId).toBe('ord-42');
657
+ expect(body.force).toBe(true);
658
+ });
659
+ });
660
+
661
+ it('executes onSuccess action after successful service call', async () => {
662
+ const { Wrapper } = createWrapper();
663
+ mockApiResponse('POST:/api/services/calc-total', { total: 1500 });
664
+
665
+ const onRecordChange = vi.fn();
666
+ const nodes: WidgetNode[] = [
667
+ {
668
+ type: 'test-button',
669
+ props: { label: 'Calculate' },
670
+ on: {
671
+ click: {
672
+ type: 'service',
673
+ name: 'calc-total',
674
+ params: { items: '{{items}}' },
675
+ onSuccess: {
676
+ type: 'setValue',
677
+ field: 'total',
678
+ value: '{{$response.total}}',
679
+ },
680
+ },
681
+ },
682
+ },
683
+ ];
684
+
685
+ render(
686
+ <Wrapper>
687
+ <WidgetSlotRenderer
688
+ nodes={nodes}
689
+ record={{ items: [{ amount: 500 }, { amount: 1000 }], total: 0 }}
690
+ model="sales.order"
691
+ onRecordChange={onRecordChange}
692
+ />
693
+ </Wrapper>,
694
+ );
695
+
696
+ fireEvent.click(screen.getByTestId('test-button'));
697
+
698
+ await waitFor(() => {
699
+ expect(onRecordChange).toHaveBeenCalledWith(expect.objectContaining({ total: 1500 }));
700
+ });
701
+ });
702
+
703
+ it('executes onError action after failed service call', async () => {
704
+ const { Wrapper } = createWrapper();
705
+ mockApiResponse('POST:/api/services/risky-op', { message: 'Server error' }, 500);
706
+
707
+ const onRecordChange = vi.fn();
708
+ const nodes: WidgetNode[] = [
709
+ {
710
+ type: 'test-button',
711
+ props: { label: 'Risky' },
712
+ on: {
713
+ click: {
714
+ type: 'service',
715
+ name: 'risky-op',
716
+ params: {},
717
+ onError: {
718
+ type: 'setValue',
719
+ field: '$state.error',
720
+ value: '{{$response.message}}',
721
+ },
722
+ },
723
+ },
724
+ },
725
+ ];
726
+
727
+ render(
728
+ <Wrapper>
729
+ <WidgetSlotRenderer
730
+ nodes={nodes}
731
+ record={{}}
732
+ model="sales.order"
733
+ onRecordChange={onRecordChange}
734
+ />
735
+ </Wrapper>,
736
+ );
737
+
738
+ fireEvent.click(screen.getByTestId('test-button'));
739
+
740
+ // Should not crash
741
+ await waitFor(() => {
742
+ expect(screen.getByTestId('test-button')).toBeInTheDocument();
743
+ });
744
+ });
745
+ });
746
+
747
+ describe('action handlers — $state integration', () => {
748
+ it('setValue writes to $state and affects visibility', async () => {
749
+ const { Wrapper } = createWrapper();
750
+
751
+ const nodes: WidgetNode[] = [
752
+ {
753
+ type: 'test-button',
754
+ props: { label: 'Show Details' },
755
+ on: { click: { type: 'setValue', field: '$state.showDetails', value: true } },
756
+ },
757
+ {
758
+ type: 'test-display',
759
+ props: { text: 'Details visible' },
760
+ visible: { field: '$state.showDetails', operator: 'eq', value: true },
761
+ },
762
+ ];
763
+
764
+ render(
765
+ <Wrapper>
766
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="sales.order" />
767
+ </Wrapper>,
768
+ );
769
+
770
+ expect(screen.queryByText('Details visible')).not.toBeInTheDocument();
771
+
772
+ fireEvent.click(screen.getByTestId('test-button'));
773
+
774
+ await waitFor(() => {
775
+ expect(screen.getByText('Details visible')).toBeInTheDocument();
776
+ });
777
+ });
778
+
779
+ it('setValues updates multiple $state keys atomically', async () => {
780
+ const { Wrapper } = createWrapper();
781
+
782
+ const nodes: WidgetNode[] = [
783
+ {
784
+ type: 'test-button',
785
+ props: { label: 'Set Multiple' },
786
+ on: {
787
+ click: {
788
+ type: 'setValues',
789
+ values: {
790
+ '$state.step': 2,
791
+ '$state.loading': false,
792
+ },
793
+ },
794
+ },
795
+ },
796
+ {
797
+ type: 'test-display',
798
+ props: { text: 'Step 2' },
799
+ visible: { field: '$state.step', operator: 'eq', value: 2 },
800
+ },
801
+ ];
802
+
803
+ render(
804
+ <Wrapper>
805
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="sales.order" />
806
+ </Wrapper>,
807
+ );
808
+
809
+ expect(screen.queryByText('Step 2')).not.toBeInTheDocument();
810
+
811
+ fireEvent.click(screen.getByTestId('test-button'));
812
+
813
+ await waitFor(() => {
814
+ expect(screen.getByText('Step 2')).toBeInTheDocument();
815
+ });
816
+ });
817
+ });
818
+
819
+ describe('action handlers — sequence and conditional', () => {
820
+ it('executes sequence of actions in order', async () => {
821
+ const { Wrapper } = createWrapper();
822
+ mockApiResponse('POST:/api/services/save', { success: true });
823
+ mockNavigate.mockClear();
824
+
825
+ const nodes: WidgetNode[] = [
826
+ {
827
+ type: 'test-button',
828
+ props: { label: 'Save & Navigate' },
829
+ on: {
830
+ click: {
831
+ type: 'sequence',
832
+ actions: [
833
+ { type: 'setValue', field: '$state.saving', value: true },
834
+ { type: 'service', name: 'save', params: {} },
835
+ { type: 'setValue', field: '$state.saving', value: false },
836
+ { type: 'navigate', path: '/dashboard' },
837
+ ],
838
+ },
839
+ },
840
+ },
841
+ ];
842
+
843
+ render(
844
+ <Wrapper>
845
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="sales.order" />
846
+ </Wrapper>,
847
+ );
848
+
849
+ fireEvent.click(screen.getByTestId('test-button'));
850
+
851
+ await waitFor(() => {
852
+ expect(mockNavigate).toHaveBeenCalledWith('/dashboard');
853
+ });
854
+ });
855
+
856
+ it('executes conditional action — then branch', async () => {
857
+ const { Wrapper } = createWrapper();
858
+ const onRecordChange = vi.fn();
859
+
860
+ const nodes: WidgetNode[] = [
861
+ {
862
+ type: 'test-button',
863
+ props: { label: 'Check' },
864
+ on: {
865
+ click: {
866
+ type: 'conditional',
867
+ condition: { field: 'status', operator: 'eq', value: 'draft' },
868
+ then: { type: 'setValue', field: 'status', value: 'confirmed' },
869
+ else: { type: 'setValue', field: 'status', value: 'draft' },
870
+ },
871
+ },
872
+ },
873
+ ];
874
+
875
+ render(
876
+ <Wrapper>
877
+ <WidgetSlotRenderer
878
+ nodes={nodes}
879
+ record={{ status: 'draft' }}
880
+ model="sales.order"
881
+ onRecordChange={onRecordChange}
882
+ />
883
+ </Wrapper>,
884
+ );
885
+
886
+ fireEvent.click(screen.getByTestId('test-button'));
887
+
888
+ await waitFor(() => {
889
+ expect(onRecordChange).toHaveBeenCalledWith(
890
+ expect.objectContaining({ status: 'confirmed' }),
891
+ );
892
+ });
893
+ });
894
+
895
+ it('executes conditional action — else branch', async () => {
896
+ const { Wrapper } = createWrapper();
897
+ const onRecordChange = vi.fn();
898
+
899
+ const nodes: WidgetNode[] = [
900
+ {
901
+ type: 'test-button',
902
+ props: { label: 'Check' },
903
+ on: {
904
+ click: {
905
+ type: 'conditional',
906
+ condition: { field: 'status', operator: 'eq', value: 'draft' },
907
+ then: { type: 'setValue', field: 'status', value: 'confirmed' },
908
+ else: { type: 'setValue', field: 'status', value: 'reverted' },
909
+ },
910
+ },
911
+ },
912
+ ];
913
+
914
+ render(
915
+ <Wrapper>
916
+ <WidgetSlotRenderer
917
+ nodes={nodes}
918
+ record={{ status: 'confirmed' }}
919
+ model="sales.order"
920
+ onRecordChange={onRecordChange}
921
+ />
922
+ </Wrapper>,
923
+ );
924
+
925
+ fireEvent.click(screen.getByTestId('test-button'));
926
+
927
+ await waitFor(() => {
928
+ expect(onRecordChange).toHaveBeenCalledWith(
929
+ expect.objectContaining({ status: 'reverted' }),
930
+ );
931
+ });
932
+ });
933
+ });
934
+
935
+ describe('action handlers — refreshSource', () => {
936
+ it('invalidates model query when refreshSource fires', async () => {
937
+ const { Wrapper, queryClient } = createWrapper();
938
+ const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
939
+
940
+ const nodes: WidgetNode[] = [
941
+ {
942
+ type: 'test-button',
943
+ props: { label: 'Refresh' },
944
+ on: { click: { type: 'refreshSource' } },
945
+ },
946
+ ];
947
+
948
+ render(
949
+ <Wrapper>
950
+ <WidgetSlotRenderer
951
+ nodes={nodes}
952
+ record={{}}
953
+ model="sales.order"
954
+ sourceQueryKey={['model', 'sales.order', {}]}
955
+ />
956
+ </Wrapper>,
957
+ );
958
+
959
+ fireEvent.click(screen.getByTestId('test-button'));
960
+
961
+ await waitFor(() => {
962
+ expect(invalidateSpy).toHaveBeenCalledWith(
963
+ expect.objectContaining({ queryKey: ['model', 'sales.order', {}] }),
964
+ );
965
+ });
966
+ });
967
+
968
+ it('falls back to model key when no sourceQueryKey provided', async () => {
969
+ const { Wrapper, queryClient } = createWrapper();
970
+ const invalidateSpy = vi.spyOn(queryClient, 'invalidateQueries');
971
+
972
+ const nodes: WidgetNode[] = [
973
+ {
974
+ type: 'test-button',
975
+ props: { label: 'Refresh' },
976
+ on: { click: { type: 'refreshSource' } },
977
+ },
978
+ ];
979
+
980
+ render(
981
+ <Wrapper>
982
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="hr.payslip" />
983
+ </Wrapper>,
984
+ );
985
+
986
+ fireEvent.click(screen.getByTestId('test-button'));
987
+
988
+ await waitFor(() => {
989
+ expect(invalidateSpy).toHaveBeenCalledWith(
990
+ expect.objectContaining({ queryKey: ['model', 'hr.payslip'] }),
991
+ );
992
+ });
993
+ });
994
+ });
995
+
996
+ describe('expression resolution in context', () => {
997
+ it('resolves nested record field expressions', () => {
998
+ const { Wrapper } = createWrapper();
999
+ const nodes: WidgetNode[] = [
1000
+ {
1001
+ type: 'test-display',
1002
+ bind: { expression: '{{qty * rate}}' },
1003
+ },
1004
+ ];
1005
+
1006
+ render(
1007
+ <Wrapper>
1008
+ <WidgetSlotRenderer nodes={nodes} record={{ qty: 5, rate: 100 }} model="sales.line" />
1009
+ </Wrapper>,
1010
+ );
1011
+
1012
+ expect(screen.getByTestId('test-display')).toHaveTextContent('500');
1013
+ });
1014
+
1015
+ it('resolves template strings with multiple expressions', () => {
1016
+ const { Wrapper } = createWrapper();
1017
+ const nodes: WidgetNode[] = [
1018
+ {
1019
+ type: 'test-display',
1020
+ props: { text: '{{name}} - {{status}}' },
1021
+ },
1022
+ ];
1023
+
1024
+ render(
1025
+ <Wrapper>
1026
+ <WidgetSlotRenderer
1027
+ nodes={nodes}
1028
+ record={{ name: 'Order #1', status: 'draft' }}
1029
+ model="sales.order"
1030
+ />
1031
+ </Wrapper>,
1032
+ );
1033
+
1034
+ expect(screen.getByTestId('test-display')).toHaveTextContent('Order #1 - draft');
1035
+ });
1036
+
1037
+ it('handles null/undefined field gracefully in expressions', () => {
1038
+ const { Wrapper } = createWrapper();
1039
+ const nodes: WidgetNode[] = [
1040
+ {
1041
+ type: 'test-display',
1042
+ bind: { expression: '{{missing_field}}' },
1043
+ },
1044
+ ];
1045
+
1046
+ render(
1047
+ <Wrapper>
1048
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="sales.order" />
1049
+ </Wrapper>,
1050
+ );
1051
+
1052
+ // Should render without crashing, showing empty or null
1053
+ expect(screen.getByTestId('test-display')).toBeInTheDocument();
1054
+ });
1055
+ });
1056
+
1057
+ describe('conditional rendering', () => {
1058
+ it('hides widget when condition is not met', () => {
1059
+ const { Wrapper } = createWrapper();
1060
+ const nodes: WidgetNode[] = [
1061
+ {
1062
+ type: 'test-display',
1063
+ props: { text: 'Only for draft' },
1064
+ visible: { field: 'status', operator: 'eq', value: 'draft' },
1065
+ },
1066
+ ];
1067
+
1068
+ render(
1069
+ <Wrapper>
1070
+ <WidgetSlotRenderer nodes={nodes} record={{ status: 'confirmed' }} model="sales.order" />
1071
+ </Wrapper>,
1072
+ );
1073
+
1074
+ expect(screen.queryByText('Only for draft')).not.toBeInTheDocument();
1075
+ });
1076
+
1077
+ it('shows widget when condition is met', () => {
1078
+ const { Wrapper } = createWrapper();
1079
+ const nodes: WidgetNode[] = [
1080
+ {
1081
+ type: 'test-display',
1082
+ props: { text: 'Only for draft' },
1083
+ visible: { field: 'status', operator: 'eq', value: 'draft' },
1084
+ },
1085
+ ];
1086
+
1087
+ render(
1088
+ <Wrapper>
1089
+ <WidgetSlotRenderer nodes={nodes} record={{ status: 'draft' }} model="sales.order" />
1090
+ </Wrapper>,
1091
+ );
1092
+
1093
+ expect(screen.getByText('Only for draft')).toBeInTheDocument();
1094
+ });
1095
+
1096
+ it('evaluates multiple conditions (AND logic)', () => {
1097
+ const { Wrapper } = createWrapper();
1098
+ const nodes: WidgetNode[] = [
1099
+ {
1100
+ type: 'test-display',
1101
+ props: { text: 'Draft with total' },
1102
+ visible: [
1103
+ { field: 'status', operator: 'eq', value: 'draft' },
1104
+ { field: 'total', operator: 'gt', value: 0 },
1105
+ ],
1106
+ },
1107
+ ];
1108
+
1109
+ render(
1110
+ <Wrapper>
1111
+ <WidgetSlotRenderer
1112
+ nodes={nodes}
1113
+ record={{ status: 'draft', total: 0 }}
1114
+ model="sales.order"
1115
+ />
1116
+ </Wrapper>,
1117
+ );
1118
+
1119
+ expect(screen.queryByText('Draft with total')).not.toBeInTheDocument();
1120
+ });
1121
+
1122
+ it('shows widget when all AND conditions pass', () => {
1123
+ const { Wrapper } = createWrapper();
1124
+ const nodes: WidgetNode[] = [
1125
+ {
1126
+ type: 'test-display',
1127
+ props: { text: 'Draft with total' },
1128
+ visible: [
1129
+ { field: 'status', operator: 'eq', value: 'draft' },
1130
+ { field: 'total', operator: 'gt', value: 0 },
1131
+ ],
1132
+ },
1133
+ ];
1134
+
1135
+ render(
1136
+ <Wrapper>
1137
+ <WidgetSlotRenderer
1138
+ nodes={nodes}
1139
+ record={{ status: 'draft', total: 100 }}
1140
+ model="sales.order"
1141
+ />
1142
+ </Wrapper>,
1143
+ );
1144
+
1145
+ expect(screen.getByText('Draft with total')).toBeInTheDocument();
1146
+ });
1147
+ });
1148
+
1149
+ describe('edge cases and stress tests', () => {
1150
+ it('handles deeply nested widget tree (5 levels)', () => {
1151
+ const { Wrapper } = createWrapper();
1152
+
1153
+ const deepNode: WidgetNode = {
1154
+ type: 'test-group',
1155
+ children: [
1156
+ {
1157
+ type: 'test-group',
1158
+ children: [
1159
+ {
1160
+ type: 'test-group',
1161
+ children: [
1162
+ {
1163
+ type: 'test-group',
1164
+ children: [{ type: 'test-display', props: { text: 'deeply nested' } }],
1165
+ },
1166
+ ],
1167
+ },
1168
+ ],
1169
+ },
1170
+ ],
1171
+ };
1172
+
1173
+ render(
1174
+ <Wrapper>
1175
+ <WidgetSlotRenderer nodes={[deepNode]} record={{}} model="test.model" />
1176
+ </Wrapper>,
1177
+ );
1178
+
1179
+ expect(screen.getByText('deeply nested')).toBeInTheDocument();
1180
+ expect(screen.getAllByTestId('test-group')).toHaveLength(4);
1181
+ });
1182
+
1183
+ it('handles large number of nodes (50 widgets)', () => {
1184
+ const { Wrapper } = createWrapper();
1185
+ const nodes: WidgetNode[] = Array.from({ length: 50 }, (_, i) => ({
1186
+ type: 'test-display',
1187
+ props: { text: `item-${i}` },
1188
+ }));
1189
+
1190
+ render(
1191
+ <Wrapper>
1192
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="test.model" />
1193
+ </Wrapper>,
1194
+ );
1195
+
1196
+ expect(screen.getAllByTestId('test-display')).toHaveLength(50);
1197
+ expect(screen.getByText('item-0')).toBeInTheDocument();
1198
+ expect(screen.getByText('item-49')).toBeInTheDocument();
1199
+ });
1200
+
1201
+ it('handles unknown widget type gracefully', () => {
1202
+ const { Wrapper } = createWrapper();
1203
+ const nodes: WidgetNode[] = [
1204
+ { type: 'nonexistent-widget', props: { label: 'Ghost' } },
1205
+ { type: 'test-display', props: { text: 'still renders' } },
1206
+ ];
1207
+
1208
+ render(
1209
+ <Wrapper>
1210
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="test.model" />
1211
+ </Wrapper>,
1212
+ );
1213
+
1214
+ expect(screen.getByText('still renders')).toBeInTheDocument();
1215
+ });
1216
+
1217
+ it('handles concurrent actions without race conditions', async () => {
1218
+ const { Wrapper } = createWrapper();
1219
+ mockApiResponse('POST:/api/services/slow', { result: 'done' });
1220
+ mockApiResponse('POST:/api/services/fast', { result: 'quick' });
1221
+
1222
+ const nodes: WidgetNode[] = [
1223
+ {
1224
+ type: 'test-button',
1225
+ props: { label: 'Fire Both' },
1226
+ on: {
1227
+ click: {
1228
+ type: 'sequence',
1229
+ actions: [
1230
+ { type: 'service', name: 'slow', params: {} },
1231
+ { type: 'service', name: 'fast', params: {} },
1232
+ { type: 'setValue', field: '$state.done', value: true },
1233
+ ],
1234
+ },
1235
+ },
1236
+ },
1237
+ {
1238
+ type: 'test-display',
1239
+ props: { text: 'All done' },
1240
+ visible: { field: '$state.done', operator: 'eq', value: true },
1241
+ },
1242
+ ];
1243
+
1244
+ render(
1245
+ <Wrapper>
1246
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="test.model" />
1247
+ </Wrapper>,
1248
+ );
1249
+
1250
+ fireEvent.click(screen.getByTestId('test-button'));
1251
+
1252
+ await waitFor(() => {
1253
+ expect(screen.getByText('All done')).toBeInTheDocument();
1254
+ });
1255
+ });
1256
+
1257
+ it('handles model with no record gracefully', () => {
1258
+ const { Wrapper } = createWrapper();
1259
+ const nodes: WidgetNode[] = [{ type: 'test-input', bind: { field: 'name' } }];
1260
+
1261
+ render(
1262
+ <Wrapper>
1263
+ <WidgetSlotRenderer nodes={nodes} model="sales.order" />
1264
+ </Wrapper>,
1265
+ );
1266
+
1267
+ expect(screen.getByTestId('test-input')).toBeInTheDocument();
1268
+ });
1269
+
1270
+ it('handles widget with no props, no bind, no triggers', () => {
1271
+ const { Wrapper } = createWrapper();
1272
+ const nodes: WidgetNode[] = [{ type: 'test-group' }];
1273
+
1274
+ render(
1275
+ <Wrapper>
1276
+ <WidgetSlotRenderer nodes={nodes} record={{}} model="test.model" />
1277
+ </Wrapper>,
1278
+ );
1279
+
1280
+ expect(screen.getByTestId('test-group')).toBeInTheDocument();
1281
+ });
1282
+
1283
+ it('handles action dispatch when model is undefined', async () => {
1284
+ const { Wrapper } = createWrapper();
1285
+
1286
+ const nodes: WidgetNode[] = [
1287
+ {
1288
+ type: 'test-button',
1289
+ props: { label: 'Nav' },
1290
+ on: { click: { type: 'navigate', path: '/home' } },
1291
+ },
1292
+ ];
1293
+
1294
+ render(
1295
+ <Wrapper>
1296
+ <WidgetSlotRenderer nodes={nodes} record={{}} />
1297
+ </Wrapper>,
1298
+ );
1299
+
1300
+ // Should not throw
1301
+ fireEvent.click(screen.getByTestId('test-button'));
1302
+
1303
+ await waitFor(() => {
1304
+ expect(screen.getByTestId('test-button')).toBeInTheDocument();
1305
+ });
1306
+ });
1307
+
1308
+ it('handles expressions with special characters in field values', () => {
1309
+ const { Wrapper } = createWrapper();
1310
+ const nodes: WidgetNode[] = [
1311
+ {
1312
+ type: 'test-display',
1313
+ bind: { expression: '{{description}}' },
1314
+ },
1315
+ ];
1316
+
1317
+ render(
1318
+ <Wrapper>
1319
+ <WidgetSlotRenderer
1320
+ nodes={nodes}
1321
+ record={{ description: 'Has "quotes" & <tags>' }}
1322
+ model="test.model"
1323
+ />
1324
+ </Wrapper>,
1325
+ );
1326
+
1327
+ expect(screen.getByTestId('test-display')).toHaveTextContent('Has "quotes" & <tags>');
1328
+ });
1329
+
1330
+ it('handles numeric zero and boolean false as valid values', () => {
1331
+ const { Wrapper } = createWrapper();
1332
+ const nodes: WidgetNode[] = [{ type: 'test-display', bind: { expression: '{{count}}' } }];
1333
+
1334
+ render(
1335
+ <Wrapper>
1336
+ <WidgetSlotRenderer nodes={nodes} record={{ count: 0 }} model="test.model" />
1337
+ </Wrapper>,
1338
+ );
1339
+
1340
+ expect(screen.getByTestId('test-display')).toHaveTextContent('0');
1341
+ });
1342
+ });
1343
+ });