@rangka/client 0.1.1 → 0.1.3

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 (317) hide show
  1. package/dist/App.d.ts.map +1 -1
  2. package/dist/App.js +7 -1
  3. package/dist/App.js.map +1 -1
  4. package/dist/index.d.ts +2 -0
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +2 -0
  7. package/dist/index.js.map +1 -1
  8. package/dist/main.d.ts.map +1 -1
  9. package/dist/main.js +20 -0
  10. package/dist/main.js.map +1 -1
  11. package/dist/shell/assets/index-63v1sBS3.css +1 -0
  12. package/dist/shell/assets/index-Dh7K40cQ.js +8634 -0
  13. package/dist/shell/assets/vendor-query-B2cydN5j.js +1 -0
  14. package/dist/shell/assets/vendor-radix-BJxYPxPb.js +69 -0
  15. package/dist/shell/assets/vendor-router-ET_myMt5.js +17 -0
  16. package/dist/shell/index.html +5 -2
  17. package/dist/theme.css +82 -0
  18. package/dist/widgets/components/lazy-manifest.d.ts +11 -0
  19. package/dist/widgets/components/lazy-manifest.d.ts.map +1 -0
  20. package/dist/widgets/components/lazy-manifest.js +32 -0
  21. package/dist/widgets/components/lazy-manifest.js.map +1 -0
  22. package/dist/widgets/components/register.js +12 -12
  23. package/dist/widgets/components/register.js.map +1 -1
  24. package/dist/widgets/loader.d.ts +3 -0
  25. package/dist/widgets/loader.d.ts.map +1 -0
  26. package/dist/widgets/loader.js +73 -0
  27. package/dist/widgets/loader.js.map +1 -0
  28. package/dist/widgets/renderer/LazyWidget.d.ts +8 -0
  29. package/dist/widgets/renderer/LazyWidget.d.ts.map +1 -0
  30. package/dist/widgets/renderer/LazyWidget.js +31 -0
  31. package/dist/widgets/renderer/LazyWidget.js.map +1 -0
  32. package/dist/widgets/renderer/WidgetErrorBoundary.d.ts +17 -0
  33. package/dist/widgets/renderer/WidgetErrorBoundary.d.ts.map +1 -0
  34. package/dist/widgets/renderer/WidgetErrorBoundary.js +18 -0
  35. package/dist/widgets/renderer/WidgetErrorBoundary.js.map +1 -0
  36. package/dist/widgets/renderer/WidgetRenderer.d.ts.map +1 -1
  37. package/dist/widgets/renderer/WidgetRenderer.js +8 -6
  38. package/dist/widgets/renderer/WidgetRenderer.js.map +1 -1
  39. package/package.json +7 -4
  40. package/.claude/skills/add-widget/SKILL.md +0 -101
  41. package/.turbo/turbo-build.log +0 -29
  42. package/CHANGELOG.md +0 -25
  43. package/CLAUDE.md +0 -236
  44. package/components.json +0 -25
  45. package/dist/components/ui/chart.d.ts +0 -45
  46. package/dist/components/ui/chart.d.ts.map +0 -1
  47. package/dist/components/ui/chart.js +0 -119
  48. package/dist/components/ui/chart.js.map +0 -1
  49. package/dist/shell/assets/index--35CAvcP.js +0 -8715
  50. package/dist/shell/assets/index-COLmoPYo.css +0 -1
  51. package/index.html +0 -12
  52. package/src/App.tsx +0 -44
  53. package/src/__tests__/setup.ts +0 -1
  54. package/src/api/auth.ts +0 -41
  55. package/src/api/boot.ts +0 -10
  56. package/src/api/client.ts +0 -26
  57. package/src/api/paths.ts +0 -3
  58. package/src/api/token.ts +0 -13
  59. package/src/auth/LoginForm.tsx +0 -67
  60. package/src/auth/SessionExpired.tsx +0 -24
  61. package/src/auth/SetupForm.tsx +0 -76
  62. package/src/boot/BootGate.tsx +0 -35
  63. package/src/boot/BootProvider.tsx +0 -28
  64. package/src/boot/types.ts +0 -9
  65. package/src/boot/useBoot.ts +0 -111
  66. package/src/components/Icon.tsx +0 -17
  67. package/src/components/ui/accordion.tsx +0 -82
  68. package/src/components/ui/alert-dialog.tsx +0 -180
  69. package/src/components/ui/alert.tsx +0 -76
  70. package/src/components/ui/aspect-ratio.tsx +0 -9
  71. package/src/components/ui/avatar.tsx +0 -94
  72. package/src/components/ui/badge.tsx +0 -45
  73. package/src/components/ui/breadcrumb.tsx +0 -104
  74. package/src/components/ui/button-group.tsx +0 -78
  75. package/src/components/ui/button.tsx +0 -65
  76. package/src/components/ui/calendar.tsx +0 -187
  77. package/src/components/ui/card.tsx +0 -85
  78. package/src/components/ui/carousel.tsx +0 -229
  79. package/src/components/ui/chart.tsx +0 -339
  80. package/src/components/ui/checkbox.tsx +0 -27
  81. package/src/components/ui/collapsible.tsx +0 -21
  82. package/src/components/ui/combobox.tsx +0 -275
  83. package/src/components/ui/command.tsx +0 -178
  84. package/src/components/ui/context-menu.tsx +0 -242
  85. package/src/components/ui/dialog.tsx +0 -146
  86. package/src/components/ui/direction.tsx +0 -20
  87. package/src/components/ui/drawer.tsx +0 -118
  88. package/src/components/ui/dropdown-menu.tsx +0 -247
  89. package/src/components/ui/empty.tsx +0 -94
  90. package/src/components/ui/field.tsx +0 -224
  91. package/src/components/ui/hover-card.tsx +0 -36
  92. package/src/components/ui/input-group.tsx +0 -142
  93. package/src/components/ui/input-otp.tsx +0 -86
  94. package/src/components/ui/input.tsx +0 -19
  95. package/src/components/ui/item.tsx +0 -182
  96. package/src/components/ui/kbd.tsx +0 -26
  97. package/src/components/ui/label.tsx +0 -19
  98. package/src/components/ui/menubar.tsx +0 -260
  99. package/src/components/ui/native-select.tsx +0 -55
  100. package/src/components/ui/navigation-menu.tsx +0 -160
  101. package/src/components/ui/pagination.tsx +0 -112
  102. package/src/components/ui/popover.tsx +0 -74
  103. package/src/components/ui/progress.tsx +0 -31
  104. package/src/components/ui/radio-group.tsx +0 -42
  105. package/src/components/ui/resizable.tsx +0 -42
  106. package/src/components/ui/scroll-area.tsx +0 -53
  107. package/src/components/ui/select.tsx +0 -185
  108. package/src/components/ui/separator.tsx +0 -26
  109. package/src/components/ui/sheet.tsx +0 -128
  110. package/src/components/ui/sidebar.tsx +0 -669
  111. package/src/components/ui/skeleton.tsx +0 -13
  112. package/src/components/ui/slider.tsx +0 -54
  113. package/src/components/ui/sonner.tsx +0 -43
  114. package/src/components/ui/spinner.tsx +0 -16
  115. package/src/components/ui/switch.tsx +0 -33
  116. package/src/components/ui/table.tsx +0 -87
  117. package/src/components/ui/tabs.tsx +0 -80
  118. package/src/components/ui/textarea.tsx +0 -18
  119. package/src/components/ui/toggle-group.tsx +0 -86
  120. package/src/components/ui/toggle.tsx +0 -44
  121. package/src/components/ui/tooltip.tsx +0 -53
  122. package/src/context/MetaContext.tsx +0 -22
  123. package/src/context/ModuleContext.tsx +0 -62
  124. package/src/context/PermissionsContext.tsx +0 -39
  125. package/src/context/ShellProviders.tsx +0 -33
  126. package/src/context/UserContext.tsx +0 -16
  127. package/src/data/QueryProvider.tsx +0 -7
  128. package/src/data/queryClient.ts +0 -18
  129. package/src/data/useModelMeta.ts +0 -17
  130. package/src/data/useMutation.ts +0 -60
  131. package/src/data/useRecord.ts +0 -29
  132. package/src/data/useSource.ts +0 -112
  133. package/src/hooks/use-mobile.ts +0 -19
  134. package/src/index.css +0 -260
  135. package/src/index.ts +0 -16
  136. package/src/lib/utils.ts +0 -6
  137. package/src/main.tsx +0 -17
  138. package/src/router/NotFound.tsx +0 -8
  139. package/src/router/RouterProvider.tsx +0 -7
  140. package/src/router/buildRouteTree.tsx +0 -63
  141. package/src/router/createShellRouter.ts +0 -9
  142. package/src/router/hooks.ts +0 -43
  143. package/src/shell/CommandPalette.tsx +0 -76
  144. package/src/shell/ConfirmDialog.tsx +0 -34
  145. package/src/shell/ConfirmProvider.tsx +0 -56
  146. package/src/shell/DrawerContext.tsx +0 -44
  147. package/src/shell/HeaderActions.tsx +0 -31
  148. package/src/shell/ModuleSelectorPage.tsx +0 -149
  149. package/src/shell/PageOutlet.tsx +0 -21
  150. package/src/shell/ShellContext.tsx +0 -45
  151. package/src/shell/ShellDevTools.tsx +0 -153
  152. package/src/shell/ShellLayout.tsx +0 -231
  153. package/src/shell/Toast.tsx +0 -58
  154. package/src/shell/ToastProvider.tsx +0 -60
  155. package/src/shell/app-sidebar/AppSidebar.tsx +0 -44
  156. package/src/shell/app-sidebar/ModuleSwitcher.tsx +0 -87
  157. package/src/shell/app-sidebar/NavMain.tsx +0 -64
  158. package/src/shell/app-sidebar/NavUser.tsx +0 -97
  159. package/src/shell/app-sidebar/SearchMenu.tsx +0 -22
  160. package/src/shell/app-sidebar/index.ts +0 -8
  161. package/src/shell/app-sidebar/types.ts +0 -38
  162. package/src/shell/types.ts +0 -6
  163. package/src/shell/useBreadcrumbs.ts +0 -42
  164. package/src/studio/bridge.ts +0 -125
  165. package/src/studio/index.ts +0 -3
  166. package/src/studio/overlay.ts +0 -47
  167. package/src/studio/types.ts +0 -32
  168. package/src/studio/walker.ts +0 -48
  169. package/src/vite-env.d.ts +0 -1
  170. package/src/widgets/__tests__/action-edge-cases.test.ts +0 -281
  171. package/src/widgets/__tests__/action.test.ts +0 -236
  172. package/src/widgets/__tests__/attachment-widget.test.tsx +0 -85
  173. package/src/widgets/__tests__/attachments-widget.test.tsx +0 -109
  174. package/src/widgets/__tests__/binding.test.ts +0 -76
  175. package/src/widgets/__tests__/button-widget.test.tsx +0 -145
  176. package/src/widgets/__tests__/checkbox-widget.test.tsx +0 -158
  177. package/src/widgets/__tests__/code-widget.test.tsx +0 -64
  178. package/src/widgets/__tests__/computed-widget.test.tsx +0 -62
  179. package/src/widgets/__tests__/condition-edge-cases.test.ts +0 -120
  180. package/src/widgets/__tests__/condition.test.ts +0 -221
  181. package/src/widgets/__tests__/context.test.ts +0 -99
  182. package/src/widgets/__tests__/data-widget.test.tsx +0 -204
  183. package/src/widgets/__tests__/datepicker-widget.test.tsx +0 -66
  184. package/src/widgets/__tests__/datetime-widget.test.tsx +0 -67
  185. package/src/widgets/__tests__/drawer-widget.test.tsx +0 -149
  186. package/src/widgets/__tests__/dynamic-link-widget.test.tsx +0 -52
  187. package/src/widgets/__tests__/edge-cases.test.ts +0 -232
  188. package/src/widgets/__tests__/evaluator.test.ts +0 -107
  189. package/src/widgets/__tests__/functions.test.ts +0 -147
  190. package/src/widgets/__tests__/grid-widget.test.tsx +0 -137
  191. package/src/widgets/__tests__/hooks.test.tsx +0 -249
  192. package/src/widgets/__tests__/icon-widget.test.tsx +0 -129
  193. package/src/widgets/__tests__/input-widget.test.tsx +0 -264
  194. package/src/widgets/__tests__/integration.test.ts +0 -116
  195. package/src/widgets/__tests__/json-widget.test.tsx +0 -70
  196. package/src/widgets/__tests__/link-widget.test.tsx +0 -92
  197. package/src/widgets/__tests__/many-to-many-widget.test.tsx +0 -93
  198. package/src/widgets/__tests__/modal-widget.test.tsx +0 -148
  199. package/src/widgets/__tests__/money-widget.test.tsx +0 -97
  200. package/src/widgets/__tests__/parser.test.ts +0 -171
  201. package/src/widgets/__tests__/reactive-variables.test.ts +0 -383
  202. package/src/widgets/__tests__/renderer.test.tsx +0 -300
  203. package/src/widgets/__tests__/repeat-widget.test.tsx +0 -229
  204. package/src/widgets/__tests__/select-widget.test.tsx +0 -231
  205. package/src/widgets/__tests__/sequence-widget.test.tsx +0 -58
  206. package/src/widgets/__tests__/shell-integration.test.tsx +0 -1343
  207. package/src/widgets/__tests__/split-widget.test.tsx +0 -133
  208. package/src/widgets/__tests__/state-edge-cases.test.ts +0 -118
  209. package/src/widgets/__tests__/state.test.ts +0 -106
  210. package/src/widgets/__tests__/table-data-binding.test.tsx +0 -482
  211. package/src/widgets/__tests__/table-filter-popover.test.tsx +0 -486
  212. package/src/widgets/__tests__/table-search.test.tsx +0 -305
  213. package/src/widgets/__tests__/table-widget.test.tsx +0 -509
  214. package/src/widgets/__tests__/textarea-widget.test.tsx +0 -105
  215. package/src/widgets/__tests__/tracker-validator-edge-cases.test.ts +0 -242
  216. package/src/widgets/__tests__/tracker.test.ts +0 -133
  217. package/src/widgets/__tests__/tree-widget.test.tsx +0 -97
  218. package/src/widgets/__tests__/use-model-source.test.ts +0 -67
  219. package/src/widgets/__tests__/validator.test.ts +0 -208
  220. package/src/widgets/action/dispatcher.ts +0 -334
  221. package/src/widgets/action/index.ts +0 -2
  222. package/src/widgets/binding/index.ts +0 -2
  223. package/src/widgets/binding/resolver.ts +0 -61
  224. package/src/widgets/components/AttachmentWidget.tsx +0 -111
  225. package/src/widgets/components/AttachmentsWidget.tsx +0 -121
  226. package/src/widgets/components/BadgeWidget.tsx +0 -35
  227. package/src/widgets/components/ButtonWidget.tsx +0 -43
  228. package/src/widgets/components/CardWidget.tsx +0 -68
  229. package/src/widgets/components/CheckboxWidget.tsx +0 -39
  230. package/src/widgets/components/CodeWidget.tsx +0 -44
  231. package/src/widgets/components/ColumnWidget.tsx +0 -22
  232. package/src/widgets/components/ComputedWidget.tsx +0 -49
  233. package/src/widgets/components/DataWidget.tsx +0 -189
  234. package/src/widgets/components/DatePickerWidget.tsx +0 -73
  235. package/src/widgets/components/DatetimeWidget.tsx +0 -160
  236. package/src/widgets/components/DividerWidget.tsx +0 -37
  237. package/src/widgets/components/DrawerWidget.tsx +0 -52
  238. package/src/widgets/components/DynamicLinkWidget.tsx +0 -130
  239. package/src/widgets/components/GridWidget.tsx +0 -134
  240. package/src/widgets/components/GroupWidget.tsx +0 -111
  241. package/src/widgets/components/IconWidget.tsx +0 -29
  242. package/src/widgets/components/ImageWidget.tsx +0 -28
  243. package/src/widgets/components/InputWidget.tsx +0 -70
  244. package/src/widgets/components/JsonWidget.tsx +0 -78
  245. package/src/widgets/components/LinkWidget.tsx +0 -99
  246. package/src/widgets/components/ManyToManyWidget.tsx +0 -125
  247. package/src/widgets/components/ModalWidget.tsx +0 -52
  248. package/src/widgets/components/MoneyWidget.tsx +0 -80
  249. package/src/widgets/components/RepeatWidget.tsx +0 -66
  250. package/src/widgets/components/ScrollAreaWidget.tsx +0 -40
  251. package/src/widgets/components/SectionWidget.tsx +0 -78
  252. package/src/widgets/components/SelectWidget.tsx +0 -63
  253. package/src/widgets/components/SequenceWidget.tsx +0 -32
  254. package/src/widgets/components/SpacerWidget.tsx +0 -29
  255. package/src/widgets/components/SplitWidget.tsx +0 -60
  256. package/src/widgets/components/StackWidget.tsx +0 -44
  257. package/src/widgets/components/TableWidget.tsx +0 -366
  258. package/src/widgets/components/TextWidget.tsx +0 -44
  259. package/src/widgets/components/TextareaWidget.tsx +0 -49
  260. package/src/widgets/components/TreeWidget.tsx +0 -109
  261. package/src/widgets/components/index.ts +0 -30
  262. package/src/widgets/components/register.ts +0 -93
  263. package/src/widgets/components/table/CellRenderers.tsx +0 -83
  264. package/src/widgets/components/table/TablePagination.tsx +0 -45
  265. package/src/widgets/components/table/TableToolbar.tsx +0 -285
  266. package/src/widgets/components/table/filter-operators.ts +0 -134
  267. package/src/widgets/components/table/index.ts +0 -11
  268. package/src/widgets/condition/evaluator.ts +0 -57
  269. package/src/widgets/condition/index.ts +0 -1
  270. package/src/widgets/context/builder.ts +0 -99
  271. package/src/widgets/context/index.ts +0 -8
  272. package/src/widgets/context/types.ts +0 -37
  273. package/src/widgets/data/index.ts +0 -5
  274. package/src/widgets/data/useModelQuery.ts +0 -116
  275. package/src/widgets/data/useModelRecord.ts +0 -37
  276. package/src/widgets/expression/evaluator.ts +0 -100
  277. package/src/widgets/expression/functions.ts +0 -131
  278. package/src/widgets/expression/index.ts +0 -13
  279. package/src/widgets/expression/parser.ts +0 -229
  280. package/src/widgets/expression/types.ts +0 -45
  281. package/src/widgets/form/FormContext.ts +0 -29
  282. package/src/widgets/form/FormProvider.tsx +0 -84
  283. package/src/widgets/form/FormWidget.tsx +0 -42
  284. package/src/widgets/form/index.ts +0 -4
  285. package/src/widgets/form/useFormState.ts +0 -127
  286. package/src/widgets/form/useFormSubmit.ts +0 -90
  287. package/src/widgets/form/useFormValidation.ts +0 -62
  288. package/src/widgets/hooks/index.ts +0 -8
  289. package/src/widgets/hooks/useAction.ts +0 -83
  290. package/src/widgets/hooks/useBind.ts +0 -34
  291. package/src/widgets/hooks/useCondition.ts +0 -21
  292. package/src/widgets/hooks/useDataQuery.ts +0 -48
  293. package/src/widgets/hooks/useExpression.ts +0 -14
  294. package/src/widgets/hooks/usePageState.ts +0 -21
  295. package/src/widgets/hooks/useSurfaceContext.ts +0 -11
  296. package/src/widgets/hooks/useWidgetContext.ts +0 -14
  297. package/src/widgets/index.ts +0 -80
  298. package/src/widgets/lib/layout-props.ts +0 -135
  299. package/src/widgets/reactivity/index.ts +0 -11
  300. package/src/widgets/reactivity/tracker.ts +0 -139
  301. package/src/widgets/reactivity/variables.ts +0 -213
  302. package/src/widgets/registry.ts +0 -41
  303. package/src/widgets/renderer/SlotRenderer.tsx +0 -47
  304. package/src/widgets/renderer/WidgetRenderer.tsx +0 -191
  305. package/src/widgets/renderer/index.ts +0 -4
  306. package/src/widgets/shell/WidgetSlotRenderer.tsx +0 -73
  307. package/src/widgets/shell/index.ts +0 -4
  308. package/src/widgets/shell/useActionHandlers.ts +0 -170
  309. package/src/widgets/state/index.ts +0 -2
  310. package/src/widgets/state/store.ts +0 -96
  311. package/src/widgets/types.ts +0 -28
  312. package/src/widgets/validation/index.ts +0 -2
  313. package/src/widgets/validation/validator.ts +0 -140
  314. package/tsconfig.json +0 -27
  315. package/tsconfig.tsbuildinfo +0 -1
  316. package/vite.config.ts +0 -21
  317. package/vitest.config.ts +0 -16
@@ -1,229 +0,0 @@
1
- import type { AstNode, BinaryOperator } from './types.js';
2
-
3
- const BINARY_OPS: Record<string, { prec: number; op: BinaryOperator }> = {
4
- '||': { prec: 1, op: '||' },
5
- '&&': { prec: 2, op: '&&' },
6
- '==': { prec: 3, op: '==' },
7
- '!=': { prec: 3, op: '!=' },
8
- '>': { prec: 4, op: '>' },
9
- '<': { prec: 4, op: '<' },
10
- '>=': { prec: 4, op: '>=' },
11
- '<=': { prec: 4, op: '<=' },
12
- '+': { prec: 5, op: '+' },
13
- '-': { prec: 5, op: '-' },
14
- '*': { prec: 6, op: '*' },
15
- '/': { prec: 6, op: '/' },
16
- '%': { prec: 6, op: '%' },
17
- };
18
-
19
- export function parse(input: string): AstNode {
20
- const trimmed = input.trim();
21
- if (trimmed.startsWith('{{') && trimmed.endsWith('}}')) {
22
- input = trimmed.slice(2, -2).trim();
23
- }
24
- const parser = new Parser(input);
25
- const ast = parser.parseExpression(0);
26
- parser.skipWhitespace();
27
- if (parser.pos < parser.input.length) {
28
- throw new Error(
29
- `Unexpected character: '${parser.input[parser.pos]}' at position ${parser.pos}`,
30
- );
31
- }
32
- return ast;
33
- }
34
-
35
- class Parser {
36
- input: string;
37
- pos: number;
38
-
39
- constructor(input: string) {
40
- this.input = input;
41
- this.pos = 0;
42
- }
43
-
44
- parseExpression(minPrec: number): AstNode {
45
- let left = this.parseUnary();
46
-
47
- while (true) {
48
- this.skipWhitespace();
49
- const op = this.peekOperator();
50
- if (!op) break;
51
- const info = BINARY_OPS[op];
52
- if (!info || info.prec < minPrec) break;
53
- this.pos += op.length;
54
- this.skipWhitespace();
55
- const right = this.parseExpression(info.prec + 1);
56
- left = { type: 'binary', operator: info.op, left, right };
57
- }
58
-
59
- return left;
60
- }
61
-
62
- parseUnary(): AstNode {
63
- this.skipWhitespace();
64
- const ch = this.input[this.pos];
65
-
66
- if (ch === '!') {
67
- this.pos++;
68
- const operand = this.parseUnary();
69
- return { type: 'unary', operator: '!', operand };
70
- }
71
-
72
- if (ch === '-' && !this.isAfterValue()) {
73
- this.pos++;
74
- const operand = this.parseUnary();
75
- return { type: 'unary', operator: '-', operand };
76
- }
77
-
78
- return this.parsePrimary();
79
- }
80
-
81
- parsePrimary(): AstNode {
82
- this.skipWhitespace();
83
- const ch = this.input[this.pos];
84
-
85
- if (ch === '(') {
86
- this.pos++;
87
- const expr = this.parseExpression(0);
88
- this.skipWhitespace();
89
- this.expect(')');
90
- return expr;
91
- }
92
-
93
- if (ch === '"' || ch === "'") {
94
- return this.parseString();
95
- }
96
-
97
- if (this.isDigit(ch)) {
98
- return this.parseNumber();
99
- }
100
-
101
- if (this.isIdentStart(ch)) {
102
- return this.parseIdentifierOrCall();
103
- }
104
-
105
- throw new Error(`Unexpected character: '${ch}' at position ${this.pos}`);
106
- }
107
-
108
- parseString(): AstNode {
109
- const quote = this.input[this.pos];
110
- this.pos++;
111
- let value = '';
112
- while (this.pos < this.input.length && this.input[this.pos] !== quote) {
113
- if (this.input[this.pos] === '\\') {
114
- this.pos++;
115
- const esc = this.input[this.pos];
116
- if (esc === 'n') value += '\n';
117
- else if (esc === 't') value += '\t';
118
- else value += esc;
119
- } else {
120
- value += this.input[this.pos];
121
- }
122
- this.pos++;
123
- }
124
- this.expect(quote);
125
- return { type: 'literal', value };
126
- }
127
-
128
- parseNumber(): AstNode {
129
- const start = this.pos;
130
- while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
131
- this.pos++;
132
- }
133
- if (this.pos < this.input.length && this.input[this.pos] === '.') {
134
- this.pos++;
135
- while (this.pos < this.input.length && this.isDigit(this.input[this.pos])) {
136
- this.pos++;
137
- }
138
- }
139
- return { type: 'literal', value: Number(this.input.slice(start, this.pos)) };
140
- }
141
-
142
- parseIdentifierOrCall(): AstNode {
143
- const name = this.readIdentifier();
144
-
145
- if (name === 'true') return { type: 'literal', value: true };
146
- if (name === 'false') return { type: 'literal', value: false };
147
- if (name === 'null') return { type: 'literal', value: null };
148
-
149
- this.skipWhitespace();
150
-
151
- if (this.input[this.pos] === '(') {
152
- this.pos++;
153
- const args: AstNode[] = [];
154
- this.skipWhitespace();
155
- if (this.input[this.pos] !== ')') {
156
- args.push(this.parseExpression(0));
157
- while (this.input[this.pos] === ',') {
158
- this.pos++;
159
- this.skipWhitespace();
160
- args.push(this.parseExpression(0));
161
- }
162
- }
163
- this.skipWhitespace();
164
- this.expect(')');
165
- return { type: 'call', name, args };
166
- }
167
-
168
- const path = [name];
169
- while (this.pos < this.input.length && this.input[this.pos] === '.') {
170
- this.pos++;
171
- path.push(this.readIdentifier());
172
- }
173
-
174
- return { type: 'field', path };
175
- }
176
-
177
- readIdentifier(): string {
178
- const start = this.pos;
179
- while (this.pos < this.input.length && this.isIdentChar(this.input[this.pos])) {
180
- this.pos++;
181
- }
182
- if (this.pos === start) {
183
- throw new Error(`Expected identifier at position ${this.pos}`);
184
- }
185
- return this.input.slice(start, this.pos);
186
- }
187
-
188
- peekOperator(): string | null {
189
- for (const op of ['||', '&&', '==', '!=', '>=', '<=', '>', '<', '+', '-', '*', '/', '%']) {
190
- if (this.input.startsWith(op, this.pos)) {
191
- return op;
192
- }
193
- }
194
- return null;
195
- }
196
-
197
- skipWhitespace(): void {
198
- while (this.pos < this.input.length && /\s/.test(this.input[this.pos])) {
199
- this.pos++;
200
- }
201
- }
202
-
203
- expect(ch: string): void {
204
- if (this.input[this.pos] !== ch) {
205
- throw new Error(
206
- `Expected '${ch}' at position ${this.pos}, got '${this.input[this.pos] ?? 'EOF'}'`,
207
- );
208
- }
209
- this.pos++;
210
- }
211
-
212
- isDigit(ch: string | undefined): boolean {
213
- return ch !== undefined && ch >= '0' && ch <= '9';
214
- }
215
-
216
- isIdentStart(ch: string | undefined): boolean {
217
- return ch !== undefined && /[a-zA-Z_$]/.test(ch);
218
- }
219
-
220
- isIdentChar(ch: string | undefined): boolean {
221
- return ch !== undefined && /[a-zA-Z0-9_$]/.test(ch);
222
- }
223
-
224
- isAfterValue(): boolean {
225
- if (this.pos === 0) return false;
226
- const prev = this.input[this.pos - 1];
227
- return prev === ')' || this.isDigit(prev) || this.isIdentChar(prev);
228
- }
229
- }
@@ -1,45 +0,0 @@
1
- export type AstNode = LiteralNode | FieldRefNode | UnaryNode | BinaryNode | CallNode;
2
-
3
- export interface LiteralNode {
4
- type: 'literal';
5
- value: string | number | boolean | null;
6
- }
7
-
8
- export interface FieldRefNode {
9
- type: 'field';
10
- path: string[];
11
- }
12
-
13
- export interface UnaryNode {
14
- type: 'unary';
15
- operator: '!' | '-';
16
- operand: AstNode;
17
- }
18
-
19
- export interface BinaryNode {
20
- type: 'binary';
21
- operator: BinaryOperator;
22
- left: AstNode;
23
- right: AstNode;
24
- }
25
-
26
- export interface CallNode {
27
- type: 'call';
28
- name: string;
29
- args: AstNode[];
30
- }
31
-
32
- export type BinaryOperator =
33
- | '+'
34
- | '-'
35
- | '*'
36
- | '/'
37
- | '%'
38
- | '=='
39
- | '!='
40
- | '>'
41
- | '<'
42
- | '>='
43
- | '<='
44
- | '&&'
45
- | '||';
@@ -1,29 +0,0 @@
1
- import { createContext, useContext } from 'react';
2
- import type { FieldMeta } from '../binding/resolver.js';
3
-
4
- export interface FormContextValue {
5
- mode: 'create' | 'edit' | 'view';
6
- values: Record<string, unknown>;
7
- errors: Record<string, string>;
8
- dirty: Set<string>;
9
- touched: Set<string>;
10
- submitting: boolean;
11
-
12
- getValue(field: string): unknown;
13
- setValue(field: string, value: unknown): void;
14
- getError(field: string): string | undefined;
15
- getFieldMeta(field: string): FieldMeta | undefined;
16
- setTouched(field: string): void;
17
-
18
- submit(): Promise<void>;
19
- reset(): void;
20
- isDirty(): boolean;
21
- }
22
-
23
- const FormContextReact = createContext<FormContextValue | null>(null);
24
-
25
- export const FormContextProvider = FormContextReact.Provider;
26
-
27
- export function useFormContext(): FormContextValue | null {
28
- return useContext(FormContextReact);
29
- }
@@ -1,84 +0,0 @@
1
- import React, { useCallback, useEffect, useMemo } from 'react';
2
- import { useModelRecord } from '../data/useModelRecord.js';
3
- import { useModelMeta } from '../../data/useModelMeta.js';
4
- import { FormContextProvider } from './FormContext.js';
5
- import { useFormState } from './useFormState.js';
6
- import { useFormValidation } from './useFormValidation.js';
7
- import { useFormSubmit } from './useFormSubmit.js';
8
- import type { FormContextValue } from './FormContext.js';
9
- import type { FieldMeta } from '../binding/resolver.js';
10
-
11
- export interface FormProviderProps {
12
- model: string;
13
- id?: string | null;
14
- onSuccess?: (record: Record<string, unknown>, mode: 'create' | 'edit') => void;
15
- onError?: (errors: Record<string, string>, message: string) => void;
16
- children: React.ReactNode;
17
- }
18
-
19
- export function FormProvider({ model, id, onSuccess, onError, children }: FormProviderProps) {
20
- const mode: 'create' | 'edit' | 'view' = id ? 'edit' : 'create';
21
-
22
- const { modelMeta } = useModelMeta(model);
23
- const { data: record } = useModelRecord({ model, id, enabled: mode === 'edit' });
24
-
25
- const validation = useFormValidation(model);
26
- const formState = useFormState(validation.validateField);
27
- const { submit } = useFormSubmit({
28
- model,
29
- mode,
30
- id,
31
- formState,
32
- validation,
33
- onSuccess,
34
- onError,
35
- });
36
-
37
- useEffect(() => {
38
- if (mode === 'edit' && record) {
39
- formState.initValues(record);
40
- }
41
- }, [record, mode]);
42
-
43
- const reset = useCallback(() => {
44
- formState.reset(mode);
45
- }, [formState, mode]);
46
-
47
- const getFieldMeta = useCallback(
48
- (field: string): FieldMeta | undefined => {
49
- if (!modelMeta) return undefined;
50
- const fieldDef = modelMeta.fields.find((f) => f.name === field);
51
- if (!fieldDef) return undefined;
52
- return {
53
- type: fieldDef.type,
54
- label: fieldDef.label ?? field,
55
- required: fieldDef.required ?? false,
56
- readOnly: false,
57
- options: fieldDef.options as unknown[] | undefined,
58
- };
59
- },
60
- [modelMeta, mode],
61
- );
62
-
63
- const contextValue: FormContextValue = useMemo(
64
- () => ({
65
- mode,
66
- values: formState.state.values,
67
- errors: formState.state.errors,
68
- dirty: formState.getDirtyFields(),
69
- touched: formState.state.touched,
70
- submitting: formState.state.submitting,
71
- getValue: formState.getValue,
72
- setValue: formState.setValue,
73
- getError: formState.getError,
74
- getFieldMeta,
75
- setTouched: formState.setTouched,
76
- submit,
77
- reset,
78
- isDirty: formState.isDirty,
79
- }),
80
- [formState.state, formState, getFieldMeta, submit, reset, mode],
81
- );
82
-
83
- return <FormContextProvider value={contextValue}>{children}</FormContextProvider>;
84
- }
@@ -1,42 +0,0 @@
1
- import React, { useCallback } from 'react';
2
- import type { WidgetProps } from '../types.js';
3
- import { useWidgetContext } from '../hooks/useWidgetContext.js';
4
- import { FormProvider } from './FormProvider.js';
5
-
6
- export function FormWidget({ bind, on, children }: WidgetProps) {
7
- const ctx = useWidgetContext();
8
- const modelName = ctx.model;
9
- const id = bind.id ?? undefined;
10
-
11
- const handleSuccess = useCallback(
12
- (record: Record<string, unknown>, mode: 'create' | 'edit') => {
13
- on.success?.({ record, mode });
14
- },
15
- [on],
16
- );
17
-
18
- const handleError = useCallback(
19
- (errors: Record<string, string>, message: string) => {
20
- on.error?.({ errors, message });
21
- },
22
- [on],
23
- );
24
-
25
- if (!modelName) return null;
26
-
27
- return (
28
- <FormProvider model={modelName} id={id} onSuccess={handleSuccess} onError={handleError}>
29
- {children}
30
- </FormProvider>
31
- );
32
- }
33
-
34
- FormWidget.widgetMeta = {
35
- name: 'form',
36
- label: 'Form',
37
- category: 'data' as const,
38
- schema: {},
39
- binding: 'model' as const,
40
- triggers: ['success', 'error'],
41
- container: true,
42
- };
@@ -1,4 +0,0 @@
1
- export { FormWidget } from './FormWidget.js';
2
- export { FormProvider } from './FormProvider.js';
3
- export { useFormContext, FormContextProvider } from './FormContext.js';
4
- export type { FormContextValue } from './FormContext.js';
@@ -1,127 +0,0 @@
1
- import { useCallback, useRef, useState } from 'react';
2
-
3
- export interface FormState {
4
- values: Record<string, unknown>;
5
- original: Record<string, unknown>;
6
- errors: Record<string, string>;
7
- touched: Set<string>;
8
- submitting: boolean;
9
- }
10
-
11
- export interface UseFormStateResult {
12
- state: FormState;
13
- getValue(field: string): unknown;
14
- setValue(field: string, value: unknown): void;
15
- getError(field: string): string | undefined;
16
- setTouched(field: string): void;
17
- setErrors(errors: Record<string, string>): void;
18
- setSubmitting(submitting: boolean): void;
19
- isDirty(): boolean;
20
- getDirtyFields(): Set<string>;
21
- reset(mode: 'create' | 'edit'): void;
22
- initValues(record: Record<string, unknown>): void;
23
- }
24
-
25
- export function useFormState(
26
- onValidateField?: (field: string, value: unknown) => string | undefined,
27
- ): UseFormStateResult {
28
- const [values, setValues] = useState<Record<string, unknown>>({});
29
- const [original, setOriginal] = useState<Record<string, unknown>>({});
30
- const [errors, setErrors] = useState<Record<string, string>>({});
31
- const [touched, setTouched] = useState<Set<string>>(new Set());
32
- const [submitting, setSubmitting] = useState(false);
33
-
34
- const valuesRef = useRef(values);
35
- valuesRef.current = values;
36
- const originalRef = useRef(original);
37
- originalRef.current = original;
38
-
39
- const getValue = useCallback((field: string): unknown => {
40
- return valuesRef.current[field];
41
- }, []);
42
-
43
- const setValue = useCallback(
44
- (field: string, value: unknown) => {
45
- setValues((prev) => ({ ...prev, [field]: value }));
46
-
47
- if (onValidateField) {
48
- const error = onValidateField(field, value);
49
- setErrors((prev) => {
50
- if (error) return { ...prev, [field]: error };
51
- const next = { ...prev };
52
- delete next[field];
53
- return next;
54
- });
55
- }
56
- },
57
- [onValidateField],
58
- );
59
-
60
- const getError = useCallback(
61
- (field: string): string | undefined => {
62
- return errors[field];
63
- },
64
- [errors],
65
- );
66
-
67
- const markTouched = useCallback((field: string) => {
68
- setTouched((prev) => {
69
- if (prev.has(field)) return prev;
70
- const next = new Set(prev);
71
- next.add(field);
72
- return next;
73
- });
74
- }, []);
75
-
76
- const setErrorsBatch = useCallback((errs: Record<string, string>) => {
77
- setErrors(errs);
78
- }, []);
79
-
80
- const isDirty = useCallback((): boolean => {
81
- for (const key of Object.keys(valuesRef.current)) {
82
- if (valuesRef.current[key] !== originalRef.current[key]) return true;
83
- }
84
- return false;
85
- }, []);
86
-
87
- const getDirtyFields = useCallback((): Set<string> => {
88
- const dirty = new Set<string>();
89
- for (const key of Object.keys(valuesRef.current)) {
90
- if (valuesRef.current[key] !== originalRef.current[key]) {
91
- dirty.add(key);
92
- }
93
- }
94
- return dirty;
95
- }, []);
96
-
97
- const reset = useCallback((mode: 'create' | 'edit') => {
98
- if (mode === 'edit') {
99
- setValues({ ...originalRef.current });
100
- } else {
101
- setValues({});
102
- }
103
- setErrors({});
104
- setTouched(new Set());
105
- }, []);
106
-
107
- const initValues = useCallback((record: Record<string, unknown>) => {
108
- setValues({ ...record });
109
- setOriginal({ ...record });
110
- setErrors({});
111
- setTouched(new Set());
112
- }, []);
113
-
114
- return {
115
- state: { values, original, errors, touched, submitting },
116
- getValue,
117
- setValue,
118
- getError,
119
- setTouched: markTouched,
120
- setErrors: setErrorsBatch,
121
- setSubmitting,
122
- isDirty,
123
- getDirtyFields,
124
- reset,
125
- initValues,
126
- };
127
- }
@@ -1,90 +0,0 @@
1
- import { useCallback } from 'react';
2
- import { useQueryClient } from '@tanstack/react-query';
3
- import { apiClient } from '../../api/client.js';
4
- import { modelToPath } from '../../api/paths.js';
5
- import type { UseFormStateResult } from './useFormState.js';
6
- import type { UseFormValidationResult } from './useFormValidation.js';
7
-
8
- export interface UseFormSubmitOptions {
9
- model: string;
10
- mode: 'create' | 'edit' | 'view';
11
- id: string | null | undefined;
12
- formState: UseFormStateResult;
13
- validation: UseFormValidationResult;
14
- onSuccess?: (record: Record<string, unknown>, mode: 'create' | 'edit') => void;
15
- onError?: (errors: Record<string, string>, message: string) => void;
16
- }
17
-
18
- export interface UseFormSubmitResult {
19
- submit(): Promise<void>;
20
- }
21
-
22
- export function useFormSubmit(options: UseFormSubmitOptions): UseFormSubmitResult {
23
- const { model, mode, id, formState, validation, onSuccess, onError } = options;
24
- const queryClient = useQueryClient();
25
-
26
- const submit = useCallback(async () => {
27
- if (mode === 'view') return;
28
-
29
- const { state, setErrors, setSubmitting, getDirtyFields, initValues } = formState;
30
- const { values } = state;
31
-
32
- const errors = validation.validateAll(values);
33
- if (Object.keys(errors).length > 0) {
34
- setErrors(errors);
35
- // Mark all fields as touched so errors display
36
- for (const field of Object.keys(errors)) {
37
- formState.setTouched(field);
38
- }
39
- return;
40
- }
41
-
42
- setSubmitting(true);
43
-
44
- try {
45
- const basePath = modelToPath(model);
46
- let payload: Record<string, unknown>;
47
-
48
- if (mode === 'edit') {
49
- const dirty = getDirtyFields();
50
- payload = {};
51
- for (const field of dirty) {
52
- payload[field] = values[field];
53
- }
54
- } else {
55
- payload = {};
56
- for (const [key, value] of Object.entries(values)) {
57
- if (value !== null && value !== undefined && value !== '') {
58
- payload[key] = value;
59
- }
60
- }
61
- }
62
-
63
- const url = mode === 'edit' ? `${basePath}/${id}` : basePath;
64
- const method = mode === 'edit' ? 'PATCH' : 'POST';
65
-
66
- const response = await apiClient(url, {
67
- method,
68
- body: JSON.stringify(payload),
69
- });
70
-
71
- if (!response.ok) {
72
- const body = await response.json().catch(() => ({ message: 'Submit failed' }));
73
- const serverErrors: Record<string, string> = body.errors ?? {};
74
- const message: string = body.message ?? 'Submit failed';
75
- setErrors(serverErrors);
76
- onError?.(serverErrors, message);
77
- return;
78
- }
79
-
80
- const record = await response.json();
81
- queryClient.invalidateQueries({ queryKey: ['model', model] });
82
- initValues(record);
83
- onSuccess?.(record, mode);
84
- } finally {
85
- setSubmitting(false);
86
- }
87
- }, [model, mode, id, formState, validation, onSuccess, onError, queryClient]);
88
-
89
- return { submit };
90
- }