@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,274 @@
1
+ import { Component, input } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { beforeEach, describe, expect, it, vi } from 'vitest';
4
+
5
+ import type { ListColumn } from '../core/models/schema.model';
6
+ import { RenderHost } from '../core/registry/render-host';
7
+ import { COLUMN_FORMAT_TYPES } from './descriptors';
8
+ import type { ColumnCell } from './column-cell';
9
+
10
+ /**
11
+ * Los 7 formatos de columna (R4.3 · `kind: 'column-format'`).
12
+ *
13
+ * Cada caso es un `@case` del `@switch` que vivía en `list-view`: si el
14
+ * formato pinta distinto de lo que pintaba allí, este test se pone rojo. Se
15
+ * monta por `RenderHost` y no por el componente suelto para cubrir también
16
+ * la resolución del registro (descriptor, `inputs`, degradación).
17
+ */
18
+ @Component({
19
+ selector: 'spec-column-host',
20
+ imports: [RenderHost],
21
+ template: `
22
+ <app-render-host kind="column-format" [type]="formato()" [config]="column()" [data]="cell()" />
23
+ `,
24
+ })
25
+ class Host {
26
+ readonly formato = input.required<string>();
27
+ readonly column = input.required<ListColumn>();
28
+ readonly cell = input.required<ColumnCell>();
29
+ }
30
+
31
+ describe('R4.3 — formatos de columna registrados', () => {
32
+ beforeEach(async () => {
33
+ await TestBed.configureTestingModule({ imports: [Host] }).compileComponents();
34
+ });
35
+
36
+ /**
37
+ * Monta el host y espera el `load()` perezoso del formato.
38
+ *
39
+ * `whenStable()` no cubre el `import()` dinámico por sí solo, así que se
40
+ * espera acotadamente mientras quede `<app-skeleton>`; si el formato nunca
41
+ * monta, la espera se agota y falla la aserción del test.
42
+ */
43
+ async function pintar(column: ListColumn, cell: Partial<ColumnCell> = {}): Promise<HTMLElement> {
44
+ const fixture = TestBed.createComponent(Host);
45
+ fixture.componentRef.setInput('formato', column.format ?? 'text');
46
+ fixture.componentRef.setInput('column', column);
47
+ fixture.componentRef.setInput('cell', celda({ column, ...cell }));
48
+ fixture.detectChanges();
49
+ await fixture.whenStable();
50
+ for (
51
+ let intento = 0;
52
+ intento < 100 && fixture.nativeElement.querySelector('app-skeleton');
53
+ intento++
54
+ ) {
55
+ fixture.detectChanges();
56
+ await new Promise((resolve) => setTimeout(resolve, 1));
57
+ }
58
+ fixture.detectChanges();
59
+ return fixture.nativeElement as HTMLElement;
60
+ }
61
+
62
+ /** Texto de la celda con los espacios duros de `Intl` normalizados. */
63
+ function texto(nodo: HTMLElement): string {
64
+ return (nodo.textContent ?? '').replace(/\u00a0/g, ' ');
65
+ }
66
+
67
+ /** Celda mínima; cada caso sobreescribe lo que necesita. */
68
+ function celda(parcial: { column: ListColumn } & Partial<ColumnCell>): ColumnCell {
69
+ return {
70
+ row: {},
71
+ fields: [],
72
+ title: 'Una fila',
73
+ canUpdate: false,
74
+ saving: false,
75
+ currency: 'PEN',
76
+ ...parcial,
77
+ };
78
+ }
79
+
80
+ it('los 7 formatos de la unión están registrados', () => {
81
+ // `ListColumnFormat` vive en tipos y el manifiesto en runtime: sólo un
82
+ // test reconcilia los dos (mismo tripwire que campos y widgets).
83
+ expect(COLUMN_FORMAT_TYPES.length).toBe(7);
84
+ });
85
+
86
+ describe('text', () => {
87
+ const col: ListColumn = { key: 'nombre', label: 'Nombre', format: 'text' };
88
+
89
+ it('pinta el valor crudo', async () => {
90
+ const nodo = await pintar(col, { row: { nombre: 'ORD-42' } });
91
+ expect(nodo.textContent).toContain('ORD-42');
92
+ });
93
+
94
+ it('null y undefined salen vacíos', async () => {
95
+ expect((await pintar(col, { row: { nombre: null } })).textContent?.trim()).toBe('');
96
+ expect((await pintar(col, { row: {} })).textContent?.trim()).toBe('');
97
+ });
98
+
99
+ it('un objeto sale [object Object] (degradación heredada)', async () => {
100
+ const nodo = await pintar(col, { row: { nombre: { a: 1 } } });
101
+ expect(nodo.textContent).toContain('[object Object]');
102
+ });
103
+ });
104
+
105
+ describe('number', () => {
106
+ const col: ListColumn = { key: 'stock', label: 'Stock', format: 'number' };
107
+
108
+ it('agrupa con separador es-PE', async () => {
109
+ const nodo = await pintar(col, { row: { stock: 1234567.5 } });
110
+ expect(nodo.textContent).toContain('1,234,567.5');
111
+ });
112
+
113
+ it('no numérico sale crudo y null sigue saliendo «0»', async () => {
114
+ expect((await pintar(col, { row: { stock: 'varios' } })).textContent).toContain('varios');
115
+ // Quirk heredado de `formatNumber`: `Number(null)` es 0 y por eso la
116
+ // lista ya mostraba «0» en vez de vacío. Se conserva — cambiarlo es
117
+ // otra decisión, no este registro.
118
+ expect((await pintar(col, { row: { stock: null } })).textContent?.trim()).toBe('0');
119
+ });
120
+ });
121
+
122
+ describe('currency', () => {
123
+ const col: ListColumn = { key: 'price', label: 'Precio', format: 'currency' };
124
+
125
+ it('usa la moneda que entrega la vista', async () => {
126
+ const nodo = await pintar(col, { row: { price: 1234.5 } });
127
+ // `Intl` separa el símbolo con espacio duro (U+00A0): se normaliza para
128
+ // que el aserto no dependa de ese byte.
129
+ expect(texto(nodo)).toContain('S/ 1,234.50');
130
+ });
131
+
132
+ it('cambia de moneda con la celda', async () => {
133
+ const nodo = await pintar(col, { row: { price: 1234.5 }, currency: 'USD' });
134
+ expect(texto(nodo)).toContain('USD 1,234.50');
135
+ });
136
+ });
137
+
138
+ describe('date', () => {
139
+ const col: ListColumn = { key: 'fecha', label: 'Fecha', format: 'date' };
140
+
141
+ it('formato corto es-PE en UTC', async () => {
142
+ const nodo = await pintar(col, { row: { fecha: '2026-12-25' } });
143
+ expect(nodo.textContent).toContain('25/12/2026');
144
+ });
145
+
146
+ it('no parseable sale crudo', async () => {
147
+ expect((await pintar(col, { row: { fecha: 'ayer' } })).textContent).toContain('ayer');
148
+ });
149
+ });
150
+
151
+ describe('badge', () => {
152
+ const col: ListColumn = { key: 'status', label: 'Estado', format: 'badge' };
153
+
154
+ it('clasifica el estado como el display del detalle (D41)', async () => {
155
+ // `warning` se pinta como `accent` (bg-accent/20 + text-accent-text).
156
+ const pendiente = await pintar(col, { row: { status: 'PENDIENTE' } });
157
+ expect(pendiente.querySelector('app-badge')?.className).toContain('bg-accent/20');
158
+
159
+ const entregado = await pintar(col, { row: { status: 'ENTREGADO' } });
160
+ expect(entregado.querySelector('app-badge')?.className).toContain('bg-success');
161
+
162
+ const cancelado = await pintar(col, { row: { status: 'Cancelado' } });
163
+ expect(cancelado.querySelector('app-badge')?.className).toContain('bg-danger');
164
+
165
+ const otro = await pintar(col, { row: { status: 'Vigente' } });
166
+ expect(otro.querySelector('app-badge')?.className).toContain('bg-neutral');
167
+ });
168
+
169
+ it('un select pinta la etiqueta de la opción, no el valor crudo', async () => {
170
+ const nodo = await pintar(col, {
171
+ row: { status: 'preparacion' },
172
+ fields: [
173
+ {
174
+ key: 'status',
175
+ label: 'Estado',
176
+ type: 'select',
177
+ options: [
178
+ { value: 'preparacion', label: 'En preparación' },
179
+ { value: 'entregado', label: 'Entregado' },
180
+ ],
181
+ },
182
+ ],
183
+ });
184
+ expect(nodo.textContent).toContain('En preparación');
185
+ });
186
+
187
+ it('el vacío sale «Sin dato» en danger', async () => {
188
+ const nodo = await pintar(col, { row: { status: '' } });
189
+ expect(nodo.textContent).toContain('Sin dato');
190
+ expect(nodo.querySelector('app-badge')?.className).toContain('bg-danger');
191
+ });
192
+
193
+ it('un booleano usa trueLabel/falseLabel del campo', async () => {
194
+ const nodo = await pintar(col, {
195
+ row: { status: true },
196
+ fields: [{ key: 'status', label: 'Estado', type: 'boolean', trueLabel: 'Vigente' }],
197
+ });
198
+ expect(nodo.textContent).toContain('Vigente');
199
+ expect(nodo.querySelector('app-badge')?.className).toContain('bg-success');
200
+ });
201
+ });
202
+
203
+ describe('boolean', () => {
204
+ const col: ListColumn = { key: 'active', label: 'Activo', format: 'boolean' };
205
+ const booleano = { key: 'active', label: 'Activo', type: 'boolean' as const };
206
+
207
+ it('con permiso y campo booleano dibuja el interruptor', async () => {
208
+ const nodo = await pintar(col, {
209
+ row: { active: true },
210
+ fields: [booleano],
211
+ canUpdate: true,
212
+ });
213
+ const box = nodo.querySelector<HTMLInputElement>('input[type="checkbox"]');
214
+ expect(box, 'debe dibujarse el interruptor').not.toBeNull();
215
+ expect(box?.checked).toBe(true);
216
+ expect(box?.getAttribute('aria-label')).toBe('Activo: Una fila');
217
+ });
218
+
219
+ it('sin permiso cae al badge', async () => {
220
+ const nodo = await pintar(col, { row: { active: true }, fields: [booleano] });
221
+ expect(nodo.querySelector('input[type="checkbox"]')).toBeNull();
222
+ expect(nodo.textContent).toContain('Sí');
223
+ });
224
+
225
+ it('mientras la fila guarda, el interruptor queda bloqueado', async () => {
226
+ const nodo = await pintar(col, {
227
+ row: { active: false },
228
+ fields: [booleano],
229
+ canUpdate: true,
230
+ saving: true,
231
+ });
232
+ const box = nodo.querySelector<HTMLInputElement>('input[type="checkbox"]');
233
+ expect(box?.disabled, 'mientras guarda, la fila está bloqueada').toBe(true);
234
+ });
235
+
236
+ it('al alternar escribe por la celda', async () => {
237
+ const toggle = vi.fn();
238
+ const nodo = await pintar(col, {
239
+ row: { active: false },
240
+ fields: [booleano],
241
+ canUpdate: true,
242
+ toggle,
243
+ });
244
+ nodo.querySelector<HTMLInputElement>('input[type="checkbox"]')!.click();
245
+ expect(toggle).toHaveBeenCalledWith(true);
246
+ });
247
+ });
248
+
249
+ describe('thumbnail', () => {
250
+ const col: ListColumn = { key: 'image_url', label: 'Imagen', format: 'thumbnail' };
251
+
252
+ it('pinta la imagen con su alt', async () => {
253
+ const nodo = await pintar(col, { row: { image_url: 'https://x.test/a.png' } });
254
+ const img = nodo.querySelector('img');
255
+ expect(img?.getAttribute('src')).toBe('https://x.test/a.png');
256
+ expect(img?.getAttribute('alt')).toBe('');
257
+ });
258
+
259
+ it('toma el primer item de images[] con su image_alt', async () => {
260
+ const nodo = await pintar(
261
+ { key: 'images', label: 'Imágenes', format: 'thumbnail' },
262
+ { row: { images: [{ image_url: 'https://x.test/b.png', image_alt: 'Frente' }] } },
263
+ );
264
+ expect(nodo.querySelector('img')?.getAttribute('src')).toBe('https://x.test/b.png');
265
+ expect(nodo.querySelector('img')?.getAttribute('alt')).toBe('Frente');
266
+ });
267
+
268
+ it('sin URL pinta el guion', async () => {
269
+ const nodo = await pintar(col, { row: { image_url: '' } });
270
+ expect(nodo.textContent).toContain('—');
271
+ expect(nodo.querySelector('img')).toBeNull();
272
+ });
273
+ });
274
+ });
@@ -0,0 +1,113 @@
1
+ import type { RenderDescriptor, RenderExtras } from '../core/registry/render-registry';
2
+
3
+ /**
4
+ * Manifiesto de descriptores de formato de columna (R4.3 · `kind:
5
+ * 'column-format'`).
6
+ *
7
+ * Es el equivalente a `fields/descriptors.ts` y `widgets/descriptors.ts`:
8
+ * una línea por formato y ningún import de componente — cada `load()` guarda
9
+ * el cierre de un `import()` **con literal**, que es lo que permite al
10
+ * bundler partir el chunk; el registro los recoge al importar este módulo
11
+ * (`core/registry/render-registry.ts`).
12
+ *
13
+ * **Añadir un formato** ⇒ crear `format-<nombre>.ts` + una entrada acá.
14
+ * `list-view` no se toca: ése es el criterio de éxito de R4.3.
15
+ *
16
+ * Todos comparten el cableado: el descriptor sólo expone la celda, que la
17
+ * vista cuelga de `extras.data`.
18
+ */
19
+ const inputs = (_config: unknown, extras: RenderExtras): Record<string, unknown> => ({
20
+ cell: extras.data,
21
+ });
22
+
23
+ const text: RenderDescriptor = {
24
+ kind: 'column-format',
25
+ type: 'text',
26
+ since: '1.0',
27
+ surfaces: ['list'],
28
+ load: () => import('./format-text').then((module) => module.ColumnFormatText),
29
+ inputs,
30
+ };
31
+
32
+ const badge: RenderDescriptor = {
33
+ kind: 'column-format',
34
+ type: 'badge',
35
+ since: '1.0',
36
+ surfaces: ['list'],
37
+ load: () => import('./format-badge').then((module) => module.ColumnFormatBadge),
38
+ inputs,
39
+ };
40
+
41
+ const thumbnail: RenderDescriptor = {
42
+ kind: 'column-format',
43
+ type: 'thumbnail',
44
+ since: '1.0',
45
+ surfaces: ['list'],
46
+ load: () => import('./format-thumbnail').then((module) => module.ColumnFormatThumbnail),
47
+ inputs,
48
+ };
49
+
50
+ const boolean: RenderDescriptor = {
51
+ kind: 'column-format',
52
+ type: 'boolean',
53
+ since: '1.0',
54
+ surfaces: ['list'],
55
+ load: () => import('./format-boolean').then((module) => module.ColumnFormatBoolean),
56
+ inputs,
57
+ };
58
+
59
+ const number: RenderDescriptor = {
60
+ kind: 'column-format',
61
+ type: 'number',
62
+ since: '1.0',
63
+ surfaces: ['list'],
64
+ load: () => import('./format-number').then((module) => module.ColumnFormatNumber),
65
+ inputs,
66
+ };
67
+
68
+ const currency: RenderDescriptor = {
69
+ kind: 'column-format',
70
+ type: 'currency',
71
+ since: '1.0',
72
+ surfaces: ['list'],
73
+ load: () => import('./format-currency').then((module) => module.ColumnFormatCurrency),
74
+ inputs,
75
+ };
76
+
77
+ const date: RenderDescriptor = {
78
+ kind: 'column-format',
79
+ type: 'date',
80
+ since: '1.0',
81
+ surfaces: ['list'],
82
+ load: () => import('./format-date').then((module) => module.ColumnFormatDate),
83
+ inputs,
84
+ };
85
+
86
+ /** Los 7 formatos del `@switch` que este registro reemplaza (R4.3). */
87
+ export const columnFormatDescriptors: readonly RenderDescriptor[] = [
88
+ text,
89
+ badge,
90
+ thumbnail,
91
+ boolean,
92
+ number,
93
+ currency,
94
+ date,
95
+ ];
96
+
97
+ /**
98
+ * Los 7 nombres, como dato.
99
+ *
100
+ * Va en paralelo a `ListColumnFormat` (unión de `schema.model`) y lo vigila
101
+ * `core/registry/contract.spec.ts`: la unión vive en tipos y el registro en
102
+ * runtime, y sólo un test reconcilia los dos — el mismo tripwire que ya
103
+ * existe para los 20 campos y los 6 widgets.
104
+ */
105
+ export const COLUMN_FORMAT_TYPES = [
106
+ 'text',
107
+ 'badge',
108
+ 'thumbnail',
109
+ 'boolean',
110
+ 'number',
111
+ 'currency',
112
+ 'date',
113
+ ] as const;
@@ -0,0 +1,51 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { Badge, type BadgeKind } from '../shared/components/badge/badge';
4
+ import { resolveBadgeKind } from '../fields/display/display-badge';
5
+ import type { SelectField } from '../core/models/schema.model';
6
+ import { getValue } from '../shared/utils/field-path';
7
+ import type { ColumnCell } from './column-cell';
8
+
9
+ /**
10
+ * Formato `badge` (R4.3, afinado en R4.4).
11
+ *
12
+ * Un booleano usa `trueLabel`/`falseLabel` del campo (o «Sí»/«No») con
13
+ * `success`/`danger` — el vocabulario de la lista para el control que
14
+ * permite escribir. El vacío sigue siendo «Sin dato»/`danger`.
15
+ *
16
+ * El resto alinea el chip con el display del detalle (D41): un `select`
17
+ * pinta la **etiqueta** de la opción y los estados se clasifican con
18
+ * `resolveBadgeKind`, el mismo criterio que la cabecera de la ficha —
19
+ * antes, toda columna declarada `badge` salía verde sin mirar el valor.
20
+ */
21
+ @Component({
22
+ selector: 'app-column-format-badge',
23
+ imports: [Badge],
24
+ template: `<app-badge [label]="badge().label" [kind]="badge().kind" />`,
25
+ })
26
+ export class ColumnFormatBadge {
27
+ readonly cell = input.required<ColumnCell>();
28
+
29
+ protected readonly badge = computed(() => badgeInfo(this.cell()));
30
+ }
31
+
32
+ export function badgeInfo(cell: ColumnCell): { label: string; kind: BadgeKind } {
33
+ const value = getValue(cell.row, cell.column.key);
34
+ const field = cell.fields.find((candidate) => candidate.key === cell.column.key);
35
+ if (typeof value === 'boolean') {
36
+ const label =
37
+ value === true
38
+ ? ((field?.type === 'boolean' ? field.trueLabel : undefined) ?? 'Sí')
39
+ : ((field?.type === 'boolean' ? field.falseLabel : undefined) ?? 'No');
40
+ return { label, kind: value ? 'success' : 'danger' };
41
+ }
42
+ const text = value == null ? '' : String(value);
43
+ if (text === '') {
44
+ return { label: 'Sin dato', kind: 'danger' };
45
+ }
46
+ const etiqueta =
47
+ field?.type === 'select'
48
+ ? ((field as SelectField).options?.find((option) => option.value === text)?.label ?? text)
49
+ : text;
50
+ return { label: etiqueta, kind: resolveBadgeKind(text) };
51
+ }
@@ -0,0 +1,58 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { Badge } from '../shared/components/badge/badge';
4
+ import { getValue } from '../shared/utils/field-path';
5
+ import type { ColumnCell } from './column-cell';
6
+ import { badgeInfo } from './format-badge';
7
+
8
+ /**
9
+ * Formato `boolean` (R4.3): interruptor o badge.
10
+ *
11
+ * Es la rama `@case ('boolean')` de `list-view`, con una sola diferencia de
12
+ * domicilio: hoy sólo monta este componente si el formato efectivo ya es
13
+ * `boolean`, así que la comprobación `column.format === 'boolean'` que hacía
14
+ * `isSwitchColumn` sobra. El criterio restante — `active` booleano y con
15
+ * permiso de edición (base.md §10) — vive acá, que es quien dibuja el control.
16
+ */
17
+ @Component({
18
+ selector: 'app-column-format-boolean',
19
+ imports: [Badge],
20
+ template: `
21
+ @if (esInterruptor()) {
22
+ <input
23
+ type="checkbox"
24
+ class="size-5 cursor-pointer accent-primary"
25
+ [checked]="valor()"
26
+ [disabled]="cell().saving"
27
+ [attr.aria-label]="cell().column.label + ': ' + cell().title"
28
+ (click)="$event.stopPropagation()"
29
+ (change)="alternar($event)"
30
+ />
31
+ } @else {
32
+ <app-badge [label]="badge().label" [kind]="badge().kind" />
33
+ }
34
+ `,
35
+ })
36
+ export class ColumnFormatBoolean {
37
+ readonly cell = input.required<ColumnCell>();
38
+
39
+ protected readonly badge = computed(() => badgeInfo(this.cell()));
40
+
41
+ protected readonly valor = computed(() => {
42
+ const value = getValue(this.cell().row, this.cell().column.key);
43
+ return value === true || value === 'true';
44
+ });
45
+
46
+ /** `active` declarado como boolean en el schema → interruptor (base.md §10). */
47
+ protected readonly esInterruptor = computed(() => {
48
+ const cell = this.cell();
49
+ if (cell.column.key !== 'active' || !cell.canUpdate) {
50
+ return false;
51
+ }
52
+ return cell.fields.some((field) => field.type === 'boolean' && field.key === 'active');
53
+ });
54
+
55
+ protected alternar(event: Event): void {
56
+ this.cell().toggle?.((event.target as HTMLInputElement).checked);
57
+ }
58
+ }
@@ -0,0 +1,30 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { getValue } from '../shared/utils/field-path';
4
+ import type { ColumnCell } from './column-cell';
5
+
6
+ /**
7
+ * Formato `currency` (R4.3). Igual que el helper de `list-view`: `es-PE`,
8
+ * moneda tomada del schema (se pasaba desde la vista porque sólo ella la
9
+ * conoce) y texto crudo si el valor no es número.
10
+ */
11
+ @Component({
12
+ selector: 'app-column-format-currency',
13
+ template: `{{ precio() }}`,
14
+ })
15
+ export class ColumnFormatCurrency {
16
+ readonly cell = input.required<ColumnCell>();
17
+
18
+ protected readonly precio = computed(() => {
19
+ const cell = this.cell();
20
+ const value = getValue(cell.row, cell.column.key);
21
+ const number = Number(value);
22
+ if (!Number.isFinite(number)) {
23
+ return value == null ? '' : String(value);
24
+ }
25
+ return new Intl.NumberFormat('es-PE', {
26
+ style: 'currency',
27
+ currency: cell.currency,
28
+ }).format(number);
29
+ });
30
+ }
@@ -0,0 +1,35 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { getValue } from '../shared/utils/field-path';
4
+ import type { ColumnCell } from './column-cell';
5
+
6
+ function formatDate(value: unknown): string {
7
+ if (typeof value !== 'string' || value === '') {
8
+ return value == null ? '' : String(value);
9
+ }
10
+ const date = new Date(value);
11
+ if (Number.isNaN(date.getTime())) {
12
+ return value;
13
+ }
14
+ // Fechas `YYYY-MM-DD`: forzar UTC para no desfasar un día.
15
+ return date.toLocaleDateString('es-PE', { timeZone: 'UTC' });
16
+ }
17
+
18
+ /**
19
+ * Formato `date` (R4.3). Corto (`toLocaleDateString`), a diferencia del
20
+ * display de la ficha, que va con `dateStyle: 'medium'`: cada superficie
21
+ * con su formato, pero ambos desde su propio componente.
22
+ */
23
+ @Component({
24
+ selector: 'app-column-format-date',
25
+ template: `{{ fecha() }}`,
26
+ })
27
+ export class ColumnFormatDate {
28
+ readonly cell = input.required<ColumnCell>();
29
+
30
+ protected readonly fecha = computed(() => formatDate(this.valor()));
31
+
32
+ private valor(): unknown {
33
+ return getValue(this.cell().row, this.cell().column.key);
34
+ }
35
+ }
@@ -0,0 +1,32 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { getValue } from '../shared/utils/field-path';
4
+ import type { ColumnCell } from './column-cell';
5
+
6
+ const NUMBER_FORMAT = new Intl.NumberFormat('es-PE');
7
+
8
+ function formatNumber(value: unknown): string {
9
+ const number = Number(value);
10
+ if (!Number.isFinite(number)) {
11
+ return value == null ? '' : String(value);
12
+ }
13
+ return NUMBER_FORMAT.format(number);
14
+ }
15
+
16
+ /**
17
+ * Formato `number` (R4.3). Misma fórmula que el helper de `list-view`:
18
+ * miles con separador `es-PE` y texto crudo si el valor no es número.
19
+ */
20
+ @Component({
21
+ selector: 'app-column-format-number',
22
+ template: `{{ numero() }}`,
23
+ })
24
+ export class ColumnFormatNumber {
25
+ readonly cell = input.required<ColumnCell>();
26
+
27
+ protected readonly numero = computed(() => formatNumber(this.valor()));
28
+
29
+ private valor(): unknown {
30
+ return getValue(this.cell().row, this.cell().column.key);
31
+ }
32
+ }
@@ -0,0 +1,23 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { getValue } from '../shared/utils/field-path';
4
+ import type { ColumnCell } from './column-cell';
5
+
6
+ /**
7
+ * Formato `text` (R4.3 · el `@default` del `@switch` viejo).
8
+ *
9
+ * `cellText` de `list-view`: `null`/`undefined` se pintan vacíos y el resto
10
+ * pasa por `String()` — un objeto sale `[object Object]`, igual que antes.
11
+ */
12
+ @Component({
13
+ selector: 'app-column-format-text',
14
+ template: `{{ texto() }}`,
15
+ })
16
+ export class ColumnFormatText {
17
+ readonly cell = input.required<ColumnCell>();
18
+
19
+ protected readonly texto = computed(() => {
20
+ const value = getValue(this.cell().row, this.cell().column.key);
21
+ return value == null ? '' : String(value);
22
+ });
23
+ }
@@ -0,0 +1,63 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+
3
+ import { getValue } from '../shared/utils/field-path';
4
+ import type { ColumnCell } from './column-cell';
5
+
6
+ /**
7
+ * Formato `thumbnail` (R4.3): miniatura de la fila.
8
+ *
9
+ * Tanto `thumbnailSrc` como `thumbnailAlt` se mudan desde `list-view`: la
10
+ * columna puede apuntar a `image_url`, al primer elemento de `images[]` o a
11
+ * un objeto con `image_url`, y el texto alternativo sale del item o de la
12
+ * fila. Sin URL se pinta el `—` de siempre.
13
+ */
14
+ @Component({
15
+ selector: 'app-column-format-thumbnail',
16
+ template: `
17
+ @if (src(); as url) {
18
+ <img [src]="url" [alt]="alt()" class="size-9 rounded-md object-cover" loading="lazy" />
19
+ } @else {
20
+ <span class="text-neutral" aria-hidden="true">—</span>
21
+ }
22
+ `,
23
+ })
24
+ export class ColumnFormatThumbnail {
25
+ readonly cell = input.required<ColumnCell>();
26
+
27
+ protected readonly src = computed(() => {
28
+ const value = this.valor();
29
+ if (typeof value === 'string') {
30
+ return value !== '' ? value : null;
31
+ }
32
+ if (Array.isArray(value)) {
33
+ const first = value[0];
34
+ const url = typeof first === 'string' ? first : getValue(first, 'image_url');
35
+ return typeof url === 'string' && url !== '' ? url : null;
36
+ }
37
+ if (value && typeof value === 'object') {
38
+ const url = getValue(value, 'image_url');
39
+ return typeof url === 'string' && url !== '' ? url : null;
40
+ }
41
+ return null;
42
+ });
43
+
44
+ protected readonly alt = computed(() => {
45
+ const value = this.valor();
46
+ let item: unknown = this.cell().row;
47
+ if (Array.isArray(value)) {
48
+ item = value[0] ?? null;
49
+ } else if (value && typeof value === 'object') {
50
+ item = value;
51
+ }
52
+ const alt = item == null ? null : getValue(item, 'image_alt');
53
+ if (typeof alt === 'string' && alt !== '') {
54
+ return alt;
55
+ }
56
+ const rootAlt = this.cell().row['image_alt'];
57
+ return typeof rootAlt === 'string' ? rootAlt : '';
58
+ });
59
+
60
+ private valor(): unknown {
61
+ return getValue(this.cell().row, this.cell().column.key);
62
+ }
63
+ }