@tinya-code/gas-admin-ui 0.2.0-alpha.12 → 0.2.0-alpha.15

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 (369) hide show
  1. package/README.md +222 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +3 -15
  4. package/src/lib/actions/action-registry.spec.ts +52 -0
  5. package/src/lib/actions/action-registry.ts +126 -0
  6. package/src/lib/actions/action-service.spec.ts +215 -0
  7. package/src/lib/actions/action-service.ts +87 -0
  8. package/src/lib/actions/descriptors.spec.ts +155 -0
  9. package/src/lib/actions/descriptors.ts +154 -0
  10. package/src/lib/columns/column-cell.ts +33 -0
  11. package/src/lib/columns/column-defaults.spec.ts +99 -0
  12. package/src/lib/columns/column-defaults.ts +84 -0
  13. package/src/lib/columns/column-format.spec.ts +274 -0
  14. package/src/lib/columns/descriptors.ts +113 -0
  15. package/src/lib/columns/format-badge.ts +51 -0
  16. package/src/lib/columns/format-boolean.ts +58 -0
  17. package/src/lib/columns/format-currency.ts +30 -0
  18. package/src/lib/columns/format-date.ts +35 -0
  19. package/src/lib/columns/format-number.ts +32 -0
  20. package/src/lib/columns/format-text.ts +23 -0
  21. package/src/lib/columns/format-thumbnail.ts +63 -0
  22. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  23. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  24. package/src/lib/core/guards/auth.guard.ts +48 -0
  25. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  27. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  28. package/src/lib/core/models/api.model.ts +104 -0
  29. package/src/lib/core/models/schema-descriptions.ts +118 -0
  30. package/src/lib/core/models/schema.model.ts +1097 -0
  31. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  32. package/src/lib/core/registry/composition-validator.ts +270 -0
  33. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  34. package/src/lib/core/registry/field-validators.ts +440 -0
  35. package/src/lib/core/registry/render-host.spec.ts +208 -0
  36. package/src/lib/core/registry/render-host.ts +646 -0
  37. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  38. package/src/lib/core/registry/render-registry.ts +312 -0
  39. package/src/lib/core/registry/schema-catalog.ts +156 -0
  40. package/src/lib/core/services/api.service.spec.ts +1142 -0
  41. package/src/lib/core/services/api.service.ts +371 -0
  42. package/src/lib/core/services/auth.service.spec.ts +653 -0
  43. package/src/lib/core/services/auth.service.ts +503 -0
  44. package/src/lib/core/services/config.service.spec.ts +121 -0
  45. package/src/lib/core/services/config.service.ts +119 -0
  46. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  47. package/src/lib/core/services/data-source.service.ts +142 -0
  48. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  49. package/src/lib/core/services/error-mapper.service.ts +49 -0
  50. package/src/lib/core/services/notification.service.ts +73 -0
  51. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  52. package/src/lib/core/services/theme.service.spec.ts +106 -0
  53. package/src/lib/core/services/theme.service.ts +86 -0
  54. package/src/lib/core/services/transport.spec.ts +198 -0
  55. package/src/lib/core/services/transport.ts +164 -0
  56. package/src/lib/core/services/upload.service.ts +81 -0
  57. package/src/lib/core/testing/categories.schema.ts +125 -0
  58. package/src/lib/core/testing/orders.schema.ts +372 -0
  59. package/src/lib/core/testing/products.schema.ts +258 -0
  60. package/src/lib/core/testing/site.schema.ts +195 -0
  61. package/src/lib/core/testing/test-catalog.ts +21 -0
  62. package/src/lib/core/tokens.ts +112 -0
  63. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  64. package/src/lib/core/utils/clipboard.ts +90 -0
  65. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  66. package/src/lib/core/utils/context-resolver.ts +101 -0
  67. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  68. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  69. package/src/lib/core/utils/source-options.spec.ts +96 -0
  70. package/src/lib/core/utils/source-options.ts +54 -0
  71. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  72. package/src/lib/core/utils/template-interpolator.ts +56 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  74. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  75. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  76. package/src/lib/fields/boolean/boolean.ts +40 -0
  77. package/src/lib/fields/color/color.descriptor.ts +12 -0
  78. package/src/lib/fields/color/color.ts +34 -0
  79. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  80. package/src/lib/fields/currency/currency.ts +67 -0
  81. package/src/lib/fields/date/date.descriptor.ts +12 -0
  82. package/src/lib/fields/date/date.ts +36 -0
  83. package/src/lib/fields/descriptors.ts +122 -0
  84. package/src/lib/fields/display/display-badge.ts +65 -0
  85. package/src/lib/fields/display/display-date.ts +39 -0
  86. package/src/lib/fields/display/display-defaults.ts +84 -0
  87. package/src/lib/fields/display/display-file.ts +41 -0
  88. package/src/lib/fields/display/display-image.ts +54 -0
  89. package/src/lib/fields/display/display-list.ts +107 -0
  90. package/src/lib/fields/display/display-number.ts +41 -0
  91. package/src/lib/fields/display/display-text.ts +42 -0
  92. package/src/lib/fields/display/display.spec.ts +242 -0
  93. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  94. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  95. package/src/lib/fields/email/email.descriptor.ts +12 -0
  96. package/src/lib/fields/email/email.ts +28 -0
  97. package/src/lib/fields/field-aria.ts +60 -0
  98. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  99. package/src/lib/fields/field-host/field-host.ts +47 -0
  100. package/src/lib/fields/field-node.ts +88 -0
  101. package/src/lib/fields/file/file.descriptor.ts +14 -0
  102. package/src/lib/fields/file/file.spec.ts +134 -0
  103. package/src/lib/fields/file/file.ts +281 -0
  104. package/src/lib/fields/group/group.descriptor.ts +21 -0
  105. package/src/lib/fields/group/group.ts +110 -0
  106. package/src/lib/fields/image/image.descriptor.ts +12 -0
  107. package/src/lib/fields/image/image.spec.ts +117 -0
  108. package/src/lib/fields/image/image.ts +281 -0
  109. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  110. package/src/lib/fields/key-value/key-value.ts +283 -0
  111. package/src/lib/fields/list/list.descriptor.ts +21 -0
  112. package/src/lib/fields/list/list.ts +376 -0
  113. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  114. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  115. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  116. package/src/lib/fields/number/number.descriptor.ts +12 -0
  117. package/src/lib/fields/number/number.ts +40 -0
  118. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  119. package/src/lib/fields/phone/phone.ts +46 -0
  120. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  121. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  122. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  123. package/src/lib/fields/relation/relation.spec.ts +193 -0
  124. package/src/lib/fields/relation/relation.ts +224 -0
  125. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  126. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  127. package/src/lib/fields/select/radio-mode.ts +25 -0
  128. package/src/lib/fields/select/select.descriptor.ts +12 -0
  129. package/src/lib/fields/select/select.spec.ts +162 -0
  130. package/src/lib/fields/select/select.ts +210 -0
  131. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  132. package/src/lib/fields/slug/slug.ts +67 -0
  133. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  134. package/src/lib/fields/string-list/string-list.ts +222 -0
  135. package/src/lib/fields/text/text.descriptor.ts +12 -0
  136. package/src/lib/fields/text/text.ts +35 -0
  137. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  138. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  139. package/src/lib/fields/textarea/textarea.ts +126 -0
  140. package/src/lib/fields/time/time.descriptor.ts +12 -0
  141. package/src/lib/fields/time/time.ts +28 -0
  142. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  143. package/src/lib/fields/url/url.descriptor.ts +12 -0
  144. package/src/lib/fields/url/url.ts +53 -0
  145. package/src/lib/gas-admin-ui.spec.ts +21 -0
  146. package/src/lib/gas-admin-ui.ts +9 -0
  147. package/src/lib/layouts/descriptors.ts +9 -0
  148. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  149. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  150. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  151. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  152. package/src/lib/pages/form-view/form-model.ts +88 -0
  153. package/src/lib/pages/form-view/form-schema.ts +255 -0
  154. package/src/lib/pages/form-view/form-view.ts +1622 -0
  155. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  156. package/src/lib/pages/list-view/list-view.ts +882 -0
  157. package/src/lib/pages/login/login.spec.ts +109 -0
  158. package/src/lib/pages/login/login.ts +227 -0
  159. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  160. package/src/lib/shared/components/badge/badge.ts +35 -0
  161. package/src/lib/shared/components/button/button.ts +65 -0
  162. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  163. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  164. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  165. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  166. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  167. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  168. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  169. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  170. package/src/lib/shared/components/modal/modal.ts +145 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  172. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  173. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  174. package/src/lib/shared/components/toast/toast.ts +58 -0
  175. package/src/lib/shared/utils/col-span.ts +25 -0
  176. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  177. package/src/lib/shared/utils/depends-on.ts +48 -0
  178. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  179. package/src/lib/shared/utils/field-path.ts +49 -0
  180. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  181. package/src/lib/shared/utils/seed.spec.ts +64 -0
  182. package/src/lib/shared/utils/seed.ts +149 -0
  183. package/src/lib/shared/utils/slugify.ts +13 -0
  184. package/src/lib/shared/utils/validators.ts +41 -0
  185. package/src/lib/shell/layout/layout.ts +63 -0
  186. package/src/lib/shell/shell.routes.spec.ts +59 -0
  187. package/src/lib/shell/shell.routes.ts +122 -0
  188. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  189. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  190. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  191. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  192. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  193. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  194. package/src/lib/views/composer/composer-host.ts +248 -0
  195. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  196. package/src/lib/views/composer/view-context.spec.ts +117 -0
  197. package/src/lib/views/composer/view-context.ts +109 -0
  198. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  199. package/src/lib/views/composer/view-slot.ts +178 -0
  200. package/src/lib/views/descriptors.ts +46 -0
  201. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  202. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  203. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  206. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  207. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  208. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  209. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  210. package/src/lib/widgets/descriptors.ts +29 -0
  211. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  212. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  213. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  216. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  217. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  218. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  219. package/src/lib/widgets/record-list/record-list.ts +115 -0
  220. package/src/lib/widgets/series.spec.ts +42 -0
  221. package/src/lib/widgets/series.ts +56 -0
  222. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  223. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  224. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  225. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  226. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  227. package/src/public-api.ts +104 -0
  228. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  229. package/tsconfig.lib.json +13 -0
  230. package/tsconfig.lib.prod.json +11 -0
  231. package/tsconfig.spec.json +10 -0
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  233. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  235. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  237. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  239. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  241. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  243. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  245. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  247. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  249. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  251. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  253. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  255. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  257. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  259. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  261. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  263. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  265. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  267. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  269. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  271. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  273. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  275. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  277. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  279. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  281. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  283. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  285. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  287. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  289. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  291. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  293. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  295. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  297. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  299. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  301. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  303. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  305. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  307. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  309. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  311. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  313. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  315. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  317. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  319. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  321. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  323. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  325. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  327. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  329. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  333. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  335. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  337. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  338. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  340. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  342. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  344. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  346. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  348. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  350. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  352. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  354. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  356. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  358. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  360. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  362. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  364. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  366. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  368. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  369. package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
@@ -0,0 +1,228 @@
1
+ import {
2
+ Component,
3
+ DestroyRef,
4
+ Injector,
5
+ OnInit,
6
+ computed,
7
+ inject,
8
+ input,
9
+ output,
10
+ signal,
11
+ } from '@angular/core';
12
+ import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
13
+ import { form, submit } from '@angular/forms/signals';
14
+ import { firstValueFrom } from 'rxjs';
15
+
16
+ import type { FieldSchema, ResourceSchema } from '../../core/models/schema.model';
17
+ import { ApiService } from '../../core/services/api.service';
18
+ import { ErrorMapperService } from '../../core/services/error-mapper.service';
19
+ import { NotificationService } from '../../core/services/notification.service';
20
+ import { FieldHost } from '../../fields/field-host/field-host';
21
+ import { childTree, type RootTree } from '../../fields/field-node';
22
+ import { Button } from '../../shared/components/button/button';
23
+ import { Drawer, type DrawerSize } from '../../shared/components/drawer/drawer';
24
+ import { Skeleton } from '../../shared/components/skeleton/skeleton';
25
+ import { buildFormSchema } from '../form-view/form-schema';
26
+ import { buildPayload, seedModel } from '../form-view/form-model';
27
+
28
+ /**
29
+ * Formulario dentro de un drawer (plan 6.4/6.5, guía §5).
30
+ *
31
+ * Es deliberadamente más chico que `form-view`: sin pestañas, sin secciones,
32
+ * sin borrador y sin resumen de errores. Sirve para abrir 1–3 campos sin
33
+ * perder el contexto de la pantalla de la que vino el usuario, y para crear
34
+ * un registro desde el dropdown de una relación.
35
+ *
36
+ * Componente controlado: `open` entra por `input()`, y el padre cierra con
37
+ * `close`/`saved`. Nada navega por su cuenta.
38
+ */
39
+ @Component({
40
+ selector: 'app-drawer-form',
41
+ imports: [Button, Drawer, FieldHost, Skeleton],
42
+ template: `
43
+ <app-drawer [open]="open()" [title]="drawerTitle()" [size]="size()" (close)="close.emit()">
44
+ @if (status() === 'loading') {
45
+ <app-skeleton [lines]="4" />
46
+ } @else if (status() === 'error') {
47
+ <p role="alert" class="rounded-lg bg-danger/10 px-3 py-2 text-sm text-danger">
48
+ {{ loadError() }}
49
+ </p>
50
+ } @else if (tree(); as formTree) {
51
+ @if (generalError()) {
52
+ <p
53
+ role="alert"
54
+ class="mb-4 rounded-lg bg-danger/10 px-3 py-2 text-sm text-danger"
55
+ data-drawer-error
56
+ >
57
+ {{ generalError() }}
58
+ </p>
59
+ }
60
+ @for (field of visibleFields(); track field.key) {
61
+ <div class="mb-4">
62
+ <app-field-host
63
+ [field]="field"
64
+ [tree]="formTree"
65
+ [exists]="mode() === 'edit'"
66
+ [serverErrors]="fieldErrors()"
67
+ [resource]="schema().id"
68
+ />
69
+ </div>
70
+ }
71
+ }
72
+
73
+ <div drawer-actions class="flex justify-end gap-2">
74
+ <button app-button variant="outline" type="button" (click)="close.emit()">Cancelar</button>
75
+ <button app-button type="button" [loading]="saving()" (click)="save()">Guardar</button>
76
+ </div>
77
+ </app-drawer>
78
+ `,
79
+ })
80
+ export class DrawerForm implements OnInit {
81
+ readonly open = input(false);
82
+ readonly schema = input.required<ResourceSchema>();
83
+ /** Subconjunto de campos a mostrar. Por defecto, todos los del schema. */
84
+ readonly fields = input<readonly FieldSchema[] | undefined>(undefined);
85
+ readonly mode = input<'create' | 'edit'>('create');
86
+ readonly recordKey = input<string | null>(null);
87
+ readonly title = input<string | null>(null);
88
+ readonly size = input<DrawerSize>('md');
89
+
90
+ /** Registro creado o actualizado, listo para que el padre refresque. */
91
+ readonly saved = output<Record<string, unknown>>();
92
+ readonly close = output<void>();
93
+
94
+ private readonly api = inject(ApiService);
95
+ private readonly notifications = inject(NotificationService);
96
+ private readonly errorMapper = inject(ErrorMapperService);
97
+ private readonly injector = inject(Injector);
98
+ private readonly destroyRef = inject(DestroyRef);
99
+
100
+ private readonly tree = signal<RootTree | null>(null);
101
+ private readonly status = signal<'idle' | 'loading' | 'ready' | 'error'>('idle');
102
+ private readonly saving = signal(false);
103
+ private readonly generalError = signal('');
104
+ private readonly fieldErrors = signal<Record<string, string>>({});
105
+ private readonly loadError = signal('');
106
+
107
+ readonly visibleFields = computed<readonly FieldSchema[]>(
108
+ () => this.fields() ?? this.schema().fields,
109
+ );
110
+
111
+ readonly drawerTitle = computed(() => {
112
+ if (this.title() !== null) {
113
+ return this.title() as string;
114
+ }
115
+ const label = this.schema().label;
116
+ return this.mode() === 'create' ? `Crear ${label}` : `Editar ${label}`;
117
+ });
118
+
119
+ ngOnInit(): void {
120
+ // Se construye al montar, NO en un `effect`: `form()` crea un effect
121
+ // interno y no puede correr dentro de otro (NG0602). Los padres montan
122
+ // este componente bajo un `@if` y fijan los inputs, así que no hace
123
+ // falta reaccionar a cambios posteriores.
124
+ this.build();
125
+ }
126
+
127
+ private build(): void {
128
+ const schema = this.schema();
129
+ const recordKey = this.recordKey();
130
+ const schemaFn = buildFormSchema(schema, this.api, recordKey ?? undefined);
131
+ const tree = form(signal(seedModel(schema)), schemaFn, { injector: this.injector });
132
+
133
+ if (this.mode() === 'create' || recordKey === null) {
134
+ this.tree.set(tree);
135
+ this.status.set('ready');
136
+ return;
137
+ }
138
+
139
+ // Edición: el valor se pisa con el registro, dejando los defaults de
140
+ // los campos que el backend no devuelve.
141
+ this.status.set('loading');
142
+ this.api
143
+ .get<Record<string, unknown>>(schema.endpoint, recordKey)
144
+ .pipe(takeUntilDestroyed(this.destroyRef))
145
+ .subscribe({
146
+ next: (record) => {
147
+ tree().value.set({ ...seedModel(schema), ...record });
148
+ this.tree.set(tree);
149
+ this.status.set('ready');
150
+ },
151
+ error: (error: unknown) => {
152
+ this.loadError.set(this.errorMapper.messageOf(error));
153
+ this.status.set('error');
154
+ },
155
+ });
156
+ }
157
+
158
+ async save(): Promise<void> {
159
+ const tree = this.tree();
160
+ const schema = this.schema();
161
+ if (tree === null || this.saving()) {
162
+ return;
163
+ }
164
+ this.saving.set(true);
165
+ this.generalError.set('');
166
+ this.fieldErrors.set({});
167
+ try {
168
+ const ok = await submit(tree, {
169
+ // `null` = sin errores propios: los del backend se pintan aparte.
170
+ action: async () => {
171
+ await this.persist(tree, schema);
172
+ return null;
173
+ },
174
+ onInvalid: () => this.markTouched(tree),
175
+ });
176
+ if (!ok || Object.keys(this.fieldErrors()).length > 0 || this.generalError() !== '') {
177
+ this.markTouched(tree);
178
+ }
179
+ } finally {
180
+ this.saving.set(false);
181
+ }
182
+ }
183
+
184
+ /**
185
+ * Marca como tocado lo que se está mostrando. Se recorre por campo y no
186
+ * desde la raíz: `FieldTree<Record<string, unknown>>` se indexa con una
187
+ * clave dinámica y `markAsTouched` no compila ahí (`childTree` centraliza
188
+ * ese cast). Baja a todos los descendientes, así un `group` queda cubierto.
189
+ */
190
+ private markTouched(tree: RootTree): void {
191
+ for (const field of this.visibleFields()) {
192
+ childTree<unknown>(tree, field.key)().markAsTouched();
193
+ }
194
+ }
195
+
196
+ /** create/update; nunca lanza (los errores quedan en las señales). */
197
+ private async persist(tree: RootTree, schema: ResourceSchema): Promise<void> {
198
+ const payload = buildPayload(schema, tree);
199
+ try {
200
+ if (this.mode() === 'create') {
201
+ const created = await firstValueFrom(
202
+ this.api.create<Record<string, unknown>>(schema.endpoint, payload),
203
+ );
204
+ this.notifications.success('Registro creado.');
205
+ this.saved.emit(created ?? {});
206
+ this.close.emit();
207
+ return;
208
+ }
209
+ const updated = await firstValueFrom(
210
+ this.api.update<Record<string, unknown>>(
211
+ schema.endpoint,
212
+ this.recordKey() ?? undefined,
213
+ payload,
214
+ ),
215
+ );
216
+ this.notifications.success('Registro guardado.');
217
+ this.saved.emit(updated ?? {});
218
+ this.close.emit();
219
+ } catch (error: unknown) {
220
+ const mapped = this.errorMapper.map(error);
221
+ this.fieldErrors.set(mapped.fields);
222
+ const withoutFields = Object.keys(mapped.fields).length === 0;
223
+ this.generalError.set(
224
+ mapped.general ?? (withoutFields ? this.errorMapper.messageOf(error) : ''),
225
+ );
226
+ }
227
+ }
228
+ }
@@ -0,0 +1,88 @@
1
+ import type { FieldSchema, ResourceSchema } from '../../core/models/schema.model';
2
+ import { childTree, itemTree, listTree, type RootTree } from '../../fields/field-node';
3
+ import { seedFields } from '../../shared/utils/seed';
4
+
5
+ /**
6
+ * Modelo y payload del formulario genérico (Fase 10, ampliado en Fase 11).
7
+ *
8
+ * - `seedModel` siembra un valor inicial para CADA campo declarado en el
9
+ * schema antes de crear el formulario: el árbol de Signal Forms replica la
10
+ * forma del modelo, así que un campo ausente no tendría estado (ni valor
11
+ * que enviar). La siembra vive en `shared/utils/seed.ts`.
12
+ * - `buildPayload` decide qué viaja al backend (base.md §10): solo las claves
13
+ * declaradas en el schema, sin los ocultos y sin `positionField` raíz.
14
+ */
15
+
16
+ /** Valores por defecto del schema, en el mismo orden/shape que el JSON. */
17
+ export function seedModel(schema: ResourceSchema): Record<string, unknown> {
18
+ return seedFields(schema.fields);
19
+ }
20
+
21
+ /**
22
+ * Payload del guardado: whitelist de `schema.fields`.
23
+ *
24
+ * - Los campos ocultos (`visibleWhen` falso) no se muestran ni se envían
25
+ * (base.md §10): usar `hidden()` garantiza que no aparezcan acá aunque
26
+ * tengan valor sembrado — también dentro de `group` y de los ítems de una
27
+ * `list` (recursivo).
28
+ * - `positionField` del recurso se excluye aunque esté declarado: el orden
29
+ * del listado lo gestiona el backend (api.md R3, nunca se expone). El
30
+ * `positionField` de los ÍTEMS de una lista sí viaja: el orden local lo
31
+ * calcula el front (base.md §8).
32
+ * - Los tipos que la UI todavía no edita SÍ viajan: sus valores llegan del
33
+ * registro leído, así que reenviarlos evita pisarlos.
34
+ */
35
+ export function buildPayload(schema: ResourceSchema, root: RootTree): Record<string, unknown> {
36
+ const payload: Record<string, unknown> = {};
37
+ collectPayload(root, schema.fields, payload, schema.positionField);
38
+ return payload;
39
+ }
40
+
41
+ function collectPayload(
42
+ tree: RootTree,
43
+ fields: readonly FieldSchema[],
44
+ payload: Record<string, unknown>,
45
+ excludedKey: string | undefined,
46
+ ): void {
47
+ for (const field of fields) {
48
+ if (excludedKey !== undefined && field.key === excludedKey) {
49
+ continue;
50
+ }
51
+ const state = childTree<unknown>(tree, field.key)();
52
+ if (state.hidden()) {
53
+ continue;
54
+ }
55
+ if (field.type === 'group') {
56
+ const nested: Record<string, unknown> = {};
57
+ collectPayload(
58
+ childTree<Record<string, unknown>>(tree, field.key),
59
+ field.fields,
60
+ nested,
61
+ undefined,
62
+ );
63
+ payload[field.key] = nested;
64
+ continue;
65
+ }
66
+ if (field.type === 'list') {
67
+ const items = listTree(tree, field.key);
68
+ const raw = (state.value() as Record<string, unknown>[] | null) ?? [];
69
+ const list: unknown[] = [];
70
+ for (let index = 0; index < raw.length; index++) {
71
+ const nested: Record<string, unknown> = {};
72
+ collectPayload(itemTree(items, index), field.itemFields, nested, undefined);
73
+ // El orden de los ítems viaja con el registro padre (base.md §8): el
74
+ // `positionField` no es un campo editable, por eso no está en
75
+ // `itemFields` y hay que leerlo del modelo crudo.
76
+ const positionKey = field.positionField;
77
+ const position = positionKey !== undefined ? raw[index]?.[positionKey] : undefined;
78
+ if (positionKey !== undefined && position !== undefined) {
79
+ nested[positionKey] = position;
80
+ }
81
+ list.push(nested);
82
+ }
83
+ payload[field.key] = list;
84
+ continue;
85
+ }
86
+ payload[field.key] = state.value();
87
+ }
88
+ }
@@ -0,0 +1,255 @@
1
+ import {
2
+ applyEach,
3
+ disabled,
4
+ hidden,
5
+ readonly as readonlyRule,
6
+ type SchemaFn,
7
+ type SchemaPathTree,
8
+ } from '@angular/forms/signals';
9
+
10
+ import type { FieldSchema, ResourceSchema } from '../../core/models/schema.model';
11
+ import type { ApiService } from '../../core/services/api.service';
12
+ import {
13
+ FIELD_VALIDATORS,
14
+ registerEmailRule,
15
+ registerUrlRule,
16
+ type FieldPath,
17
+ type ValidatorContext,
18
+ } from '../../core/registry/field-validators';
19
+ import { isSupported } from '../../core/registry/render-registry';
20
+ import { evaluateCondition } from '../../shared/utils/condition-evaluator';
21
+
22
+ /** Ruta de un hijo bajo un path (raíz, grupo o ítem). */
23
+ type ChildPath<V> = FieldPath<V>;
24
+ /** Ruta raíz del formulario. */
25
+ type RootPath = SchemaPathTree<Record<string, unknown>>;
26
+ /** Ruta de un campo lista (para `applyEach`). */
27
+ type ListPath<V> = FieldPath<V>;
28
+
29
+ /**
30
+ * Reglas del formulario desde el schema (base.md §4, §6, §9, §10).
31
+ *
32
+ * Hay DOS clases de reglas y viven en sitios distintos:
33
+ *
34
+ * 1. **Ciclo de vida** — `visibleWhen`, `readonlyOn`, `hiddenOn`, `dependsOn`:
35
+ * decides si el campo se toca, se ve o se manda. Vive acá, con la
36
+ * estructura del formulario.
37
+ * 2. **Validadores** — obligatoriedad, longitud, patrón, rangos, únicos…:
38
+ * vive en `core/registry/field-validators.ts` (F7 + B11), con los MISMOS
39
+ * nombres que el back. Este módulo sólo RECORRE ese registro por cada
40
+ * campo: añadir un validador es una entrada allá, no un `case` acá.
41
+ *
42
+ * El schema de Signal Forms recibe el path RAÍZ; las reglas se atan a la ruta
43
+ * dinámica de cada campo. El barrido es **recursivo** — un `group` registra
44
+ * las reglas de sus hijos bajo su propia ruta y un `list` registra las de
45
+ * cada ítem con `applyEach`, de modo que las reglas corran también para los
46
+ * ítems que se agregan después.
47
+ *
48
+ * Solo se generan reglas para los tipos que la UI sabe editar (`isSupported('field', …)`):
49
+ * un campo invisible con `required` bloquearía el guardado para siempre.
50
+ *
51
+ * `api` y `excludeKey` alimentan la validación `unique`: se consulta el
52
+ * listado del recurso y se descarta el propio registro (la clave del registro
53
+ * en edición, tomada de la ruta).
54
+ */
55
+ export function buildFormSchema(
56
+ schema: ResourceSchema,
57
+ api: ApiService,
58
+ excludeKey?: string,
59
+ ): SchemaFn<Record<string, unknown>> {
60
+ return (root) => {
61
+ applyRulesToFields(
62
+ { schema, api, excludeKey, root, container: root, siblings: schema.fields },
63
+ schema.fields,
64
+ );
65
+ };
66
+ }
67
+
68
+ /**
69
+ * Ámbito de registro de reglas.
70
+ *
71
+ * - `root` es siempre la raíz del formulario (referencia para condiciones que
72
+ * nombran un campo de la raíz, base.md §4: «mismo nivel o registro raíz»).
73
+ * - `container` es el nodo del que cuelgan las rutas hijas: la raíz, un
74
+ * `group` o un ítem de un `list`.
75
+ * - `siblings` son los campos declarados en `container`: si una condición
76
+ * nombra a uno de ellos se evalúa contra el contenedor, si no, contra la
77
+ * raíz.
78
+ */
79
+ interface RuleScope {
80
+ schema: ResourceSchema;
81
+ api: ApiService;
82
+ excludeKey: string | undefined;
83
+ root: RootPath;
84
+ container: object;
85
+ siblings: readonly FieldSchema[];
86
+ }
87
+
88
+ function applyRulesToFields(scope: RuleScope, fields: readonly FieldSchema[]): void {
89
+ for (const field of fields) {
90
+ if (!isSupported('field', field.type)) {
91
+ continue;
92
+ }
93
+ applyRulesToField(scope, field, pathAt<unknown>(scope.container, field.key));
94
+ }
95
+ }
96
+
97
+ function applyRulesToField(scope: RuleScope, field: FieldSchema, path: ChildPath<unknown>): void {
98
+ applyLifecycleRules(scope, field, path);
99
+
100
+ // Validadores: el registro manda. Cada entrada decide si el campo la pide
101
+ // y ata su regla a ESTA ruta (F7: sin `if` por regla en este módulo).
102
+ const ctx: ValidatorContext = {
103
+ path,
104
+ field,
105
+ schema: scope.schema,
106
+ api: scope.api,
107
+ excludeKey: scope.excludeKey,
108
+ };
109
+ for (const entry of FIELD_VALIDATORS) {
110
+ if (entry.declares(ctx)) {
111
+ entry.register(ctx);
112
+ }
113
+ }
114
+
115
+ // Estructura (no es validador): los HIJOS se registran bajo su propia
116
+ // ruta, recursivamente, para que las reglas corran también en los ítems
117
+ // que el usuario agrega después.
118
+ switch (field.type) {
119
+ case 'group': {
120
+ applyRulesToFields({ ...scope, container: path, siblings: field.fields }, field.fields);
121
+ break;
122
+ }
123
+ case 'list': {
124
+ applyEach(path as unknown as ListPath<Record<string, unknown>[]>, (item) =>
125
+ applyRulesToFields(
126
+ { ...scope, container: item as object, siblings: field.itemFields },
127
+ field.itemFields,
128
+ ),
129
+ );
130
+ break;
131
+ }
132
+ case 'string-list': {
133
+ applyEach(path as unknown as ListPath<string[]>, (item) =>
134
+ applyStringItemRules(item as ChildPath<string>, field.itemType),
135
+ );
136
+ break;
137
+ }
138
+ default:
139
+ break;
140
+ }
141
+ }
142
+
143
+ /**
144
+ * ¿Aplica `mode` en ESTE contexto de formulario?
145
+ *
146
+ * Lo comparten `readonlyOn` y `hiddenOn`: `always` vale en todos los modos,
147
+ * `create` sólo en alta y `update` sólo en edición. El modo se detecta por
148
+ * `scope.excludeKey !== undefined`, que es la señal que ya usaba `readonlyOn`
149
+ * (form-view define `mode` a partir de la misma condición).
150
+ */
151
+ function modeAppliesTo(mode: 'create' | 'update' | 'always', isEdit: boolean): boolean {
152
+ return mode === 'always' || (mode === 'update' && isEdit) || (mode === 'create' && !isEdit);
153
+ }
154
+
155
+ /**
156
+ * Ciclo de vida del campo: visibilidad, edición y dependencias (base.md §4).
157
+ *
158
+ * Aquí NO hay validadores — la obligatoriedad vive en el registro.
159
+ * `hidden` además excluye el campo de `dirty`, `invalid` y del payload
160
+ * (base.md §10 «Campos ocultos»), y `disabled` lo deja fuera de la
161
+ * validación, así un `required` en el hijo no bloquea el guardado mientras
162
+ * no haya nada que elegir.
163
+ */
164
+ function applyLifecycleRules(scope: RuleScope, field: FieldSchema, path: ChildPath<unknown>): void {
165
+ if (field.visibleWhen) {
166
+ const condition = field.visibleWhen;
167
+ hidden(path, {
168
+ when: (ctx) =>
169
+ !evaluateCondition(condition, ctx.valueOf(conditionPath(scope, condition.field))),
170
+ });
171
+ }
172
+ // Selección dependiente (guía §1, plan 6.6): el hijo no se puede tocar
173
+ // hasta que el padre tenga valor.
174
+ //
175
+ // Acá NO sirve `conditionPath`: eso devuelve el contenedor entero porque
176
+ // `evaluateCondition` recibe el objeto de hermanos. Acá se necesita el
177
+ // valor concreto del padre.
178
+ if (field.dependsOn) {
179
+ const parentKey = field.dependsOn;
180
+ const isSibling = scope.siblings.some((candidate) => candidate.key === parentKey);
181
+ const parent = isSibling ? scope.container : scope.root;
182
+ const parentPath = pathAt<unknown>(parent, parentKey);
183
+ disabled(path, ({ valueOf }) => isEmptyValue(valueOf(parentPath)));
184
+ }
185
+ if (field.readonly) {
186
+ readonlyRule(path, { when: () => true });
187
+ }
188
+ if (field.readonlyOn && modeAppliesTo(field.readonlyOn, scope.excludeKey !== undefined)) {
189
+ readonlyRule(path, { when: () => true });
190
+ }
191
+ // Ciclo de vida de la visibilidad — simétrico a `readonlyOn` de arriba.
192
+ // `hidden()` deja el campo fuera de la validación y del payload, así que en
193
+ // alta un campo que LLENA EL BACK no ocupa la sección ni exige nada: sin
194
+ // esto, `ref` y `pin` aparecían vacíos con un "se genera solo al guardar".
195
+ if (field.hiddenOn && modeAppliesTo(field.hiddenOn, scope.excludeKey !== undefined)) {
196
+ hidden(path, { when: () => true });
197
+ }
198
+ if (field.readonlyWhen) {
199
+ const condition = field.readonlyWhen;
200
+ readonlyRule(path, {
201
+ when: (ctx) =>
202
+ evaluateCondition(condition, ctx.valueOf(conditionPath(scope, condition.field))),
203
+ });
204
+ }
205
+ }
206
+
207
+ /** Vacío para el propósito de «el padre todavía no eligió nada». */
208
+ function isEmptyValue(value: unknown): boolean {
209
+ if (value === undefined || value === null || value === '') {
210
+ return true;
211
+ }
212
+ return Array.isArray(value) && value.length === 0;
213
+ }
214
+
215
+ /**
216
+ * Path contra el que se evalúa una condición: el contenedor si nombra a un
217
+ * campo hermano, la raíz si no (base.md §4: «otro campo del mismo nivel o
218
+ * del registro raíz»).
219
+ */
220
+ function conditionPath(scope: RuleScope, key: string): RootPath {
221
+ const isSibling = scope.siblings.some((field) => field.key === key);
222
+ const target = isSibling ? scope.container : scope.root;
223
+ return target as RootPath;
224
+ }
225
+
226
+ /** Ítems de `string-list`: validan según el tipo declarado (§9: text/url/email). */
227
+ function applyStringItemRules(
228
+ path: ChildPath<string>,
229
+ itemType: 'text' | 'url' | 'email' | undefined,
230
+ ): void {
231
+ if (itemType === 'email') {
232
+ registerEmailRule(path);
233
+ }
234
+ if (itemType === 'url') {
235
+ registerUrlRule(path);
236
+ }
237
+ }
238
+
239
+ /**
240
+ * Navegación dinámica sobre el árbol de paths.
241
+ *
242
+ * Indexar `SchemaPathTree<Record<string, unknown>>` con una clave dinámica
243
+ * devuelve `SchemaPath<unknown, …> | undefined`: el `| undefined` es solo una
244
+ * consecuencia del index signature, y las reglas con valor concreto (`min`,
245
+ * `minLength`, `email`…) exigen afinar el tipo. Los casts de este módulo
246
+ * viven acá; los del registro, en `field-validators.ts` (solución reportada
247
+ * en plan.md, Fase 10).
248
+ */
249
+ function pathAt<V>(parent: object, key: string): ChildPath<V> {
250
+ const path = (parent as unknown as Record<string, unknown>)[key];
251
+ if (path === undefined) {
252
+ throw new Error(`El formulario no contiene la ruta "${key}".`);
253
+ }
254
+ return path as ChildPath<V>;
255
+ }