@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
package/README.md CHANGED
@@ -310,6 +310,228 @@ The shell starts at `/login` and, once authenticated, shows the sidebar menu and
310
310
 
311
311
  ---
312
312
 
313
+ ## Todos los tipos de campo del formulario
314
+
315
+ Esta es la referencia completa de todos los tipos de campo que puedes declarar en `ResourceSchema.fields`. Cada tipo tiene su propio comportamiento de renderizado, validación y propiedades específicas.
316
+
317
+ | Tipo | Descripción | Uso típico |
318
+ |------|-------------|------------|
319
+ | `text` | Input de una línea para texto corto | Nombres, slugs, URLs cortas |
320
+ | `textarea` | Área de texto multilínea | Descripciones largas, notas |
321
+ | `slug` | Slug generado automáticamente de otro campo | Identificadores amigables para SEO |
322
+ | `number` | Campo numérico con controles de incremento | Cantidades, precios, edades |
323
+ | `currency` | Campo monetario con formato de moneda | Precios, montos, saldos |
324
+ | `boolean` | Interruptor (toggle) o casilla de verificación | Estados activos/inactivos |
325
+ | `select` | Desplegable con radios para 2-4 opciones o dropdown para más | Estados, categorías pequeñas |
326
+ | `dropdown` | Desplegable nativo (`<select>`) SIEMPRE, sin mutar a radios | Estados con muchas opciones |
327
+ | `rich-select` | Selector visual con tarjetas y preview HTML personalizado | Opciones con iconos, colores, estados visuales |
328
+ | `multiselect` | Selección múltiple de opciones | Tags, etiquetas múltiples |
329
+ | `url` | Campo para URLs válidas | Enlaces externos, sitios web |
330
+ | `email` | Campo de email con validación de formato | Correos electrónicos |
331
+ | `phone` | Campo de teléfono con formato telefónico | Números de teléfono |
332
+ | `date` | Selector de fecha (ISO `YYYY-MM-DD`) | Fechas de nacimiento, citas |
333
+ | `time` | Selector de hora | Horas de apertura, citas |
334
+ | `readonly-text` | Texto de solo lectura que no se envía al back | IDs generados, timestamps |
335
+ | `relation` | Relación con otro recurso (fuente de datos) | Clientes, productos, usuarios relacionados |
336
+ | `image` | Subida y vista previa de imágenes | Logos, fotos de perfil |
337
+ | `file` | Subida de archivos genéricos (PDF, DOCX, etc.) | Documentos, adjuntos |
338
+ | `group` | Grupo de campos con tarjeta, colapsable o inline | Secciones de formulario relacionadas |
339
+ | `list` | Lista repetible de campos (items) | Múltiples direcciones, teléfonos |
340
+ | `string-list` | Lista simple de strings | Tags simples, palabras clave |
341
+ | `key-value` | Tabla de pares clave-valor | Metadatos, configuraciones personalizadas |
342
+ | `color` | Selector de color nativo (`<input type="color">`) | Colores primarios, secundarios, temas |
343
+
344
+ ### Ejemplos por tipo
345
+
346
+ A continuación verás cómo declarar cada tipo en tu schema, con sus propiedades específicas:
347
+
348
+ #### `text` — Input de una línea
349
+
350
+ ```ts
351
+ { key: 'name', type: 'text', label: 'Nombre del Negocio', required: true, validators: { minLength: 2, maxLength: 120 }, section: 'general', width: 6 }
352
+ ```
353
+
354
+ #### `textarea` — Área de texto multilínea
355
+
356
+ ```ts
357
+ { key: 'description', type: 'textarea', label: 'Descripción', validators: { maxLength: 500 }, section: 'general', width: 12 }
358
+ ```
359
+
360
+ #### `slug` — Identificador amigable
361
+
362
+ ```ts
363
+ { key: 'slug', type: 'slug', from: 'name', label: 'Slug', required: true, lockAfterCreate: true, section: 'general', width: 6 }
364
+ ```
365
+
366
+ #### `number` — Campo numérico
367
+
368
+ ```ts
369
+ { key: 'minVisits', type: 'number', label: 'Visitas Mínimas', required: true, min: 0, section: 'general', width: 6 }
370
+ ```
371
+
372
+ #### `currency` — Campo monetario
373
+
374
+ ```ts
375
+ { key: 'price', type: 'currency', label: 'Precio', validators: { min: 0 }, section: 'pricing', width: 6 }
376
+ ```
377
+
378
+ #### `boolean` — Interruptor / Casilla
379
+
380
+ ```ts
381
+ { key: 'active', type: 'boolean', label: 'Activo', section: 'general', width: 3 }
382
+ ```
383
+
384
+ #### `select` — Desplegable (radios si 2-4 opciones, dropdown si más)
385
+
386
+ ```ts
387
+ { key: 'status', type: 'select', label: 'Estado', required: true, options: [
388
+ { value: 'active', label: 'Activo' },
389
+ { value: 'inactive', label: 'Inactivo' },
390
+ { value: 'pending', label: 'Pendiente' },
391
+ ], section: 'general', width: 6 }
392
+ ```
393
+
394
+ #### `dropdown` — Desplegable nativo SIEMPRE
395
+
396
+ ```ts
397
+ { key: 'timezone', type: 'dropdown', label: 'Zona Horaria', required: true, section: 'general', width: 6, default: 'America/Lima', options: [
398
+ { value: 'America/Lima', label: 'Lima (Perú)' },
399
+ { value: 'America/Bogota', label: 'Bogotá (Colombia)' },
400
+ { value: 'America/Mexico_City', label: 'Ciudad de México' },
401
+ ], }
402
+ ```
403
+
404
+ #### `rich-select` — Selector visual con tarjetas
405
+
406
+ ```ts
407
+ { key: 'cardStyle', type: 'rich-select', label: 'Estilo de Tarjeta', cardWidth: 3, section: 'branding', width: 12,
408
+ options: [
409
+ { value: 'none', label: 'Ninguno', preview: '<span class="w-8 h-8 bg-amber-300 rounded-sm flex items-center justify-center text-xs font-medium text-amber-900">Ninguna</span>' },
410
+ { value: 'monthly', label: 'Mensual', preview: '<span class="w-8 h-8 bg-blue-100 rounded-sm flex items-center justify-center text-xs font-medium text-blue-900">Mensual</span>' },
411
+ { value: 'yearly', label: 'Anual', preview: '<span class="w-8 h-8 bg-green-100 rounded-sm flex items-center justify-center text-xs font-medium text-green-900">Anual</span>' },
412
+ ], }
413
+ ```
414
+
415
+ #### `multiselect` — Selección múltiple
416
+
417
+ ```ts
418
+ { key: 'tags', type: 'multiselect', label: 'Etiquetas', section: 'tags', width: 12, options: [
419
+ { value: 'vip', label: 'Cliente VIP' },
420
+ { value: 'new', label: 'Cliente Nuevo' },
421
+ { value: 'inactive', label: 'Inactivo' },
422
+ ], }
423
+ ```
424
+
425
+ #### `url` — Campo de URL
426
+
427
+ ```ts
428
+ { key: 'website', type: 'url', label: 'Sitio Web', placeholder: 'https://ejemplo.com', section: 'contact', width: 12 }
429
+ ```
430
+
431
+ #### `email` — Campo de correo electrónico
432
+
433
+ ```ts
434
+ { key: 'email', type: 'email', label: 'Correo Electrónico', required: true, section: 'contact', width: 12 }
435
+ ```
436
+
437
+ #### `phone` — Campo de teléfono
438
+
439
+ ```ts
440
+ { key: 'phone', type: 'phone', label: 'Teléfono', section: 'contact', width: 6 }
441
+ ```
442
+
443
+ #### `date` — Selector de fecha
444
+
445
+ ```ts
446
+ { key: 'birthDate', type: 'date', label: 'Fecha de Nacimiento', section: 'personal', width: 6 }
447
+ ```
448
+
449
+ #### `time` — Selector de hora
450
+
451
+ ```ts
452
+ { key: 'openingTime', type: 'time', label: 'Hora de Apertura', section: 'hours', width: 6 }
453
+ ```
454
+
455
+ #### `readonly-text` — Texto de solo lectura
456
+
457
+ ```ts
458
+ { key: 'memberId', type: 'readonly-text', label: 'ID Membresía', readonly: true, section: 'general', width: 6 }
459
+ ```
460
+
461
+ #### `relation` — Relación con otro recurso
462
+
463
+ ```ts
464
+ { key: 'clientId', type: 'relation', label: 'Cliente', source: { resource: 'clients', labelField: 'name', valueField: 'membershipId' }, section: 'association', width: 12 }
465
+ ```
466
+
467
+ #### `image` — Subida de imagen
468
+
469
+ ```ts
470
+ { key: 'logoUrl', type: 'image', label: 'Logo', section: 'branding', width: 12, accept: 'image/*', maxSizeMB: 2 }
471
+ ```
472
+
473
+ #### `file` — Subida de archivo genérico
474
+
475
+ ```ts
476
+ { key: 'document', type: 'file', label: 'Documento', section: 'files', width: 12, accept: '.pdf,.doc,.docx', maxSizeMB: 5 }
477
+ ```
478
+
479
+ #### `group` — Grupo de campos
480
+
481
+ ```ts
482
+ { key: 'address', type: 'group', label: 'Dirección', display: 'card', section: 'location', width: 12, fields: [
483
+ { key: 'street', type: 'text', label: 'Calle', width: 6 },
484
+ { key: 'city', type: 'text', label: 'Ciudad', width: 6 },
485
+ ]},
486
+ ```
487
+
488
+ #### `list` — Lista repetible de items
489
+
490
+ ```ts
491
+ { key: 'phones', type: 'list', label: 'Teléfonos', itemFields: [
492
+ { key: 'number', type: 'phone', label: 'Número', width: 6 },
493
+ { key: 'type', type: 'select', label: 'Tipo', options: [{ value: 'mobile', label: 'Móvil' }, { value: 'home', label: 'Hogar' }], width: 6 },
494
+ ], section: 'contact', width: 12 }
495
+ ```
496
+
497
+ #### `string-list` — Lista simple de strings
498
+
499
+ ```ts
500
+ { key: 'skills', type: 'string-list', label: 'Habilidades', itemType: 'text', section: 'skills', width: 12 }
501
+ ```
502
+
503
+ #### `key-value` — Pares clave-valor
504
+
505
+ ```ts
506
+ { key: 'metadata', type: 'key-value', label: 'Metadatos', section: 'advanced', width: 12 }
507
+ ```
508
+
509
+ #### `color` — Selector de color
510
+
511
+ ```ts
512
+ { key: 'colorPrimary', type: 'color', label: 'Color Primario', placeholder: '#4A2C11', section: 'branding', width: 6, default: '#4A2C11' }
513
+ ```
514
+
515
+ ### Propiedades comunes a todos los campos
516
+
517
+ | Propiedad | Tipo | Descripción |
518
+ |-----------|------|-------------|
519
+ | `key` | `string` | Nombre de la propiedad en el JSON que envía/recibe el back. |
520
+ | `label` | `string` | Rótulo visible para el usuario en el formulario y la ficha. |
521
+ | `help` | `string` | Texto corto de ayuda que se muestra debajo del campo. |
522
+ | `placeholder` | `string` | Texto sugerente que se muestra dentro del input cuando está vacío. |
523
+ | `required` | `boolean` | Si es obligatorio. El back también lo valida (R7.4). |
524
+ | `default` | `any` | Valor por defecto si el registro no trae este campo (ej. `default: 'none'` en un `select`/`dropdown`). |
525
+ | `readonly` | `boolean` | Si el campo es solo lectura (no editable en el formulario). |
526
+ | `width` | `1-12` | Ancho en la grilla del formulario (1–12), igual que un widget del dashboard. |
527
+ | `section` | `string` | Id de la sección/pestaña del layout a la que pertenece (base.md §4). |
528
+ | `description` | `string` | Descripción corta de para qué sirve este campo. Se muestra como tooltip al pasar el cursor sobre el rótulo. |
529
+ | `docLink` | `string` | URL de documentación externa para este campo (se abre al hacer clic en el tooltip). |
530
+ | `validators` | `object` | Reglas de validación (`minLength`, `maxLength`, `pattern`, `min`, `max`, `unique`). |
531
+ | `access` | `object` | Nivel de acceso requerido (`read`, `write`) — solo UX, el back vuelve a verificar en cada petición. |
532
+
533
+ ---
534
+
313
535
  ## Schema examples (real implementations)
314
536
 
315
537
  The examples below are taken from a production consumer of this library.
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
3
+ "dest": "../../dist/gas-admin-ui",
4
+ "lib": {
5
+ "entryFile": "src/public-api.ts"
6
+ }
7
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tinya-code/gas-admin-ui",
3
- "version": "0.2.0-alpha.12",
3
+ "version": "0.2.0-alpha.15",
4
4
  "description": "Motor de render Angular para admin CRUD: declará ResourceSchema y el motor dibuja vistas, campos y widgets.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -28,17 +28,5 @@
28
28
  "dependencies": {
29
29
  "tslib": "^2.3.0"
30
30
  },
31
- "sideEffects": false,
32
- "module": "fesm2022/tinya-code-gas-admin-ui.mjs",
33
- "typings": "types/tinya-code-gas-admin-ui.d.ts",
34
- "exports": {
35
- "./package.json": {
36
- "default": "./package.json"
37
- },
38
- ".": {
39
- "types": "./types/tinya-code-gas-admin-ui.d.ts",
40
- "default": "./fesm2022/tinya-code-gas-admin-ui.mjs"
41
- }
42
- },
43
- "type": "module"
44
- }
31
+ "sideEffects": false
32
+ }
@@ -0,0 +1,52 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import {
4
+ actionType,
5
+ actionsFor,
6
+ getAction,
7
+ registerAction,
8
+ type ActionDescriptor,
9
+ } from './action-registry';
10
+
11
+ function descriptor(
12
+ type: string,
13
+ surfaces: ActionDescriptor['surfaces'] = ['row'],
14
+ ): ActionDescriptor {
15
+ return { type, surfaces, run: async () => ({ ok: true }) };
16
+ }
17
+
18
+ describe('action-registry', () => {
19
+ it('registra un descriptor y lo devuelve por su tipo', () => {
20
+ registerAction(descriptor('compartir'));
21
+ expect(getAction('compartir')?.type).toBe('compartir');
22
+ });
23
+
24
+ it('devuelve undefined para un tipo sin registrar', () => {
25
+ expect(getAction('sin-registrar')).toBeUndefined();
26
+ });
27
+
28
+ it('al re-registrar un tipo reemplaza el descriptor anterior', () => {
29
+ registerAction(descriptor('navegar', ['detail']));
30
+ registerAction(descriptor('navegar', ['row', 'detail']));
31
+ expect(getAction('navegar')?.surfaces).toEqual(['row', 'detail']);
32
+ });
33
+
34
+ it('lista sólo los descriptores cuya superficie coincide', () => {
35
+ registerAction(descriptor('cabecera', ['header']));
36
+ expect(actionsFor('header').map((d) => d.type)).toEqual(['cabecera']);
37
+ expect(actionsFor('bulk')).toEqual([]);
38
+ });
39
+ });
40
+
41
+ describe('actionType', () => {
42
+ it('usa el tipo declarado', () => {
43
+ expect(actionType({ id: 'x', label: 'X', type: 'trigger' })).toBe('trigger');
44
+ expect(actionType({ id: 'x', label: 'X', type: 'copy-share', urlTemplate: '/p/{ref}' })).toBe(
45
+ 'copy-share',
46
+ );
47
+ });
48
+
49
+ it('una acción sin type es una navegación (NavigateAction.type es opcional)', () => {
50
+ expect(actionType({ id: 'x', label: 'X', urlTemplate: '/p/{ref}' })).toBe('navigate');
51
+ });
52
+ });
@@ -0,0 +1,126 @@
1
+ import type { Router } from '@angular/router';
2
+
3
+ import type { LucideIcon } from '@lucide/angular';
4
+
5
+ import {
6
+ actionDescriptors,
7
+ isShareAction,
8
+ isTriggerAction,
9
+ resolveShare,
10
+ type ResolvedShare,
11
+ } from './descriptors';
12
+ import type { ResourceAction } from '../core/models/schema.model';
13
+ import type { SessionUser } from '../core/services/auth.service';
14
+ import type { ApiService } from '../core/services/api.service';
15
+ import type { NotificationService } from '../core/services/notification.service';
16
+
17
+ /**
18
+ * Registro único de acciones (R3.9 · `refactormotor.md`).
19
+ *
20
+ * Es el gemelo del registro de render (`core/registry/render-registry.ts`):
21
+ * en lugar de «qué componente pinta un tipo de campo», responde «qué
22
+ * comportamiento ejecuta un tipo de acción» y permite **migrar el dispatch
23
+ * if/copiado-pegado de las vistas (R3.11) a datos + un solo `run`**.
24
+ *
25
+ * Como los descriptores son objetos planos de módulo (no los inyecta Angular),
26
+ * todo lo que necesiten llegar por el `ActionContext`: los puertos (`api`,
27
+ * `router`, `toast`) y los datos (`record`, `resource`, `vars`).
28
+ */
29
+
30
+ /** Superficies donde una acción puede aparecer. */
31
+ export type ActionSurface = 'row' | 'detail' | 'header' | 'bulk' | 'post-create';
32
+
33
+ /**
34
+ * Lo que aporta el llamador al ejecutar una acción: el registro sobre el que
35
+ * corre y los puertos que el descriptor usará para actuar.
36
+ *
37
+ * `record` es `null` en las superficies sin fila (`header`, `bulk`), para que
38
+ * un descriptor pueda distinguir «sin registro» de «registro vacío».
39
+ */
40
+ export interface ActionRequest {
41
+ record: Record<string, unknown> | null;
42
+ /** Id del recurso (schema) dueño de la acción. */
43
+ resource: string;
44
+ api: ApiService;
45
+ router: Router;
46
+ toast: NotificationService;
47
+ user: SessionUser | null;
48
+ /**
49
+ * `keyField` del schema. **R3.12 (desviación nº 3 del contrato):** con
50
+ * `record` forma la clave que necesita la ruta lógica de `trigger`
51
+ * (`/admin/<recurso>/<key>/actions/<name>`). Lo aporta la vista, que es
52
+ * quien tiene el schema.
53
+ */
54
+ keyField: string;
55
+ }
56
+
57
+ /**
58
+ * Contexto que recibe el descriptor: la petición del llamador más el contexto
59
+ * de interpolación ya resuelto. `ActionService.run` lo arma, así que ninguna
60
+ * vista repite `buildInterpolationContext(record)`.
61
+ */
62
+ export interface ActionContext extends ActionRequest {
63
+ /** `{...configGlobal, ...record, public_base_url}` — listo para las plantillas. */
64
+ vars: Record<string, unknown>;
65
+ }
66
+
67
+ /** Resultado que la vista puede leer para saber si debe recargar. */
68
+ export interface ActionResult {
69
+ /** `false` cuando la acción no llegó a ejecutarse; el error ya se notificó. */
70
+ ok: boolean;
71
+ /** `true` cuando tocó datos y la vista debe recargar su listado/detalle. */
72
+ reload?: boolean;
73
+ }
74
+
75
+ export interface ActionDescriptor {
76
+ /** Clave del dispatch: el `type` del schema (`'navigate'` no lo declara). */
77
+ type: string;
78
+ /** Superficies donde esta acción puede aparecer. */
79
+ surfaces: ActionSurface[];
80
+ run: (ctx: ActionContext, action: ResourceAction) => Promise<ActionResult>;
81
+ /** `true` si el descriptor exige confirmación antes de ejecutar. */
82
+ confirm?: boolean;
83
+ /**
84
+ * Icono lucide que representa la acción donde el espacio es reducido
85
+ * (la fila del listado). Sin él, la vista muestra `action.label`.
86
+ */
87
+ icon?: LucideIcon;
88
+ }
89
+
90
+ export { isShareAction, isTriggerAction, resolveShare, type ResolvedShare };
91
+
92
+ const ACCIONES = new Map<string, ActionDescriptor>();
93
+
94
+ /** Registra (o reemplaza) un descriptor de acción. */
95
+ export function registerAction(descriptor: ActionDescriptor): void {
96
+ ACCIONES.set(descriptor.type, descriptor);
97
+ }
98
+
99
+ export function getAction(type: string): ActionDescriptor | undefined {
100
+ return ACCIONES.get(type);
101
+ }
102
+
103
+ /** Descriptores disponibles en una superficie. */
104
+ export function actionsFor(surface: ActionSurface): ActionDescriptor[] {
105
+ return [...ACCIONES.values()].filter((descriptor) => descriptor.surfaces.includes(surface));
106
+ }
107
+
108
+ /**
109
+ * Clave de dispatch de una acción. `NavigateAction.type` es **opcional**
110
+ * (schema.model.ts), así que una acción sin `type` es una navegación.
111
+ */
112
+ export function actionType(action: ResourceAction): string {
113
+ return action.type ?? 'navigate';
114
+ }
115
+
116
+ /** Tipos registrados. Sólo para comprobaciones y tests. */
117
+ export function actionTypes(): string[] {
118
+ return [...ACCIONES.keys()];
119
+ }
120
+
121
+ // Catálogo a scope de módulo: se registra al importar, sin esperar a DI,
122
+ // igual que el registro de render (mismo motivo: `buildFormSchema()` es una
123
+ // función pura que consulta sin inyectar nada).
124
+ for (const descriptor of actionDescriptors) {
125
+ registerAction(descriptor);
126
+ }
@@ -0,0 +1,215 @@
1
+ import { TestBed } from '@angular/core/testing';
2
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
3
+
4
+ import { ActionService } from './action-service';
5
+ import { registerAction, type ActionContext, type ActionDescriptor } from './action-registry';
6
+ import { actionDescriptors } from './descriptors';
7
+ import type { ResourceAction } from '../core/models/schema.model';
8
+ import type { ApiService } from '../core/services/api.service';
9
+ import { ConfigService } from '../core/services/config.service';
10
+ import type { NotificationService } from '../core/services/notification.service';
11
+ import type { Router } from '@angular/router';
12
+
13
+ /** Notificador falso: sólo el contrato que `ActionService` usa. */
14
+ function fakeToast(): NotificationService & { confirm: ReturnType<typeof vi.fn> } {
15
+ return {
16
+ confirm: vi.fn().mockResolvedValue(true),
17
+ error: vi.fn(),
18
+ success: vi.fn(),
19
+ info: vi.fn(),
20
+ dismiss: vi.fn(),
21
+ } as unknown as NotificationService & { confirm: ReturnType<typeof vi.fn> };
22
+ }
23
+
24
+ function fakeRequest(toast: NotificationService) {
25
+ return {
26
+ record: { slug: 'tecla', ref: 'p1' },
27
+ resource: 'productos',
28
+ api: {} as ApiService,
29
+ router: { navigate: async () => true } as unknown as Router,
30
+ toast,
31
+ user: null,
32
+ keyField: 'ref',
33
+ };
34
+ }
35
+
36
+ const COMPARTIR: ResourceAction = {
37
+ id: 'compartir',
38
+ label: 'Compartir',
39
+ type: 'copy-share',
40
+ urlTemplate: 'https://demo/p/{ref}',
41
+ };
42
+
43
+ describe('ActionService', () => {
44
+ let service: ActionService;
45
+ let toast: ReturnType<typeof fakeToast>;
46
+ const ensureLoaded = vi.fn<() => Promise<void>>().mockResolvedValue(undefined);
47
+
48
+ beforeEach(() => {
49
+ toast = fakeToast();
50
+ ensureLoaded.mockClear();
51
+ ensureLoaded.mockResolvedValue(undefined);
52
+ TestBed.configureTestingModule({
53
+ providers: [
54
+ // ConfigService real pediría HttpClient/API_URL; sólo aporta `vars`
55
+ // y el reintento de config que `copy-share` dispara.
56
+ {
57
+ provide: ConfigService,
58
+ useValue: {
59
+ ensureLoaded,
60
+ buildInterpolationContext: (record: Record<string, unknown>) => ({
61
+ ...record,
62
+ public_base_url: 'https://demo',
63
+ }),
64
+ },
65
+ },
66
+ ],
67
+ });
68
+ service = TestBed.inject(ActionService);
69
+ });
70
+
71
+ // El `Map` del registro es estado de módulo **compartido entre ficheros**
72
+ // cuando vitest los ejecuta en el mismo worker: varios tests de éste
73
+ // sobrescriben `copy-share` / `navigate` / `trigger` con dobles.
74
+ // Sin restaurar aquí, `contract.spec` y `descriptors.spec` leen esos dobles
75
+ // y la suite se pone flaka **según el orden de ficheros** (falló 4 tests de
76
+ // `descriptors.spec` en una corrida y 0 en la siguiente, sin tocar código).
77
+ // Quien ensucia es quien limpia: el estado por defecto es el catálogo real.
78
+ afterEach(() => {
79
+ for (const descriptor of actionDescriptors) registerAction(descriptor);
80
+ });
81
+
82
+ it('ejecuta el descriptor y le entrega la petición + vars de interpolación', async () => {
83
+ const vistos: ActionContext[] = [];
84
+ registerAction({
85
+ type: 'copy-share',
86
+ surfaces: ['row', 'post-create'],
87
+ run: async (ctx) => {
88
+ vistos.push(ctx);
89
+ return { ok: true, reload: true };
90
+ },
91
+ });
92
+
93
+ const resultado = await service.run(COMPARTIR, fakeRequest(toast));
94
+
95
+ expect(resultado).toEqual({ ok: true, reload: true });
96
+ expect(vistos).toHaveLength(1);
97
+ expect(vistos[0].record).toEqual({ slug: 'tecla', ref: 'p1' });
98
+ expect(vistos[0].resource).toBe('productos');
99
+ expect(vistos[0].vars).toEqual({ slug: 'tecla', ref: 'p1', public_base_url: 'https://demo' });
100
+ });
101
+
102
+ it('copy-share reintenta la config global ANTES de resolver vars', async () => {
103
+ // Regresión del «mensaje = sólo la URL»: si el load del bootstrap falló,
104
+ // el descriptor debe ver la config ya recargada, no el snapshot vacío.
105
+ let ensureHecho = false;
106
+ ensureLoaded.mockImplementation(async () => {
107
+ ensureHecho = true;
108
+ });
109
+ const varsAlEjecutar: Record<string, unknown>[] = [];
110
+ registerAction({
111
+ type: 'copy-share',
112
+ surfaces: ['row'],
113
+ run: async (ctx) => {
114
+ expect(ensureHecho).toBe(true);
115
+ varsAlEjecutar.push(ctx.vars);
116
+ return { ok: true };
117
+ },
118
+ });
119
+
120
+ await service.run(COMPARTIR, fakeRequest(toast));
121
+
122
+ expect(ensureLoaded).toHaveBeenCalledOnce();
123
+ expect(varsAlEjecutar[0]).toEqual({
124
+ slug: 'tecla',
125
+ ref: 'p1',
126
+ public_base_url: 'https://demo',
127
+ });
128
+ });
129
+
130
+ it('acciones que no son copy-share no tocan la config', async () => {
131
+ const run = vi.fn<NonNullable<ActionDescriptor['run']>>().mockResolvedValue({ ok: true });
132
+ registerAction({ type: 'navigate', surfaces: ['row'], run });
133
+
134
+ await service.run({ id: 'ver', label: 'Ver', urlTemplate: '/p/{ref}' }, fakeRequest(toast));
135
+
136
+ expect(ensureLoaded).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it('una acción sin type resuelve el descriptor navigate', async () => {
140
+ const run = vi.fn<NonNullable<ActionDescriptor['run']>>();
141
+ registerAction({ type: 'navigate', surfaces: ['row', 'detail'], run });
142
+
143
+ await service.run({ id: 'ver', label: 'Ver', urlTemplate: '/p/{ref}' }, fakeRequest(toast));
144
+
145
+ expect(run).toHaveBeenCalledOnce();
146
+ });
147
+
148
+ it('sin descriptor registrado rompe en lugar de ignorarse', async () => {
149
+ // `ResourceAction` es una unión cerrada y el registro es estado de
150
+ // módulo compartido entre tests: se fuerza un type que nadie registra
151
+ // (el cast a `unknown` sólo salva al compilador, no cambia el código).
152
+ const desconocida = { id: 'x', label: 'X', type: 'nadie' } as unknown as ResourceAction;
153
+ await expect(service.run(desconocida, fakeRequest(toast))).rejects.toThrow(
154
+ 'no tiene descriptor registrado',
155
+ );
156
+ });
157
+
158
+ it('con confirmación rechazada no ejecuta y devuelve ok false', async () => {
159
+ toast.confirm.mockResolvedValue(false);
160
+ const run = vi.fn<NonNullable<ActionDescriptor['run']>>();
161
+ registerAction({
162
+ type: 'trigger',
163
+ surfaces: ['row'],
164
+ confirm: true,
165
+ run,
166
+ });
167
+
168
+ const resultado = await service.run(
169
+ {
170
+ id: 'publicar',
171
+ label: 'Publicar',
172
+ type: 'trigger',
173
+ confirmMessage: 'Se publicará para todos.',
174
+ },
175
+ fakeRequest(toast),
176
+ );
177
+
178
+ expect(toast.confirm).toHaveBeenCalledWith({
179
+ title: 'Publicar',
180
+ message: 'Se publicará para todos.',
181
+ confirmLabel: 'Confirmar',
182
+ cancelLabel: 'Cancelar',
183
+ });
184
+ expect(run).not.toHaveBeenCalled();
185
+ expect(resultado).toEqual({ ok: false });
186
+ });
187
+
188
+ it('con confirmación aceptada ejecuta el descriptor', async () => {
189
+ const run = vi.fn<NonNullable<ActionDescriptor['run']>>().mockResolvedValue({ ok: true });
190
+ registerAction({ type: 'trigger', surfaces: ['row'], confirm: true, run });
191
+
192
+ const resultado = await service.run(
193
+ { id: 'duplicar', label: 'Duplicar', type: 'trigger' },
194
+ fakeRequest(toast),
195
+ );
196
+
197
+ expect(run).toHaveBeenCalledOnce();
198
+ expect(resultado).toEqual({ ok: true });
199
+ });
200
+
201
+ it('un error del descriptor se notifica y no se devuelve como éxito', async () => {
202
+ registerAction({
203
+ type: 'copy-share',
204
+ surfaces: ['row'],
205
+ run: async () => {
206
+ throw new Error('El backend respondió 500');
207
+ },
208
+ });
209
+
210
+ const resultado = await service.run(COMPARTIR, fakeRequest(toast));
211
+
212
+ expect(toast.error).toHaveBeenCalledWith('El backend respondió 500');
213
+ expect(resultado).toEqual({ ok: false });
214
+ });
215
+ });