@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,199 @@
1
+ /**
2
+ * Registro de validadores — vocabulario y declaración (Plan 2 · F7 + B11).
3
+ *
4
+ * Dos capas se prueban acá, sin construir formularios:
5
+ *
6
+ * 1. **Vocabulario**: los nombres del registro son los del back
7
+ * (`api/engine/23-validate.js`, tabla `VALIDATORS_`); el contract check
8
+ * (`scripts/contract-check.mjs`) es quien los compara con la tabla real,
9
+ * y esta lista congelada evita que un nombre se caiga en silencio.
10
+ * 2. **`declares`**: qué validador pide cada campo. Es la parte que antes
11
+ * vivía en un `switch (field.type)` de `form-schema.ts` — un error acá es
12
+ * un campo sin validar (o un `required` bloqueando un booleano).
13
+ */
14
+ import { of } from 'rxjs';
15
+
16
+ import type { FieldSchema, ResourceSchema } from '../models/schema.model';
17
+ import type { ApiService } from '../services/api.service';
18
+ import {
19
+ FIELD_VALIDATORS,
20
+ VALIDATOR_NAMES,
21
+ type FieldPath,
22
+ type ValidatorContext,
23
+ } from './field-validators';
24
+
25
+ /** El vocabulario compartido, escrito a mano: caer de él tiene que doler. */
26
+ const VOCAB_ESPERADO = [
27
+ 'date-max',
28
+ 'date-min',
29
+ 'email',
30
+ 'max',
31
+ 'max-items',
32
+ 'max-length',
33
+ 'max-words',
34
+ 'min',
35
+ 'min-items',
36
+ 'min-length',
37
+ 'min-words',
38
+ 'pattern',
39
+ 'required',
40
+ 'unique',
41
+ 'url',
42
+ ];
43
+
44
+ function contexto(field: FieldSchema, list = '/api/items'): ValidatorContext {
45
+ const schema = {
46
+ id: 'items',
47
+ kind: 'collection',
48
+ endpoint: list === '' ? {} : { list },
49
+ keyField: 'key',
50
+ titleField: 'name',
51
+ listColumns: [],
52
+ fields: [field],
53
+ } as unknown as ResourceSchema;
54
+ return {
55
+ path: {} as FieldPath<unknown>,
56
+ field,
57
+ schema,
58
+ api: { list: () => of([]) } as unknown as ApiService,
59
+ excludeKey: undefined,
60
+ };
61
+ }
62
+
63
+ /** Nombres que el registro declara para un campo. */
64
+ function declarados(field: FieldSchema, list?: string): string[] {
65
+ const ctx = contexto(field, list);
66
+ return FIELD_VALIDATORS.filter((entry) => entry.declares(ctx)).map((entry) => entry.name);
67
+ }
68
+
69
+ describe('FIELD_VALIDATORS — vocabulario compartido con el back (B11)', () => {
70
+ it('declara exactamente los nombres de la lista congelada', () => {
71
+ expect([...VALIDATOR_NAMES].sort()).toEqual(VOCAB_ESPERADO);
72
+ });
73
+
74
+ it('sin nombres repetidos: un validador es una entrada', () => {
75
+ expect(new Set(VALIDATOR_NAMES).size).toBe(VALIDATOR_NAMES.length);
76
+ });
77
+
78
+ it('cada entrada del registro tiene nombre, declara y registra', () => {
79
+ for (const entry of FIELD_VALIDATORS) {
80
+ expect(typeof entry.declares).toBe('function');
81
+ expect(typeof entry.register).toBe('function');
82
+ expect(VOCAB_ESPERADO).toContain(entry.name);
83
+ }
84
+ });
85
+ });
86
+
87
+ describe('declares — qué validador pide cada campo', () => {
88
+ it('un campo simple sin reglas no declara nada', () => {
89
+ expect(declarados({ type: 'text', key: 'n', label: 'N' })).toEqual([]);
90
+ expect(declarados({ type: 'select', key: 's', label: 'S', options: [] })).toEqual([]);
91
+ });
92
+
93
+ it('required: sí en escalares, nunca en booleano, group ni listas', () => {
94
+ expect(declarados({ type: 'text', key: 'n', label: 'N', required: true })).toEqual([
95
+ 'required',
96
+ ]);
97
+ expect(declarados({ type: 'boolean', key: 'b', label: 'B', required: true })).toEqual([]);
98
+ expect(declarados({ type: 'group', key: 'g', label: 'G', required: true, fields: [] })).toEqual(
99
+ [],
100
+ );
101
+ // En listas `required` se vuelve min-items (un `[]` necesita un ítem).
102
+ expect(
103
+ declarados({ type: 'list', key: 'l', label: 'L', required: true, itemFields: [] }),
104
+ ).toEqual(['min-items']);
105
+ });
106
+
107
+ it('longitud y patrón: de `validators`, con `text` tomando los suyos', () => {
108
+ expect(
109
+ declarados({
110
+ type: 'text',
111
+ key: 'n',
112
+ label: 'N',
113
+ maxLength: 10,
114
+ pattern: '^\\d+$',
115
+ validators: { minLength: 2 },
116
+ }),
117
+ ).toEqual(['min-length', 'max-length', 'pattern']);
118
+
119
+ // `validators` tiene prioridad sobre la opción propia del tipo.
120
+ expect(
121
+ declarados({
122
+ type: 'text',
123
+ key: 'n',
124
+ label: 'N',
125
+ maxLength: 10,
126
+ validators: { maxLength: 4 },
127
+ }),
128
+ ).toEqual(['max-length']);
129
+
130
+ // `textarea` trae sus propios minWords/maxWords.
131
+ expect(
132
+ declarados({ type: 'textarea', key: 't', label: 'T', minWords: 3, maxWords: 12 }),
133
+ ).toEqual(['min-words', 'max-words']);
134
+ });
135
+
136
+ it('palabras: sólo en tipos de texto (un número no cuenta palabras)', () => {
137
+ expect(
138
+ declarados({ type: 'number', key: 'n', label: 'N', validators: { minWords: 3 } }),
139
+ ).toEqual([]);
140
+ expect(declarados({ type: 'text', key: 'n', label: 'N', validators: { minWords: 3 } })).toEqual(
141
+ ['min-words'],
142
+ );
143
+ });
144
+
145
+ it('rangos numéricos: `number` declara min/max propios, `currency` no', () => {
146
+ expect(declarados({ type: 'number', key: 'n', label: 'N', min: -90, max: 90 })).toEqual([
147
+ 'min',
148
+ 'max',
149
+ ]);
150
+ expect(declarados({ type: 'currency', key: 'c', label: 'C' })).toEqual([]);
151
+ expect(declarados({ type: 'currency', key: 'c', label: 'C', validators: { min: 0 } })).toEqual([
152
+ 'min',
153
+ ]);
154
+ });
155
+
156
+ it('conteo de ítems en los tres tipos de lista', () => {
157
+ expect(declarados({ type: 'string-list', key: 'x', label: 'X', min: 1, max: 4 })).toEqual([
158
+ 'min-items',
159
+ 'max-items',
160
+ ]);
161
+ expect(declarados({ type: 'key-value', key: 'k', label: 'K', max: 2 })).toEqual(['max-items']);
162
+ });
163
+
164
+ it('email/url y el rango de fechas salen de las props del tipo', () => {
165
+ expect(declarados({ type: 'email', key: 'e', label: 'E', required: true })).toEqual([
166
+ 'required',
167
+ 'email',
168
+ ]);
169
+ expect(
170
+ declarados({ type: 'url', key: 'u', label: 'U', validators: { maxLength: 60 } }),
171
+ ).toEqual(['url', 'max-length']);
172
+ expect(declarados({ type: 'date', key: 'd', label: 'D', min: '2026-01-01' })).toEqual([
173
+ 'date-min',
174
+ ]);
175
+ expect(
176
+ declarados({ type: 'date', key: 'd', label: 'D', min: '2026-01-01', max: '2026-12-31' }),
177
+ ).toEqual(['date-min', 'date-max']);
178
+ });
179
+
180
+ it('unique: sólo con `validators.unique`, valor de texto y listado', () => {
181
+ expect(
182
+ declarados({ type: 'text', key: 'sku', label: 'SKU', validators: { unique: true } }),
183
+ ).toEqual(['unique']);
184
+ expect(
185
+ declarados({
186
+ type: 'slug',
187
+ key: 'slug',
188
+ label: 'Slug',
189
+ from: 'name',
190
+ validators: { unique: true },
191
+ }),
192
+ ).toEqual(['unique']);
193
+ // Sin listado (singleton) no hay con qué verificarlo.
194
+ expect(
195
+ declarados({ type: 'text', key: 'sku', label: 'SKU', validators: { unique: true } }, ''),
196
+ ).toEqual([]);
197
+ expect(declarados({ type: 'text', key: 'sku', label: 'SKU' })).toEqual([]);
198
+ });
199
+ });
@@ -0,0 +1,440 @@
1
+ /**
2
+ * Registro de validadores del formulario (Plan 2 · F7 + B11 · R4.6).
3
+ *
4
+ * F7: «los validadores y reglas del formulario viven en un registro». Un
5
+ * validador es UNA entrada: `declares` dice si el campo lo pide (tipo + props
6
+ * del schema) y `register` ata la regla de Signal Forms a su ruta.
7
+ * `buildFormSchema` sólo recorre el registro — añadir un validador no toca
8
+ * `form-schema.ts`, que queda con lo que NO es validador: el ciclo de vida
9
+ * (visible/readonly/dependencias) y la estructura (hijos de `group`, ítems
10
+ * de `list`).
11
+ *
12
+ * B11: «mismos nombres que el back». `ValidatorName` es el vocabulario
13
+ * compartido con `api/engine/23-validate.js` (tabla `VALIDATORS_`): el
14
+ * contract check (`scripts/contract-check.mjs`) lo compara con esa tabla y
15
+ * un nombre de un lado sin su entrada del otro es ROJO. Así el front y el
16
+ * back hablan del mismo validador aunque cada uno pinte su mensaje.
17
+ *
18
+ * Los MENSAJES son los del front (más amables que los del back): lo que se
19
+ * comparte es el nombre, el código `VALIDATION_*` y el significado.
20
+ *
21
+ * Para añadir un validador: una entrada acá + su nombre en `ValidatorName` +
22
+ * su tabla en el back. Nada más.
23
+ */
24
+ import { resource } from '@angular/core';
25
+ import {
26
+ email as emailRule,
27
+ max as maxRule,
28
+ maxLength as maxLengthRule,
29
+ min as minRule,
30
+ minLength as minLengthRule,
31
+ pattern as patternRule,
32
+ required as requiredRule,
33
+ validate,
34
+ validateAsync,
35
+ type PathKind,
36
+ type SchemaPath,
37
+ type SchemaPathRules,
38
+ } from '@angular/forms/signals';
39
+ import { firstValueFrom } from 'rxjs';
40
+
41
+ import { validateMaxWords, validateMinWords } from '../../shared/utils/validators';
42
+ import type { FieldSchema, ListOptions, ResourceSchema } from '../models/schema.model';
43
+ import type { ApiService } from '../services/api.service';
44
+
45
+ /** Ruta de un campo en el árbol de Signal Forms. Los casts viven acá. */
46
+ export type FieldPath<V> = SchemaPath<V, SchemaPathRules.Supported, PathKind.Child>;
47
+
48
+ /**
49
+ * Vocabulario compartido con el back (`VALIDATORS_` en 23-validate.js).
50
+ *
51
+ * El orden es el de registro de `FIELD_VALIDATORS`, que fija el orden de los
52
+ * errores en `field.errors()`: el primero es el que pinta el campo.
53
+ */
54
+ export type ValidatorName =
55
+ | 'required'
56
+ | 'email'
57
+ | 'url'
58
+ | 'min-length'
59
+ | 'max-length'
60
+ | 'pattern'
61
+ | 'min-words'
62
+ | 'max-words'
63
+ | 'min'
64
+ | 'max'
65
+ | 'min-items'
66
+ | 'max-items'
67
+ | 'date-min'
68
+ | 'date-max'
69
+ | 'unique';
70
+
71
+ /** Todo lo que un validador necesita para decidir y para registrarse. */
72
+ export interface ValidatorContext {
73
+ path: FieldPath<unknown>;
74
+ field: FieldSchema;
75
+ schema: ResourceSchema;
76
+ api: ApiService;
77
+ excludeKey: string | undefined;
78
+ }
79
+
80
+ export interface ValidatorEntry {
81
+ readonly name: ValidatorName;
82
+ /** ¿Este campo declara este validador? (tipo + props del schema) */
83
+ readonly declares: (ctx: ValidatorContext) => boolean;
84
+ /** Ata la regla de Signal Forms a `ctx.path`. */
85
+ readonly register: (ctx: ValidatorContext) => void;
86
+ }
87
+
88
+ // ── Tipos ───────────────────────────────────────────────────────────────────
89
+
90
+ /** Tipos con valor de texto: son los que pueden llevar reglas de string. */
91
+ const TEXT_TYPES: ReadonlySet<string> = new Set([
92
+ 'text',
93
+ 'textarea',
94
+ 'email',
95
+ 'url',
96
+ 'slug',
97
+ 'phone',
98
+ 'time',
99
+ 'readonly-text',
100
+ 'select',
101
+ 'relation',
102
+ ]);
103
+
104
+ /** Tipos que son un arreglo con conmin: `required` se vuelve `min-items`. */
105
+ const LIST_TYPES: ReadonlySet<string> = new Set(['list', 'string-list', 'key-value']);
106
+
107
+ // ── Resolución de límites (los mismos `??` de siempre, en un solo lugar) ────
108
+
109
+ function minLengthOf(field: FieldSchema): number | undefined {
110
+ return TEXT_TYPES.has(field.type) ? field.validators?.minLength : undefined;
111
+ }
112
+
113
+ /** `validators` tiene prioridad; `text` además declara `maxLength` propio. */
114
+ function maxLengthOf(field: FieldSchema): number | undefined {
115
+ if (!TEXT_TYPES.has(field.type)) return undefined;
116
+ if (field.type === 'text') return field.validators?.maxLength ?? field.maxLength;
117
+ return field.validators?.maxLength;
118
+ }
119
+
120
+ function patternOf(field: FieldSchema): string | undefined {
121
+ if (!TEXT_TYPES.has(field.type)) return undefined;
122
+ if (field.type === 'text') return field.validators?.pattern ?? field.pattern;
123
+ return field.validators?.pattern;
124
+ }
125
+
126
+ /** `textarea` trae `minWords`/`maxWords` propios; el resto, sólo `validators`. */
127
+ function minWordsOf(field: FieldSchema): number | undefined {
128
+ if (!TEXT_TYPES.has(field.type)) return undefined;
129
+ if (field.type === 'textarea') return field.validators?.minWords ?? field.minWords;
130
+ return field.validators?.minWords;
131
+ }
132
+
133
+ function maxWordsOf(field: FieldSchema): number | undefined {
134
+ if (!TEXT_TYPES.has(field.type)) return undefined;
135
+ if (field.type === 'textarea') return field.validators?.maxWords ?? field.maxWords;
136
+ return field.validators?.maxWords;
137
+ }
138
+
139
+ /** `number` declara `min`/`max` propio; `currency` sólo en `validators`. */
140
+ function numberBoundOf(field: FieldSchema, bound: 'min' | 'max'): number | undefined {
141
+ if (field.type === 'number') {
142
+ return field.validators?.[bound] ?? (bound === 'min' ? field.min : field.max);
143
+ }
144
+ if (field.type === 'currency') return field.validators?.[bound];
145
+ return undefined;
146
+ }
147
+
148
+ /** Ítems mínimos: un `required` de lista exige al menos uno. */
149
+ function minItemsOf(field: FieldSchema): number | undefined {
150
+ if (!LIST_TYPES.has(field.type)) return undefined;
151
+ const list = field as FieldSchema & ListOptions;
152
+ return list.required ? Math.max(list.min ?? 1, 1) : list.min;
153
+ }
154
+
155
+ function maxItemsOf(field: FieldSchema): number | undefined {
156
+ if (!LIST_TYPES.has(field.type)) return undefined;
157
+ return (field as FieldSchema & ListOptions).max;
158
+ }
159
+
160
+ // ── Implementaciones compartidas ────────────────────────────────────────────
161
+
162
+ function toError(kind: string, message: string | null): { kind: string; message: string } | null {
163
+ return message === null ? null : { kind, message };
164
+ }
165
+
166
+ /**
167
+ * Mensaje cuando el `pattern` no coincide (§2: «nada de *Formato inválido*»).
168
+ *
169
+ * El error **reemplaza** a la ayuda bajo el campo (`field-host.ts:284`), así
170
+ * que si el mensaje no repite lo que decía la ayuda el usuario pierde el
171
+ * único texto que explica lo esperado: «11 dígitos.» se convierte en
172
+ * «Formato esperado: 11 dígitos.». La ayuda de las schemas ya está escrita
173
+ * en lenguaje humano, así que se reutiliza en vez de inventar otro.
174
+ */
175
+ function patternMessageFor(field: FieldSchema): string {
176
+ const hint = field.help?.trim().replace(/[.。]+$/, '');
177
+ return hint ? `Formato esperado: ${hint}.` : 'Revisa el formato de este campo.';
178
+ }
179
+
180
+ function validateUrl(value: unknown): string | null {
181
+ if (value === undefined || value === null || value === '') {
182
+ return null;
183
+ }
184
+ if (typeof value !== 'string') {
185
+ return URL_MESSAGE;
186
+ }
187
+ return /^https?:\/\/\S+$/i.test(value) ? null : URL_MESSAGE;
188
+ }
189
+
190
+ const URL_MESSAGE = 'Debe ser una URL válida (http o https).';
191
+
192
+ /** Fechas ISO `YYYY-MM-DD`: la comparación lexicográfica es correcta. */
193
+ function dateError(
194
+ value: unknown,
195
+ bound: 'min' | 'max',
196
+ iso: string,
197
+ ): { kind: string; message: string } | null {
198
+ if (typeof value !== 'string' || value === '') {
199
+ return null;
200
+ }
201
+ const outside = bound === 'min' ? value < iso : value > iso;
202
+ if (!outside) {
203
+ return null;
204
+ }
205
+ return {
206
+ kind: `${bound}-date`,
207
+ message:
208
+ bound === 'min'
209
+ ? `La fecha no puede ser anterior a ${iso}.`
210
+ : `La fecha no puede ser posterior a ${iso}.`,
211
+ };
212
+ }
213
+
214
+ /** Formato de URL para una ruta cualquiera (`string-list`, ítems de list). */
215
+ export function registerUrlRule(path: FieldPath<unknown>): void {
216
+ validate(path as FieldPath<string>, (ctx) => toError('url', validateUrl(ctx.value())));
217
+ }
218
+
219
+ /** Formato de correo para una ruta cualquiera (`string-list`, ítems de list). */
220
+ export function registerEmailRule(path: FieldPath<unknown>): void {
221
+ emailRule(path as FieldPath<string>, { message: 'Debe ser un correo electrónico válido.' });
222
+ }
223
+
224
+ /**
225
+ * Regla `unique`: `resource` contra el listado del recurso, descartando el
226
+ * propio registro. Si el listado no se puede leer (error de red) no se
227
+ * bloquea el formulario — `onError` falla abierto.
228
+ */
229
+ function registerUniqueRule(ctx: ValidatorContext): void {
230
+ const { path, field, schema, api, excludeKey } = ctx;
231
+ const endpoint = schema.endpoint;
232
+ const keyField = schema.keyField;
233
+ const fieldName = field.key;
234
+ const text = path as FieldPath<string>;
235
+
236
+ validateAsync(text, {
237
+ debounce: 300,
238
+ params: (c) => {
239
+ const value = c.value().trim();
240
+ return value === '' ? undefined : { value, exclude: excludeKey };
241
+ },
242
+ factory: (params) =>
243
+ resource({
244
+ // El `Signal` es a la vez la fuente de `params` del recurso y la que
245
+ // `validateAsync` controla: `undefined` deja el recurso en `idle`.
246
+ params,
247
+ loader: async ({ params: request }) => {
248
+ const rows = await firstValueFrom(api.list<Record<string, unknown>>(endpoint));
249
+ if (!Array.isArray(rows)) {
250
+ return [];
251
+ }
252
+ return rows.filter(
253
+ (row) =>
254
+ row[fieldName] === request.value &&
255
+ (request.exclude === undefined || row[keyField] !== request.exclude),
256
+ );
257
+ },
258
+ }),
259
+ onError: () => null,
260
+ onSuccess: (rows, c) => {
261
+ const value = c.value().trim();
262
+ if (value === '' || !Array.isArray(rows) || rows.length === 0) {
263
+ return null;
264
+ }
265
+ return { kind: 'unique', message: 'Este valor ya existe en el registro.' };
266
+ },
267
+ });
268
+ }
269
+
270
+ // ── El registro ─────────────────────────────────────────────────────────────
271
+
272
+ export const FIELD_VALIDATORS: readonly ValidatorEntry[] = [
273
+ {
274
+ // `boolean`: `isEmpty` de Signal Forms considera `false` vacío, así que un
275
+ // booleano obligatorio bloquearía el guardado al desmarcarlo → se omite.
276
+ // `group`: su valor es un objeto y un objeto nunca está vacío → sin regla.
277
+ // Listas: lo resuelve `min-items` (un `[]` necesita al menos un ítem).
278
+ name: 'required',
279
+ declares: ({ field }) =>
280
+ field.required === true &&
281
+ field.type !== 'boolean' &&
282
+ field.type !== 'group' &&
283
+ !LIST_TYPES.has(field.type),
284
+ register: ({ path }) => requiredRule(path, { message: 'Campo obligatorio.' }),
285
+ },
286
+ {
287
+ name: 'email',
288
+ declares: ({ field }) => field.type === 'email',
289
+ register: ({ path }) => registerEmailRule(path),
290
+ },
291
+ {
292
+ name: 'url',
293
+ declares: ({ field }) => field.type === 'url',
294
+ register: ({ path }) => registerUrlRule(path),
295
+ },
296
+ {
297
+ name: 'min-length',
298
+ declares: ({ field }) => minLengthOf(field) !== undefined,
299
+ register: ({ path, field }) => {
300
+ const min = minLengthOf(field);
301
+ if (min !== undefined) {
302
+ minLengthRule(path as FieldPath<string>, min, {
303
+ message: `Debe tener al menos ${min} caracteres.`,
304
+ });
305
+ }
306
+ },
307
+ },
308
+ {
309
+ name: 'max-length',
310
+ declares: ({ field }) => maxLengthOf(field) !== undefined,
311
+ register: ({ path, field }) => {
312
+ const max = maxLengthOf(field);
313
+ if (max !== undefined) {
314
+ maxLengthRule(path as FieldPath<string>, max, {
315
+ message: `Como máximo ${max} caracteres.`,
316
+ });
317
+ }
318
+ },
319
+ },
320
+ {
321
+ name: 'pattern',
322
+ declares: ({ field }) => patternOf(field) !== undefined,
323
+ register: ({ path, field }) => {
324
+ const pattern = patternOf(field);
325
+ if (pattern !== undefined) {
326
+ patternRule(path as FieldPath<string>, new RegExp(pattern), {
327
+ message: patternMessageFor(field),
328
+ });
329
+ }
330
+ },
331
+ },
332
+ {
333
+ name: 'min-words',
334
+ declares: ({ field }) => minWordsOf(field) !== undefined,
335
+ register: ({ path, field }) => {
336
+ const min = minWordsOf(field);
337
+ if (min !== undefined) {
338
+ validate(path as FieldPath<string>, (ctx) =>
339
+ toError('min-words', validateMinWords(ctx.value(), min)),
340
+ );
341
+ }
342
+ },
343
+ },
344
+ {
345
+ name: 'max-words',
346
+ declares: ({ field }) => maxWordsOf(field) !== undefined,
347
+ register: ({ path, field }) => {
348
+ const max = maxWordsOf(field);
349
+ if (max !== undefined) {
350
+ validate(path as FieldPath<string>, (ctx) =>
351
+ toError('max-words', validateMaxWords(ctx.value(), max)),
352
+ );
353
+ }
354
+ },
355
+ },
356
+ {
357
+ name: 'min',
358
+ declares: ({ field }) => numberBoundOf(field, 'min') !== undefined,
359
+ register: ({ path, field }) => {
360
+ const min = numberBoundOf(field, 'min');
361
+ if (min !== undefined) {
362
+ minRule(path as FieldPath<number | null>, min, {
363
+ message: `Debe ser como mínimo ${min}.`,
364
+ });
365
+ }
366
+ },
367
+ },
368
+ {
369
+ name: 'max',
370
+ declares: ({ field }) => numberBoundOf(field, 'max') !== undefined,
371
+ register: ({ path, field }) => {
372
+ const max = numberBoundOf(field, 'max');
373
+ if (max !== undefined) {
374
+ maxRule(path as FieldPath<number | null>, max, {
375
+ message: `Debe ser como máximo ${max}.`,
376
+ });
377
+ }
378
+ },
379
+ },
380
+ {
381
+ name: 'min-items',
382
+ declares: ({ field }) => minItemsOf(field) !== undefined,
383
+ register: ({ path, field }) => {
384
+ const min = minItemsOf(field);
385
+ if (min !== undefined) {
386
+ minLengthRule(path as FieldPath<unknown[]>, min, {
387
+ message:
388
+ min === 1 ? 'Debe tener al menos 1 elemento.' : `Debe tener al menos ${min} elementos.`,
389
+ });
390
+ }
391
+ },
392
+ },
393
+ {
394
+ name: 'max-items',
395
+ declares: ({ field }) => maxItemsOf(field) !== undefined,
396
+ register: ({ path, field }) => {
397
+ const max = maxItemsOf(field);
398
+ if (max !== undefined) {
399
+ maxLengthRule(path as FieldPath<unknown[]>, max, {
400
+ message: max === 1 ? 'Como máximo 1 elemento.' : `Como máximo ${max} elementos.`,
401
+ });
402
+ }
403
+ },
404
+ },
405
+ {
406
+ name: 'date-min',
407
+ declares: ({ field }) => field.type === 'date' && field.min !== undefined,
408
+ register: ({ path, field }) => {
409
+ const min = field.type === 'date' ? field.min : undefined;
410
+ if (min !== undefined) {
411
+ validate(path as FieldPath<string>, (ctx) => dateError(ctx.value(), 'min', min));
412
+ }
413
+ },
414
+ },
415
+ {
416
+ name: 'date-max',
417
+ declares: ({ field }) => field.type === 'date' && field.max !== undefined,
418
+ register: ({ path, field }) => {
419
+ const max = field.type === 'date' ? field.max : undefined;
420
+ if (max !== undefined) {
421
+ validate(path as FieldPath<string>, (ctx) => dateError(ctx.value(), 'max', max));
422
+ }
423
+ },
424
+ },
425
+ {
426
+ // Un singleton no tiene listado, así que no hay cómo verificarlo y se omite.
427
+ // Cualquier campo de valor texto puede ser único: `text`, `slug`, `select`…
428
+ // (antes sólo los tipos sin `case` propio, y `products.sku` — `text` —
429
+ // se quedaba sin regla aunque el schema la declaraba y el back la exige).
430
+ name: 'unique',
431
+ declares: ({ field, schema }) =>
432
+ field.validators?.unique === true && TEXT_TYPES.has(field.type) && !!schema.endpoint.list,
433
+ register: (ctx) => registerUniqueRule(ctx),
434
+ },
435
+ ];
436
+
437
+ /** Nombres del registro: lo que `contract-check.mjs` compara con el back. */
438
+ export const VALIDATOR_NAMES: readonly ValidatorName[] = FIELD_VALIDATORS.map(
439
+ (entry) => entry.name,
440
+ );