@tinya-code/gas-admin-ui 0.2.0-alpha.13 → 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 (368) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +3 -15
  3. package/src/lib/actions/action-registry.spec.ts +52 -0
  4. package/src/lib/actions/action-registry.ts +126 -0
  5. package/src/lib/actions/action-service.spec.ts +215 -0
  6. package/src/lib/actions/action-service.ts +87 -0
  7. package/src/lib/actions/descriptors.spec.ts +155 -0
  8. package/src/lib/actions/descriptors.ts +154 -0
  9. package/src/lib/columns/column-cell.ts +33 -0
  10. package/src/lib/columns/column-defaults.spec.ts +99 -0
  11. package/src/lib/columns/column-defaults.ts +84 -0
  12. package/src/lib/columns/column-format.spec.ts +274 -0
  13. package/src/lib/columns/descriptors.ts +113 -0
  14. package/src/lib/columns/format-badge.ts +51 -0
  15. package/src/lib/columns/format-boolean.ts +58 -0
  16. package/src/lib/columns/format-currency.ts +30 -0
  17. package/src/lib/columns/format-date.ts +35 -0
  18. package/src/lib/columns/format-number.ts +32 -0
  19. package/src/lib/columns/format-text.ts +23 -0
  20. package/src/lib/columns/format-thumbnail.ts +63 -0
  21. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  22. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  23. package/src/lib/core/guards/auth.guard.ts +48 -0
  24. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  25. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  27. package/src/lib/core/models/api.model.ts +104 -0
  28. package/src/lib/core/models/schema-descriptions.ts +118 -0
  29. package/src/lib/core/models/schema.model.ts +1097 -0
  30. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  31. package/src/lib/core/registry/composition-validator.ts +270 -0
  32. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  33. package/src/lib/core/registry/field-validators.ts +440 -0
  34. package/src/lib/core/registry/render-host.spec.ts +208 -0
  35. package/src/lib/core/registry/render-host.ts +646 -0
  36. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  37. package/src/lib/core/registry/render-registry.ts +312 -0
  38. package/src/lib/core/registry/schema-catalog.ts +156 -0
  39. package/src/lib/core/services/api.service.spec.ts +1142 -0
  40. package/src/lib/core/services/api.service.ts +371 -0
  41. package/src/lib/core/services/auth.service.spec.ts +653 -0
  42. package/src/lib/core/services/auth.service.ts +503 -0
  43. package/src/lib/core/services/config.service.spec.ts +121 -0
  44. package/src/lib/core/services/config.service.ts +119 -0
  45. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  46. package/src/lib/core/services/data-source.service.ts +142 -0
  47. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  48. package/src/lib/core/services/error-mapper.service.ts +49 -0
  49. package/src/lib/core/services/notification.service.ts +73 -0
  50. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  51. package/src/lib/core/services/theme.service.spec.ts +106 -0
  52. package/src/lib/core/services/theme.service.ts +86 -0
  53. package/src/lib/core/services/transport.spec.ts +198 -0
  54. package/src/lib/core/services/transport.ts +164 -0
  55. package/src/lib/core/services/upload.service.ts +81 -0
  56. package/src/lib/core/testing/categories.schema.ts +125 -0
  57. package/src/lib/core/testing/orders.schema.ts +372 -0
  58. package/src/lib/core/testing/products.schema.ts +258 -0
  59. package/src/lib/core/testing/site.schema.ts +195 -0
  60. package/src/lib/core/testing/test-catalog.ts +21 -0
  61. package/src/lib/core/tokens.ts +112 -0
  62. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  63. package/src/lib/core/utils/clipboard.ts +90 -0
  64. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  65. package/src/lib/core/utils/context-resolver.ts +101 -0
  66. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  67. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  68. package/src/lib/core/utils/source-options.spec.ts +96 -0
  69. package/src/lib/core/utils/source-options.ts +54 -0
  70. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  71. package/src/lib/core/utils/template-interpolator.ts +56 -0
  72. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  74. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  75. package/src/lib/fields/boolean/boolean.ts +40 -0
  76. package/src/lib/fields/color/color.descriptor.ts +12 -0
  77. package/src/lib/fields/color/color.ts +34 -0
  78. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  79. package/src/lib/fields/currency/currency.ts +67 -0
  80. package/src/lib/fields/date/date.descriptor.ts +12 -0
  81. package/src/lib/fields/date/date.ts +36 -0
  82. package/src/lib/fields/descriptors.ts +122 -0
  83. package/src/lib/fields/display/display-badge.ts +65 -0
  84. package/src/lib/fields/display/display-date.ts +39 -0
  85. package/src/lib/fields/display/display-defaults.ts +84 -0
  86. package/src/lib/fields/display/display-file.ts +41 -0
  87. package/src/lib/fields/display/display-image.ts +54 -0
  88. package/src/lib/fields/display/display-list.ts +107 -0
  89. package/src/lib/fields/display/display-number.ts +41 -0
  90. package/src/lib/fields/display/display-text.ts +42 -0
  91. package/src/lib/fields/display/display.spec.ts +242 -0
  92. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  93. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  94. package/src/lib/fields/email/email.descriptor.ts +12 -0
  95. package/src/lib/fields/email/email.ts +28 -0
  96. package/src/lib/fields/field-aria.ts +60 -0
  97. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  98. package/src/lib/fields/field-host/field-host.ts +47 -0
  99. package/src/lib/fields/field-node.ts +88 -0
  100. package/src/lib/fields/file/file.descriptor.ts +14 -0
  101. package/src/lib/fields/file/file.spec.ts +134 -0
  102. package/src/lib/fields/file/file.ts +281 -0
  103. package/src/lib/fields/group/group.descriptor.ts +21 -0
  104. package/src/lib/fields/group/group.ts +110 -0
  105. package/src/lib/fields/image/image.descriptor.ts +12 -0
  106. package/src/lib/fields/image/image.spec.ts +117 -0
  107. package/src/lib/fields/image/image.ts +281 -0
  108. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  109. package/src/lib/fields/key-value/key-value.ts +283 -0
  110. package/src/lib/fields/list/list.descriptor.ts +21 -0
  111. package/src/lib/fields/list/list.ts +376 -0
  112. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  113. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  114. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  115. package/src/lib/fields/number/number.descriptor.ts +12 -0
  116. package/src/lib/fields/number/number.ts +40 -0
  117. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  118. package/src/lib/fields/phone/phone.ts +46 -0
  119. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  120. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  121. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  122. package/src/lib/fields/relation/relation.spec.ts +193 -0
  123. package/src/lib/fields/relation/relation.ts +224 -0
  124. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  125. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  126. package/src/lib/fields/select/radio-mode.ts +25 -0
  127. package/src/lib/fields/select/select.descriptor.ts +12 -0
  128. package/src/lib/fields/select/select.spec.ts +162 -0
  129. package/src/lib/fields/select/select.ts +210 -0
  130. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  131. package/src/lib/fields/slug/slug.ts +67 -0
  132. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  133. package/src/lib/fields/string-list/string-list.ts +222 -0
  134. package/src/lib/fields/text/text.descriptor.ts +12 -0
  135. package/src/lib/fields/text/text.ts +35 -0
  136. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  137. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  138. package/src/lib/fields/textarea/textarea.ts +126 -0
  139. package/src/lib/fields/time/time.descriptor.ts +12 -0
  140. package/src/lib/fields/time/time.ts +28 -0
  141. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  142. package/src/lib/fields/url/url.descriptor.ts +12 -0
  143. package/src/lib/fields/url/url.ts +53 -0
  144. package/src/lib/gas-admin-ui.spec.ts +21 -0
  145. package/src/lib/gas-admin-ui.ts +9 -0
  146. package/src/lib/layouts/descriptors.ts +9 -0
  147. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  148. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  149. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  150. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  151. package/src/lib/pages/form-view/form-model.ts +88 -0
  152. package/src/lib/pages/form-view/form-schema.ts +255 -0
  153. package/src/lib/pages/form-view/form-view.ts +1622 -0
  154. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  155. package/src/lib/pages/list-view/list-view.ts +882 -0
  156. package/src/lib/pages/login/login.spec.ts +109 -0
  157. package/src/lib/pages/login/login.ts +227 -0
  158. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  159. package/src/lib/shared/components/badge/badge.ts +35 -0
  160. package/src/lib/shared/components/button/button.ts +65 -0
  161. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  162. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  163. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  164. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  165. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  166. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  167. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  168. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  169. package/src/lib/shared/components/modal/modal.ts +145 -0
  170. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  172. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  173. package/src/lib/shared/components/toast/toast.ts +58 -0
  174. package/src/lib/shared/utils/col-span.ts +25 -0
  175. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  176. package/src/lib/shared/utils/depends-on.ts +48 -0
  177. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  178. package/src/lib/shared/utils/field-path.ts +49 -0
  179. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  180. package/src/lib/shared/utils/seed.spec.ts +64 -0
  181. package/src/lib/shared/utils/seed.ts +149 -0
  182. package/src/lib/shared/utils/slugify.ts +13 -0
  183. package/src/lib/shared/utils/validators.ts +41 -0
  184. package/src/lib/shell/layout/layout.ts +63 -0
  185. package/src/lib/shell/shell.routes.spec.ts +59 -0
  186. package/src/lib/shell/shell.routes.ts +122 -0
  187. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  188. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  189. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  190. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  191. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  192. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  193. package/src/lib/views/composer/composer-host.ts +248 -0
  194. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  195. package/src/lib/views/composer/view-context.spec.ts +117 -0
  196. package/src/lib/views/composer/view-context.ts +109 -0
  197. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  198. package/src/lib/views/composer/view-slot.ts +178 -0
  199. package/src/lib/views/descriptors.ts +46 -0
  200. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  201. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  202. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  203. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  206. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  207. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  208. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  209. package/src/lib/widgets/descriptors.ts +29 -0
  210. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  211. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  212. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  213. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  216. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  217. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  218. package/src/lib/widgets/record-list/record-list.ts +115 -0
  219. package/src/lib/widgets/series.spec.ts +42 -0
  220. package/src/lib/widgets/series.ts +56 -0
  221. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  222. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  223. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  224. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  225. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  226. package/src/public-api.ts +104 -0
  227. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  228. package/tsconfig.lib.json +13 -0
  229. package/tsconfig.lib.prod.json +11 -0
  230. package/tsconfig.spec.json +10 -0
  231. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  233. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  235. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  237. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  239. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  241. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  243. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  245. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  247. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  249. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  251. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  253. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  255. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  257. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  259. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  261. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  263. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  265. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  267. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  269. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  271. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  273. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  275. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  277. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  279. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  281. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  283. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  285. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  287. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  289. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  291. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  293. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  295. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  297. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  299. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  301. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  303. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  305. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  307. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  309. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  311. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  313. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  315. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  317. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  319. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  321. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  323. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  325. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  327. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  329. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  333. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  335. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  337. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  338. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  340. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  342. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  344. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  346. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  348. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  350. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  352. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  354. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  356. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  358. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  360. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  362. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  364. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  366. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  368. package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
@@ -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.13",
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
+ });
@@ -0,0 +1,87 @@
1
+ import { Service, inject } from '@angular/core';
2
+
3
+ import {
4
+ actionType,
5
+ getAction,
6
+ type ActionContext,
7
+ type ActionRequest,
8
+ type ActionResult,
9
+ } from './action-registry';
10
+ import type { ResourceAction } from '../core/models/schema.model';
11
+ import { ConfigService } from '../core/services/config.service';
12
+ import { ErrorMapperService } from '../core/services/error-mapper.service';
13
+
14
+ /**
15
+ * Ejecuta una acción declarada en el schema (R3.9 · `refactormotor.md`).
16
+ *
17
+ * Sustituye el dispatch por tipo que hoy vive en `list-view` y en las
18
+ * copias de `detail-view` y `post-create-modal` (R3.11). El camino siempre
19
+ * es el mismo:
20
+ *
21
+ * 1. resolver el descriptor (si no existe, **rompe** — nada de «disponible
22
+ * próximamente», que hoy oculta esquemas mal declarados);
23
+ * 2. pedir confirmación si el descriptor la exige;
24
+ * 3. montar el `ActionContext` (petición + `vars` de interpolación);
25
+ * 4. ejecutar y, si el descriptor lanza, notificar el error y devolver
26
+ * `{ ok: false }` para que la vista no trate el fallo como éxito.
27
+ */
28
+ @Service()
29
+ export class ActionService {
30
+ private readonly config = inject(ConfigService);
31
+ private readonly errores = inject(ErrorMapperService);
32
+
33
+ async run(action: ResourceAction, request: ActionRequest): Promise<ActionResult> {
34
+ const type = actionType(action);
35
+ const descriptor = getAction(type);
36
+ if (!descriptor) {
37
+ throw new Error(
38
+ `[actions] «${action.label}» no tiene descriptor registrado (type "${type}").`,
39
+ );
40
+ }
41
+
42
+ if (descriptor.confirm) {
43
+ const aceptado = await request.toast.confirm({
44
+ title: action.label,
45
+ message: confirmMessage(action),
46
+ confirmLabel: 'Confirmar',
47
+ cancelLabel: 'Cancelar',
48
+ });
49
+ if (!aceptado) {
50
+ return { ok: false };
51
+ }
52
+ }
53
+
54
+ // `copy-share` (R8.7) resuelve su mensaje contra la config global
55
+ // (`share_message_template`). Si el load del bootstrap no llegó (arranque
56
+ // sin sesión o red lenta), lo pedimos ahora —con la sesión ya iniciada—
57
+ // ANTES de congelar `vars`: si no, el mensaje copiado degradaría a
58
+ // «sólo la URL» hasta el próximo F5.
59
+ if (descriptor.type === 'copy-share') {
60
+ await this.config.ensureLoaded();
61
+ }
62
+
63
+ const ctx: ActionContext = { ...request, vars: this.vars(request.record) };
64
+ try {
65
+ return await descriptor.run(ctx, action);
66
+ } catch (err) {
67
+ request.toast.error(this.errores.messageOf(err));
68
+ return { ok: false };
69
+ }
70
+ }
71
+
72
+ /**
73
+ * Config global + registro. Sin registro (`header`, `bulk`) el contexto de
74
+ * interpolación queda vacío: el descriptor no tiene nada que interpolar.
75
+ *
76
+ * Público porque `detail-view` sólo **presenta** una acción (pinta
77
+ * `<app-copy-share>`) en vez de ejecutarla, y aun así necesita el mismo
78
+ * contexto: R3.11 acabó con la tercera copia de esta lógica.
79
+ */
80
+ vars(record: Record<string, unknown> | null): Record<string, unknown> {
81
+ return record ? this.config.buildInterpolationContext(record) : {};
82
+ }
83
+ }
84
+
85
+ function confirmMessage(action: ResourceAction): string {
86
+ return 'confirmMessage' in action && action.confirmMessage ? action.confirmMessage : action.label;
87
+ }
@@ -0,0 +1,155 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi, type MockInstance } from 'vitest';
2
+ import { of } from 'rxjs';
3
+
4
+ import { getAction, type ActionContext } from './action-registry';
5
+ import { resolveShare } from './descriptors';
6
+ import type { CopyShareAction, NavigateAction, TriggerAction } from '../core/models/schema.model';
7
+ import type { ApiService } from '../core/services/api.service';
8
+ import type { NotificationService } from '../core/services/notification.service';
9
+ import type { Router } from '@angular/router';
10
+
11
+ const VARS = { public_base_url: 'https://demo', ref: 'p1', customer_name: 'Ana' };
12
+
13
+ const COMPARTIR: CopyShareAction = {
14
+ id: 'share',
15
+ label: 'Compartir',
16
+ type: 'copy-share',
17
+ urlTemplate: '{public_base_url}/p/{ref}',
18
+ shareTextTemplate: 'Hola {customer_name}, tu pedido {ref}',
19
+ };
20
+
21
+ function contexto(): ActionContext {
22
+ return {
23
+ record: { ref: 'p1' },
24
+ resource: 'orders',
25
+ api: { request: vi.fn().mockReturnValue(of({ ok: true })) } as unknown as ApiService,
26
+ router: { navigateByUrl: vi.fn().mockResolvedValue(true) } as unknown as Router,
27
+ toast: {
28
+ success: vi.fn(),
29
+ error: vi.fn(),
30
+ info: vi.fn(),
31
+ confirm: vi.fn(),
32
+ dismiss: vi.fn(),
33
+ } as unknown as NotificationService,
34
+ user: null,
35
+ keyField: 'ref',
36
+ vars: VARS,
37
+ };
38
+ }
39
+
40
+ /** El `run` de un tipo registrado; lanza si no existe (estado incoherente). */
41
+ function ejecutar(type: string, action: CopyShareAction | NavigateAction | TriggerAction) {
42
+ const descriptor = getAction(type);
43
+ if (!descriptor) throw new Error(`sin descriptor «${type}»`);
44
+ return descriptor.run;
45
+ }
46
+
47
+ describe('resolveShare', () => {
48
+ it('interpola enlace y mensaje con el contexto del registro', () => {
49
+ expect(resolveShare(COMPARTIR, VARS)).toEqual({
50
+ url: 'https://demo/p/p1',
51
+ shareText: 'Hola Ana, tu pedido p1',
52
+ });
53
+ });
54
+
55
+ it('sin shareTextTemplate cae a share_message_template (config global)', () => {
56
+ const action: CopyShareAction = { ...COMPARTIR, shareTextTemplate: undefined };
57
+ expect(resolveShare(action, { ...VARS, share_message_template: 'Hola {ref}' })).toEqual({
58
+ url: 'https://demo/p/p1',
59
+ shareText: 'Hola p1',
60
+ });
61
+ });
62
+
63
+ it('sin plantilla ninguna el mensaje queda vacío', () => {
64
+ const action: CopyShareAction = { ...COMPARTIR, shareTextTemplate: undefined };
65
+ expect(resolveShare(action, VARS).shareText).toBe('');
66
+ });
67
+ });
68
+
69
+ describe('descriptores de acción', () => {
70
+ const escritor = vi.fn().mockResolvedValue(undefined);
71
+ let abrir: MockInstance;
72
+
73
+ beforeEach(() => {
74
+ escritor.mockClear();
75
+ Object.defineProperty(window.navigator, 'clipboard', {
76
+ value: { writeText: escritor },
77
+ configurable: true,
78
+ });
79
+ abrir = vi.spyOn(window, 'open').mockReturnValue(null);
80
+ });
81
+
82
+ afterEach(() => {
83
+ vi.restoreAllMocks();
84
+ });
85
+
86
+ it('copy-share copia el mensaje completo y lo anuncia', async () => {
87
+ const ctx = contexto();
88
+ const resultado = await ejecutar('copy-share', COMPARTIR)(ctx, COMPARTIR);
89
+
90
+ expect(escritor).toHaveBeenCalledWith('Hola Ana, tu pedido p1 https://demo/p/p1');
91
+ expect(ctx.toast.success).toHaveBeenCalledWith('Mensaje copiado al portapapeles.');
92
+ expect(resultado).toEqual({ ok: true });
93
+ });
94
+
95
+ it('navigate abre una URL absoluta en otra pestaña', async () => {
96
+ const action: NavigateAction = {
97
+ id: 'ver',
98
+ label: 'Ver público',
99
+ urlTemplate: '{public_base_url}/p/{ref}',
100
+ };
101
+ const ctx = contexto();
102
+
103
+ await ejecutar('navigate', action)(ctx, action);
104
+
105
+ expect(abrir).toHaveBeenCalledWith('https://demo/p/p1', '_blank', 'noopener');
106
+ expect(ctx.router.navigateByUrl).not.toHaveBeenCalled();
107
+ });
108
+
109
+ it('navigate entrega una ruta interna al router', async () => {
110
+ const action: NavigateAction = { id: 'editar', label: 'Editar', urlTemplate: '/orders/p1' };
111
+ const ctx = contexto();
112
+
113
+ await ejecutar('navigate', action)(ctx, action);
114
+
115
+ expect(ctx.router.navigateByUrl).toHaveBeenCalledWith('/orders/p1');
116
+ expect(abrir).not.toHaveBeenCalled();
117
+ });
118
+
119
+ it('trigger arma la ruta lógica de la acción declarada y pide recarga', async () => {
120
+ const action: TriggerAction = {
121
+ id: 'markDelivered',
122
+ label: 'Marcar entregado',
123
+ type: 'trigger',
124
+ };
125
+ const ctx = contexto();
126
+
127
+ const resultado = await ejecutar('trigger', action)(ctx, action);
128
+
129
+ expect(ctx.api.request).toHaveBeenCalledWith('POST', '/admin/orders/p1/actions/markDelivered');
130
+ expect(resultado).toEqual({ ok: true, reload: true });
131
+ });
132
+
133
+ it('trigger usa `name` cuando en el back se llama distinto que el `id`', async () => {
134
+ const action: TriggerAction = {
135
+ id: 'reenviar',
136
+ label: 'Reenviar pin',
137
+ type: 'trigger',
138
+ name: 'resendPin',
139
+ };
140
+ const ctx = contexto();
141
+
142
+ await ejecutar('trigger', action)(ctx, action);
143
+
144
+ expect(ctx.api.request).toHaveBeenCalledWith('POST', '/admin/orders/p1/actions/resendPin');
145
+ });
146
+
147
+ it('sin clave de registro no dispara nada y lo dice (fail-loud)', async () => {
148
+ const action: TriggerAction = { id: 'markDelivered', label: 'Marcar', type: 'trigger' };
149
+ const ctx = contexto();
150
+ ctx.record = null;
151
+
152
+ await expect(ejecutar('trigger', action)(ctx, action)).rejects.toThrow('necesita la clave ref');
153
+ expect(ctx.api.request).not.toHaveBeenCalled();
154
+ });
155
+ });