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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (369) hide show
  1. package/README.md +222 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +3 -15
  4. package/src/lib/actions/action-registry.spec.ts +52 -0
  5. package/src/lib/actions/action-registry.ts +126 -0
  6. package/src/lib/actions/action-service.spec.ts +215 -0
  7. package/src/lib/actions/action-service.ts +87 -0
  8. package/src/lib/actions/descriptors.spec.ts +155 -0
  9. package/src/lib/actions/descriptors.ts +154 -0
  10. package/src/lib/columns/column-cell.ts +33 -0
  11. package/src/lib/columns/column-defaults.spec.ts +99 -0
  12. package/src/lib/columns/column-defaults.ts +84 -0
  13. package/src/lib/columns/column-format.spec.ts +274 -0
  14. package/src/lib/columns/descriptors.ts +113 -0
  15. package/src/lib/columns/format-badge.ts +51 -0
  16. package/src/lib/columns/format-boolean.ts +58 -0
  17. package/src/lib/columns/format-currency.ts +30 -0
  18. package/src/lib/columns/format-date.ts +35 -0
  19. package/src/lib/columns/format-number.ts +32 -0
  20. package/src/lib/columns/format-text.ts +23 -0
  21. package/src/lib/columns/format-thumbnail.ts +63 -0
  22. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  23. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  24. package/src/lib/core/guards/auth.guard.ts +48 -0
  25. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  27. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  28. package/src/lib/core/models/api.model.ts +104 -0
  29. package/src/lib/core/models/schema-descriptions.ts +118 -0
  30. package/src/lib/core/models/schema.model.ts +1097 -0
  31. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  32. package/src/lib/core/registry/composition-validator.ts +270 -0
  33. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  34. package/src/lib/core/registry/field-validators.ts +440 -0
  35. package/src/lib/core/registry/render-host.spec.ts +208 -0
  36. package/src/lib/core/registry/render-host.ts +646 -0
  37. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  38. package/src/lib/core/registry/render-registry.ts +312 -0
  39. package/src/lib/core/registry/schema-catalog.ts +156 -0
  40. package/src/lib/core/services/api.service.spec.ts +1142 -0
  41. package/src/lib/core/services/api.service.ts +371 -0
  42. package/src/lib/core/services/auth.service.spec.ts +653 -0
  43. package/src/lib/core/services/auth.service.ts +503 -0
  44. package/src/lib/core/services/config.service.spec.ts +121 -0
  45. package/src/lib/core/services/config.service.ts +119 -0
  46. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  47. package/src/lib/core/services/data-source.service.ts +142 -0
  48. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  49. package/src/lib/core/services/error-mapper.service.ts +49 -0
  50. package/src/lib/core/services/notification.service.ts +73 -0
  51. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  52. package/src/lib/core/services/theme.service.spec.ts +106 -0
  53. package/src/lib/core/services/theme.service.ts +86 -0
  54. package/src/lib/core/services/transport.spec.ts +198 -0
  55. package/src/lib/core/services/transport.ts +164 -0
  56. package/src/lib/core/services/upload.service.ts +81 -0
  57. package/src/lib/core/testing/categories.schema.ts +125 -0
  58. package/src/lib/core/testing/orders.schema.ts +372 -0
  59. package/src/lib/core/testing/products.schema.ts +258 -0
  60. package/src/lib/core/testing/site.schema.ts +195 -0
  61. package/src/lib/core/testing/test-catalog.ts +21 -0
  62. package/src/lib/core/tokens.ts +112 -0
  63. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  64. package/src/lib/core/utils/clipboard.ts +90 -0
  65. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  66. package/src/lib/core/utils/context-resolver.ts +101 -0
  67. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  68. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  69. package/src/lib/core/utils/source-options.spec.ts +96 -0
  70. package/src/lib/core/utils/source-options.ts +54 -0
  71. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  72. package/src/lib/core/utils/template-interpolator.ts +56 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  74. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  75. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  76. package/src/lib/fields/boolean/boolean.ts +40 -0
  77. package/src/lib/fields/color/color.descriptor.ts +12 -0
  78. package/src/lib/fields/color/color.ts +34 -0
  79. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  80. package/src/lib/fields/currency/currency.ts +67 -0
  81. package/src/lib/fields/date/date.descriptor.ts +12 -0
  82. package/src/lib/fields/date/date.ts +36 -0
  83. package/src/lib/fields/descriptors.ts +122 -0
  84. package/src/lib/fields/display/display-badge.ts +65 -0
  85. package/src/lib/fields/display/display-date.ts +39 -0
  86. package/src/lib/fields/display/display-defaults.ts +84 -0
  87. package/src/lib/fields/display/display-file.ts +41 -0
  88. package/src/lib/fields/display/display-image.ts +54 -0
  89. package/src/lib/fields/display/display-list.ts +107 -0
  90. package/src/lib/fields/display/display-number.ts +41 -0
  91. package/src/lib/fields/display/display-text.ts +42 -0
  92. package/src/lib/fields/display/display.spec.ts +242 -0
  93. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  94. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  95. package/src/lib/fields/email/email.descriptor.ts +12 -0
  96. package/src/lib/fields/email/email.ts +28 -0
  97. package/src/lib/fields/field-aria.ts +60 -0
  98. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  99. package/src/lib/fields/field-host/field-host.ts +47 -0
  100. package/src/lib/fields/field-node.ts +88 -0
  101. package/src/lib/fields/file/file.descriptor.ts +14 -0
  102. package/src/lib/fields/file/file.spec.ts +134 -0
  103. package/src/lib/fields/file/file.ts +281 -0
  104. package/src/lib/fields/group/group.descriptor.ts +21 -0
  105. package/src/lib/fields/group/group.ts +110 -0
  106. package/src/lib/fields/image/image.descriptor.ts +12 -0
  107. package/src/lib/fields/image/image.spec.ts +117 -0
  108. package/src/lib/fields/image/image.ts +281 -0
  109. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  110. package/src/lib/fields/key-value/key-value.ts +283 -0
  111. package/src/lib/fields/list/list.descriptor.ts +21 -0
  112. package/src/lib/fields/list/list.ts +376 -0
  113. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  114. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  115. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  116. package/src/lib/fields/number/number.descriptor.ts +12 -0
  117. package/src/lib/fields/number/number.ts +40 -0
  118. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  119. package/src/lib/fields/phone/phone.ts +46 -0
  120. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  121. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  122. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  123. package/src/lib/fields/relation/relation.spec.ts +193 -0
  124. package/src/lib/fields/relation/relation.ts +224 -0
  125. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  126. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  127. package/src/lib/fields/select/radio-mode.ts +25 -0
  128. package/src/lib/fields/select/select.descriptor.ts +12 -0
  129. package/src/lib/fields/select/select.spec.ts +162 -0
  130. package/src/lib/fields/select/select.ts +210 -0
  131. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  132. package/src/lib/fields/slug/slug.ts +67 -0
  133. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  134. package/src/lib/fields/string-list/string-list.ts +222 -0
  135. package/src/lib/fields/text/text.descriptor.ts +12 -0
  136. package/src/lib/fields/text/text.ts +35 -0
  137. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  138. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  139. package/src/lib/fields/textarea/textarea.ts +126 -0
  140. package/src/lib/fields/time/time.descriptor.ts +12 -0
  141. package/src/lib/fields/time/time.ts +28 -0
  142. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  143. package/src/lib/fields/url/url.descriptor.ts +12 -0
  144. package/src/lib/fields/url/url.ts +53 -0
  145. package/src/lib/gas-admin-ui.spec.ts +21 -0
  146. package/src/lib/gas-admin-ui.ts +9 -0
  147. package/src/lib/layouts/descriptors.ts +9 -0
  148. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  149. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  150. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  151. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  152. package/src/lib/pages/form-view/form-model.ts +88 -0
  153. package/src/lib/pages/form-view/form-schema.ts +255 -0
  154. package/src/lib/pages/form-view/form-view.ts +1622 -0
  155. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  156. package/src/lib/pages/list-view/list-view.ts +882 -0
  157. package/src/lib/pages/login/login.spec.ts +109 -0
  158. package/src/lib/pages/login/login.ts +227 -0
  159. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  160. package/src/lib/shared/components/badge/badge.ts +35 -0
  161. package/src/lib/shared/components/button/button.ts +65 -0
  162. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  163. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  164. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  165. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  166. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  167. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  168. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  169. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  170. package/src/lib/shared/components/modal/modal.ts +145 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  172. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  173. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  174. package/src/lib/shared/components/toast/toast.ts +58 -0
  175. package/src/lib/shared/utils/col-span.ts +25 -0
  176. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  177. package/src/lib/shared/utils/depends-on.ts +48 -0
  178. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  179. package/src/lib/shared/utils/field-path.ts +49 -0
  180. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  181. package/src/lib/shared/utils/seed.spec.ts +64 -0
  182. package/src/lib/shared/utils/seed.ts +149 -0
  183. package/src/lib/shared/utils/slugify.ts +13 -0
  184. package/src/lib/shared/utils/validators.ts +41 -0
  185. package/src/lib/shell/layout/layout.ts +63 -0
  186. package/src/lib/shell/shell.routes.spec.ts +59 -0
  187. package/src/lib/shell/shell.routes.ts +122 -0
  188. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  189. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  190. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  191. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  192. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  193. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  194. package/src/lib/views/composer/composer-host.ts +248 -0
  195. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  196. package/src/lib/views/composer/view-context.spec.ts +117 -0
  197. package/src/lib/views/composer/view-context.ts +109 -0
  198. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  199. package/src/lib/views/composer/view-slot.ts +178 -0
  200. package/src/lib/views/descriptors.ts +46 -0
  201. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  202. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  203. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  206. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  207. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  208. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  209. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  210. package/src/lib/widgets/descriptors.ts +29 -0
  211. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  212. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  213. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  216. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  217. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  218. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  219. package/src/lib/widgets/record-list/record-list.ts +115 -0
  220. package/src/lib/widgets/series.spec.ts +42 -0
  221. package/src/lib/widgets/series.ts +56 -0
  222. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  223. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  224. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  225. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  226. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  227. package/src/public-api.ts +104 -0
  228. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  229. package/tsconfig.lib.json +13 -0
  230. package/tsconfig.lib.prod.json +11 -0
  231. package/tsconfig.spec.json +10 -0
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  233. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  235. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  237. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  239. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  241. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  243. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  245. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  247. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  249. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  251. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  253. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  255. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  257. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  259. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  261. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  263. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  265. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  267. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  269. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  271. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  273. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  275. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  277. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  279. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  281. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  283. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  285. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  287. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  289. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  291. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  293. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  295. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  297. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  299. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  301. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  303. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  305. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  307. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  309. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  311. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  313. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  315. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  317. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  319. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  321. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  323. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  325. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  327. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  329. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  333. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  335. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  337. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  338. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  340. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  342. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  344. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  346. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  348. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  350. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  352. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  354. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  356. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  358. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  360. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  362. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  364. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  366. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  368. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  369. package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
@@ -0,0 +1,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
+ });
@@ -0,0 +1,154 @@
1
+ import { firstValueFrom } from 'rxjs';
2
+ import { LucideShare2 } from '@lucide/angular';
3
+
4
+ import type { ActionDescriptor } from './action-registry';
5
+ import type {
6
+ CopyShareAction,
7
+ NavigateAction,
8
+ ResourceAction,
9
+ TriggerAction,
10
+ } from '../core/models/schema.model';
11
+ import { buildShareMessage, writeClipboard } from '../core/utils/clipboard';
12
+ import { interpolateTemplate } from '../core/utils/template-interpolator';
13
+
14
+ /**
15
+ * Descriptores de las tres acciones que hoy declara el modelo
16
+ * (R3.10 · `doc/plan-refactor-motor.md` §3.2).
17
+ *
18
+ * Éste es el fichero que **cierra la triplicación de `copy-share`**: la
19
+ * resolución de enlace y de mensaje vivía en `list-view.runAction`,
20
+ * `detail-view.resolveUrl/resolveShareText` y
21
+ * `post-create-modal.resolvedShareUrl/resolvedShareText`. Aquí se resuelve
22
+ * una sola vez (`resolveShare`) y las vistas consumen ese resultado en R3.11.
23
+ *
24
+ * Se registra al importar, desde `action-registry.ts`, con el mismo criterio
25
+ * que el registro de render: sin esperar a DI, porque el registro es la única
26
+ * fuente de verdad en runtime.
27
+ */
28
+
29
+ /** Enlace y mensaje ya interpolados, listos para copiar o compartir. */
30
+ export interface ResolvedShare {
31
+ url: string;
32
+ shareText: string;
33
+ }
34
+
35
+ /**
36
+ * `true` cuando la acción es de las que las vistas presentan como botón de
37
+ * compartir (`detail-view`, `post-create-modal`).
38
+ *
39
+ * Vive acá y no en la vista porque es **el único sitio donde se puede nombrar
40
+ * `'copy-share'`**: fuera de `descriptors.ts` ninguna vista decide por tipo
41
+ * (criterio de R3.11).
42
+ */
43
+ export function isShareAction(action: ResourceAction): action is CopyShareAction {
44
+ return action.type === 'copy-share';
45
+ }
46
+
47
+ /**
48
+ * `true` cuando la acción es un `trigger` (acción de dominio del back).
49
+ *
50
+ * Mismo criterio que `isShareAction`: el único sitio que nombra el tipo,
51
+ * para que las vistas no decidan por string literal (R3.11). `detail-view`
52
+ * lo usa en R8.8 para renderizar «Regenerar PIN» junto a los botones de
53
+ * compartir.
54
+ */
55
+ export function isTriggerAction(action: ResourceAction): action is TriggerAction {
56
+ return action.type === 'trigger';
57
+ }
58
+
59
+ /**
60
+ * Resuelve `urlTemplate` y el mensaje de compartir contra `vars`
61
+ * (config global + registro).
62
+ *
63
+ * **Única implementación** de esta lógica en el admin. El fallback a
64
+ * `share_message_template` (la plantilla que declara el singleton de
65
+ * configuración) lo aplican los tres puntos por igual: hasta ahora la fila
66
+ * del listado no lo honraba y se quedaba sin saludo.
67
+ */
68
+ export function resolveShare(
69
+ action: CopyShareAction,
70
+ vars: Record<string, unknown>,
71
+ ): ResolvedShare {
72
+ const template =
73
+ action.shareTextTemplate || (vars['share_message_template'] as string | undefined);
74
+ return {
75
+ url: interpolateTemplate(action.urlTemplate, vars),
76
+ shareText: template ? interpolateTemplate(template, vars) : '',
77
+ };
78
+ }
79
+
80
+ /** `copy-share` — copia (o comparte) el mensaje completo del enlace público. */
81
+ const copyShare: ActionDescriptor = {
82
+ type: 'copy-share',
83
+ surfaces: ['row', 'detail', 'post-create'],
84
+ // Icono de la fila del listado: la vista sólo decide entre «icono» y
85
+ // «rótulo», nunca por tipo. Se pasa la CLASE, no el nombre: sin
86
+ // `provideLucideIcons` no hay riesgo de `Unable to resolve icon`.
87
+ icon: LucideShare2,
88
+ run: async (ctx, action) => {
89
+ const { url, shareText } = resolveShare(action as CopyShareAction, ctx.vars);
90
+ const message = buildShareMessage(url, shareText);
91
+ const copied = await writeClipboard(message);
92
+ if (copied) {
93
+ ctx.toast.success('Mensaje copiado al portapapeles.');
94
+ } else {
95
+ ctx.toast.info(`Copiar manualmente: ${message}`);
96
+ }
97
+ return { ok: true };
98
+ },
99
+ };
100
+
101
+ /**
102
+ * `navigate` — abre la plantilla resuelta: URL absoluta en otra pestaña,
103
+ * ruta interna en el router. `NavigateAction.type` es opcional, así que el
104
+ * servicio llega acá también con una acción que no declara `type`.
105
+ */
106
+ const navigate: ActionDescriptor = {
107
+ type: 'navigate',
108
+ surfaces: ['row', 'detail'],
109
+ run: async (ctx, action) => {
110
+ const url = interpolateTemplate((action as NavigateAction).urlTemplate, ctx.vars);
111
+ if (/^https?:\/\//.test(url)) {
112
+ window.open(url, '_blank', 'noopener');
113
+ } else {
114
+ await ctx.router.navigateByUrl(url);
115
+ }
116
+ return { ok: true };
117
+ },
118
+ };
119
+
120
+ /**
121
+ * `trigger` — dispara una **acción de dominio declarada en el back** (B3).
122
+ *
123
+ * **R3.12:** el destino ya no lo declara el schema (`endpoint` y `method`
124
+ * murieron en este cambio). Se deriva de `resource` + clave del registro +
125
+ * `name` (o `id`), que es exactamente la ruta que el back publica solo en
126
+ * R5.4: `POST /admin/<recurso>/:id/actions/<name>`.
127
+ *
128
+ * ⚠️ **O2:** la ruta lógica viaja en el `path` del envelope, no en la URL —
129
+ * el HTTP sigue siendo el `POST API_URL` de siempre.
130
+ *
131
+ * Siempre pide confirmación: es la única acción de las tres que puede dejar
132
+ * una cola de trabajo hecha.
133
+ */
134
+ const trigger: ActionDescriptor = {
135
+ type: 'trigger',
136
+ surfaces: ['row', 'detail'],
137
+ confirm: true,
138
+ run: async (ctx, action) => {
139
+ const declared = action as TriggerAction;
140
+ const rawKey = ctx.record?.[ctx.keyField];
141
+ if (rawKey === null || rawKey === undefined || rawKey === '') {
142
+ throw new Error(
143
+ `La acción «${declared.label}» necesita la clave ${ctx.keyField} del registro.`,
144
+ );
145
+ }
146
+ const name = declared.name ?? declared.id;
147
+ const route = `/admin/${ctx.resource}/${String(rawKey)}/actions/${name}`;
148
+ await firstValueFrom(ctx.api.request('POST', route));
149
+ return { ok: true, reload: true };
150
+ },
151
+ };
152
+
153
+ /** Catálogo — una línea por acción, en el orden en que se registran. */
154
+ export const actionDescriptors: readonly ActionDescriptor[] = [copyShare, navigate, trigger];
@@ -0,0 +1,33 @@
1
+ import type { FieldSchema, ListColumn } from '../core/models/schema.model';
2
+
3
+ type Row = Record<string, unknown>;
4
+
5
+ /**
6
+ * Contexto de una celda de listado (R4.3 · `kind: 'column-format'`).
7
+ *
8
+ * Es lo que antes cada `@case` de `list-view` pedía a la vista como
9
+ * `row`/`column` y lo que el descriptor cuelga de `extras.data`: el
10
+ * componente de formato no conoce a la vista, sólo a su celda.
11
+ *
12
+ * `fields`, `canUpdate`, `saving`, `currency` y `toggle` viajan acá porque
13
+ * son decisiones de la vista (permisos, moneda del schema, escritura de la
14
+ * fila); un formato nuevo no necesita tocar `list-view` para leerlas.
15
+ */
16
+ export interface ColumnCell {
17
+ /** Fila completa. */
18
+ row: Row;
19
+ /** Columna que se pinta (`key`, `label`, `format`). */
20
+ column: ListColumn;
21
+ /** Campos del recurso: de acá salen `trueLabel`/`falseLabel`. */
22
+ fields: FieldSchema[];
23
+ /** `titleField` de la fila, para el `aria-label` del interruptor. */
24
+ title: string;
25
+ /** ¿La fila admite escritura? (`ListView.canUpdate`) */
26
+ canUpdate: boolean;
27
+ /** ¿La fila está guardando? (`ListView.savingKey === keyOf(row)`) */
28
+ saving: boolean;
29
+ /** Moneda del schema, para el formato `currency`. */
30
+ currency: string;
31
+ /** Escribe el valor en la fila. Sólo lo usa el interruptor `active`. */
32
+ toggle?: (checked: boolean) => void;
33
+ }
@@ -0,0 +1,99 @@
1
+ import { describe, expect, it } from 'vitest';
2
+
3
+ import { SUPPORTED_FIELD_TYPE_LIST } from '../fields/descriptors';
4
+ import type { FieldSchema, ListColumn } from '../core/models/schema.model';
5
+ import { FORMATO_POR_DEFECTO, formatoDeColumna, formatoPorTipo } from './column-defaults';
6
+
7
+ /**
8
+ * R4.4 — «declarada, no implícita»: la tabla de formato por defecto.
9
+ *
10
+ * Dos promesas: que la tabla cubra **todos** los tipos del catálogo, y que
11
+ * una columna sin `format` pinta lo que la tabla dice — no un `?? 'text'`
12
+ * implícito que dependía de la memoria de quien escribía el schema.
13
+ */
14
+ describe('R4.4 — formato por defecto según field.type', () => {
15
+ const columna = (parcial: Partial<ListColumn> & Pick<ListColumn, 'key'>): ListColumn => ({
16
+ label: parcial.key,
17
+ ...parcial,
18
+ });
19
+ const campo = (key: string, type: FieldSchema['type']): FieldSchema =>
20
+ ({ key, label: key, type }) as FieldSchema;
21
+
22
+ it('cubre exactamente el catálogo de tipos de campo', () => {
23
+ // El `Record<FieldType, …>` ya lo exige en compilación; el test además
24
+ // fija que ningún tipo se quede sin fila ni aparezca una fantasma.
25
+ expect(Object.keys(FORMATO_POR_DEFECTO).sort()).toEqual([...SUPPORTED_FIELD_TYPE_LIST].sort());
26
+ expect(SUPPORTED_FIELD_TYPE_LIST.length).toBe(24);
27
+ });
28
+
29
+ it('repasa la paridad tipo a tipo con el display del detalle', () => {
30
+ const esperado: Record<string, string> = {
31
+ text: 'text',
32
+ textarea: 'text',
33
+ slug: 'text',
34
+ url: 'text',
35
+ email: 'text',
36
+ phone: 'text',
37
+ 'readonly-text': 'text',
38
+ relation: 'text',
39
+ group: 'text',
40
+ 'key-value': 'text',
41
+ 'string-list': 'text',
42
+ multiselect: 'text',
43
+ time: 'text',
44
+ list: 'text',
45
+ date: 'date',
46
+ number: 'number',
47
+ currency: 'currency',
48
+ boolean: 'boolean',
49
+ select: 'badge',
50
+ image: 'thumbnail',
51
+ file: 'text',
52
+ color: 'text',
53
+ 'rich-select': 'badge',
54
+ dropdown: 'badge',
55
+ };
56
+ expect(Object.keys(FORMATO_POR_DEFECTO).sort()).toEqual(Object.keys(esperado).sort());
57
+ for (const [tipo, formato] of Object.entries(esperado)) {
58
+ expect(formatoPorTipo(tipo), `tipo ${tipo}`).toBe(formato);
59
+ }
60
+ });
61
+
62
+ it('la declaración del schema manda sobre la tabla', () => {
63
+ expect(
64
+ formatoDeColumna(columna({ key: 'status', format: 'text' }), [campo('status', 'select')]),
65
+ ).toBe('text');
66
+ expect(
67
+ formatoDeColumna(columna({ key: 'price', format: 'badge' }), [campo('price', 'currency')]),
68
+ ).toBe('badge');
69
+ });
70
+
71
+ it('sin formato declarado usa el tipo del campo', () => {
72
+ const campos = [
73
+ campo('status', 'select'),
74
+ campo('pickup_date', 'date'),
75
+ campo('active', 'boolean'),
76
+ campo('photo', 'image'),
77
+ campo('price', 'currency'),
78
+ campo('name', 'slug'),
79
+ ];
80
+ expect(formatoDeColumna(columna({ key: 'status' }), campos)).toBe('badge');
81
+ expect(formatoDeColumna(columna({ key: 'pickup_date' }), campos)).toBe('date');
82
+ expect(formatoDeColumna(columna({ key: 'active' }), campos)).toBe('boolean');
83
+ expect(formatoDeColumna(columna({ key: 'photo' }), campos)).toBe('thumbnail');
84
+ expect(formatoDeColumna(columna({ key: 'price' }), campos)).toBe('currency');
85
+ expect(formatoDeColumna(columna({ key: 'name' }), campos)).toBe('text');
86
+ });
87
+
88
+ it('una columna sin campo asociado o con tipo mal escrito degrada a text', () => {
89
+ // El `@default` del `@switch` que R4.3 reemplazó: la degradación es la
90
+ // misma y nunca lanza.
91
+ expect(formatoDeColumna(columna({ key: 'inventada' }), [campo('otra', 'text')])).toBe('text');
92
+ expect(formatoPorTipo('holograma')).toBeUndefined();
93
+ expect(
94
+ formatoDeColumna(columna({ key: 'raro' }), [
95
+ campo('raro', 'holograma' as FieldSchema['type']),
96
+ ]),
97
+ ).toBe('text');
98
+ });
99
+ });
@@ -0,0 +1,84 @@
1
+ import type {
2
+ FieldSchema,
3
+ FieldType,
4
+ ListColumn,
5
+ ListColumnFormat,
6
+ } from '../core/models/schema.model';
7
+
8
+ /**
9
+ * **Tabla de formato de columna por defecto según `field.type`** (R4.4 del
10
+ * plan): «declarada, no implícita» — `column.format` sólo sirve para
11
+ * sobreescribirla.
12
+ *
13
+ * Es la hermana de `fields/display/display-defaults.ts` (R4.1): un solo
14
+ * sitio decide qué pinta una columna cuando el schema no dice nada, en vez
15
+ * de un `?? 'text'` repartido por la vista. El vocabulario se alinea con el
16
+ * display del detalle — `select` es chip, `date` es fecha, `image` es
17
+ * miniatura — con las dos concesiones que sólo el listado puede hacer:
18
+ * `boolean` baja al interruptor (R4.3) en vez de al chip, y `list`, que en
19
+ * la ficha es timeline, acá sigue siendo texto porque el registro de
20
+ * formatos no tiene timeline (D41).
21
+ *
22
+ * `Record<FieldType, …>` es el **tripwire de exhaustividad en compilación**:
23
+ * un tipo nuevo obliga a elegir su formato de lista antes de que el build
24
+ * pase, y un tipo retirado obliga a limpiar la entrada.
25
+ */
26
+ export const FORMATO_POR_DEFECTO: Record<FieldType, ListColumnFormat> = {
27
+ text: 'text',
28
+ textarea: 'text',
29
+ slug: 'text',
30
+ url: 'text',
31
+ email: 'text',
32
+ phone: 'text',
33
+ 'readonly-text': 'text',
34
+ relation: 'text',
35
+ group: 'text',
36
+ 'key-value': 'text',
37
+ 'string-list': 'text',
38
+ multiselect: 'text',
39
+ time: 'text',
40
+ list: 'text',
41
+ date: 'date',
42
+ number: 'number',
43
+ currency: 'currency',
44
+ boolean: 'boolean',
45
+ select: 'badge',
46
+ image: 'thumbnail',
47
+ // Un archivo no tiene miniatura: en la lista va como URL en crudo (R9.5.3).
48
+ file: 'text',
49
+ // Un color se pinta como texto (el hex #RRGGBB) en el listado.
50
+ color: 'text',
51
+ // Un rich-select se pinta como badge con el label de la opción.
52
+ 'rich-select': 'badge',
53
+ // Un dropdown se pinta como badge con el label de la opción.
54
+ dropdown: 'badge',
55
+ };
56
+
57
+ /**
58
+ * Formato por defecto para un `type`, o `undefined` si el schema lo escribió
59
+ * mal (tipo fuera de la unión). La degradación — igual que `displayLoader()` —
60
+ * es `text`, que era el `@default` del `@switch` que R4.3 reemplazó.
61
+ */
62
+ export function formatoPorTipo(type: string): ListColumnFormat | undefined {
63
+ return Object.prototype.hasOwnProperty.call(FORMATO_POR_DEFECTO, type)
64
+ ? FORMATO_POR_DEFECTO[type as FieldType]
65
+ : undefined;
66
+ }
67
+
68
+ /**
69
+ * Formato final de una columna: la declaración del schema si existe, si no,
70
+ * la tabla de arriba según el `type` del campo con esa `key`, y si la columna
71
+ * ni siquiera aparece en `fields` (columna derivada o key inventada), `text`.
72
+ *
73
+ * Es la única puerta por la que `list-view` decide qué pinta una celda.
74
+ */
75
+ export function formatoDeColumna(
76
+ column: ListColumn,
77
+ fields: readonly FieldSchema[],
78
+ ): ListColumnFormat {
79
+ if (column.format) {
80
+ return column.format;
81
+ }
82
+ const field = fields.find((candidate) => candidate.key === column.key);
83
+ return (field ? formatoPorTipo(field.type) : undefined) ?? 'text';
84
+ }