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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/ng-package.json +7 -0
  2. package/package.json +3 -15
  3. package/src/lib/actions/action-registry.spec.ts +52 -0
  4. package/src/lib/actions/action-registry.ts +126 -0
  5. package/src/lib/actions/action-service.spec.ts +215 -0
  6. package/src/lib/actions/action-service.ts +87 -0
  7. package/src/lib/actions/descriptors.spec.ts +155 -0
  8. package/src/lib/actions/descriptors.ts +154 -0
  9. package/src/lib/columns/column-cell.ts +33 -0
  10. package/src/lib/columns/column-defaults.spec.ts +99 -0
  11. package/src/lib/columns/column-defaults.ts +84 -0
  12. package/src/lib/columns/column-format.spec.ts +274 -0
  13. package/src/lib/columns/descriptors.ts +113 -0
  14. package/src/lib/columns/format-badge.ts +51 -0
  15. package/src/lib/columns/format-boolean.ts +58 -0
  16. package/src/lib/columns/format-currency.ts +30 -0
  17. package/src/lib/columns/format-date.ts +35 -0
  18. package/src/lib/columns/format-number.ts +32 -0
  19. package/src/lib/columns/format-text.ts +23 -0
  20. package/src/lib/columns/format-thumbnail.ts +63 -0
  21. package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
  22. package/src/lib/core/guards/auth.guard.spec.ts +88 -0
  23. package/src/lib/core/guards/auth.guard.ts +48 -0
  24. package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
  25. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
  26. package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
  27. package/src/lib/core/models/api.model.ts +104 -0
  28. package/src/lib/core/models/schema-descriptions.ts +118 -0
  29. package/src/lib/core/models/schema.model.ts +1097 -0
  30. package/src/lib/core/registry/composition-validator.spec.ts +358 -0
  31. package/src/lib/core/registry/composition-validator.ts +270 -0
  32. package/src/lib/core/registry/field-validators.spec.ts +199 -0
  33. package/src/lib/core/registry/field-validators.ts +440 -0
  34. package/src/lib/core/registry/render-host.spec.ts +208 -0
  35. package/src/lib/core/registry/render-host.ts +646 -0
  36. package/src/lib/core/registry/render-registry.spec.ts +96 -0
  37. package/src/lib/core/registry/render-registry.ts +312 -0
  38. package/src/lib/core/registry/schema-catalog.ts +156 -0
  39. package/src/lib/core/services/api.service.spec.ts +1142 -0
  40. package/src/lib/core/services/api.service.ts +371 -0
  41. package/src/lib/core/services/auth.service.spec.ts +653 -0
  42. package/src/lib/core/services/auth.service.ts +503 -0
  43. package/src/lib/core/services/config.service.spec.ts +121 -0
  44. package/src/lib/core/services/config.service.ts +119 -0
  45. package/src/lib/core/services/data-source.service.spec.ts +179 -0
  46. package/src/lib/core/services/data-source.service.ts +142 -0
  47. package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
  48. package/src/lib/core/services/error-mapper.service.ts +49 -0
  49. package/src/lib/core/services/notification.service.ts +73 -0
  50. package/src/lib/core/services/relation-drawer.service.ts +41 -0
  51. package/src/lib/core/services/theme.service.spec.ts +106 -0
  52. package/src/lib/core/services/theme.service.ts +86 -0
  53. package/src/lib/core/services/transport.spec.ts +198 -0
  54. package/src/lib/core/services/transport.ts +164 -0
  55. package/src/lib/core/services/upload.service.ts +81 -0
  56. package/src/lib/core/testing/categories.schema.ts +125 -0
  57. package/src/lib/core/testing/orders.schema.ts +372 -0
  58. package/src/lib/core/testing/products.schema.ts +258 -0
  59. package/src/lib/core/testing/site.schema.ts +195 -0
  60. package/src/lib/core/testing/test-catalog.ts +21 -0
  61. package/src/lib/core/tokens.ts +112 -0
  62. package/src/lib/core/utils/clipboard.spec.ts +55 -0
  63. package/src/lib/core/utils/clipboard.ts +90 -0
  64. package/src/lib/core/utils/context-resolver.spec.ts +95 -0
  65. package/src/lib/core/utils/context-resolver.ts +101 -0
  66. package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
  67. package/src/lib/core/utils/response-unwrapper.ts +103 -0
  68. package/src/lib/core/utils/source-options.spec.ts +96 -0
  69. package/src/lib/core/utils/source-options.ts +54 -0
  70. package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
  71. package/src/lib/core/utils/template-interpolator.ts +56 -0
  72. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
  73. package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
  74. package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
  75. package/src/lib/fields/boolean/boolean.ts +40 -0
  76. package/src/lib/fields/color/color.descriptor.ts +12 -0
  77. package/src/lib/fields/color/color.ts +34 -0
  78. package/src/lib/fields/currency/currency.descriptor.ts +12 -0
  79. package/src/lib/fields/currency/currency.ts +67 -0
  80. package/src/lib/fields/date/date.descriptor.ts +12 -0
  81. package/src/lib/fields/date/date.ts +36 -0
  82. package/src/lib/fields/descriptors.ts +122 -0
  83. package/src/lib/fields/display/display-badge.ts +65 -0
  84. package/src/lib/fields/display/display-date.ts +39 -0
  85. package/src/lib/fields/display/display-defaults.ts +84 -0
  86. package/src/lib/fields/display/display-file.ts +41 -0
  87. package/src/lib/fields/display/display-image.ts +54 -0
  88. package/src/lib/fields/display/display-list.ts +107 -0
  89. package/src/lib/fields/display/display-number.ts +41 -0
  90. package/src/lib/fields/display/display-text.ts +42 -0
  91. package/src/lib/fields/display/display.spec.ts +242 -0
  92. package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
  93. package/src/lib/fields/dropdown/dropdown.ts +45 -0
  94. package/src/lib/fields/email/email.descriptor.ts +12 -0
  95. package/src/lib/fields/email/email.ts +28 -0
  96. package/src/lib/fields/field-aria.ts +60 -0
  97. package/src/lib/fields/field-host/field-host.spec.ts +118 -0
  98. package/src/lib/fields/field-host/field-host.ts +47 -0
  99. package/src/lib/fields/field-node.ts +88 -0
  100. package/src/lib/fields/file/file.descriptor.ts +14 -0
  101. package/src/lib/fields/file/file.spec.ts +134 -0
  102. package/src/lib/fields/file/file.ts +281 -0
  103. package/src/lib/fields/group/group.descriptor.ts +21 -0
  104. package/src/lib/fields/group/group.ts +110 -0
  105. package/src/lib/fields/image/image.descriptor.ts +12 -0
  106. package/src/lib/fields/image/image.spec.ts +117 -0
  107. package/src/lib/fields/image/image.ts +281 -0
  108. package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
  109. package/src/lib/fields/key-value/key-value.ts +283 -0
  110. package/src/lib/fields/list/list.descriptor.ts +21 -0
  111. package/src/lib/fields/list/list.ts +376 -0
  112. package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
  113. package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
  114. package/src/lib/fields/multiselect/multiselect.ts +152 -0
  115. package/src/lib/fields/number/number.descriptor.ts +12 -0
  116. package/src/lib/fields/number/number.ts +40 -0
  117. package/src/lib/fields/phone/phone.descriptor.ts +12 -0
  118. package/src/lib/fields/phone/phone.ts +46 -0
  119. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
  120. package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
  121. package/src/lib/fields/relation/relation.descriptor.ts +12 -0
  122. package/src/lib/fields/relation/relation.spec.ts +193 -0
  123. package/src/lib/fields/relation/relation.ts +224 -0
  124. package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
  125. package/src/lib/fields/rich-select/rich-select.ts +175 -0
  126. package/src/lib/fields/select/radio-mode.ts +25 -0
  127. package/src/lib/fields/select/select.descriptor.ts +12 -0
  128. package/src/lib/fields/select/select.spec.ts +162 -0
  129. package/src/lib/fields/select/select.ts +210 -0
  130. package/src/lib/fields/slug/slug.descriptor.ts +19 -0
  131. package/src/lib/fields/slug/slug.ts +67 -0
  132. package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
  133. package/src/lib/fields/string-list/string-list.ts +222 -0
  134. package/src/lib/fields/text/text.descriptor.ts +12 -0
  135. package/src/lib/fields/text/text.ts +35 -0
  136. package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
  137. package/src/lib/fields/textarea/textarea.spec.ts +117 -0
  138. package/src/lib/fields/textarea/textarea.ts +126 -0
  139. package/src/lib/fields/time/time.descriptor.ts +12 -0
  140. package/src/lib/fields/time/time.ts +28 -0
  141. package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
  142. package/src/lib/fields/url/url.descriptor.ts +12 -0
  143. package/src/lib/fields/url/url.ts +53 -0
  144. package/src/lib/gas-admin-ui.spec.ts +21 -0
  145. package/src/lib/gas-admin-ui.ts +9 -0
  146. package/src/lib/layouts/descriptors.ts +9 -0
  147. package/src/lib/pages/dashboard/dashboard.ts +167 -0
  148. package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
  149. package/src/lib/pages/detail-view/detail-view.ts +570 -0
  150. package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
  151. package/src/lib/pages/form-view/form-model.ts +88 -0
  152. package/src/lib/pages/form-view/form-schema.ts +255 -0
  153. package/src/lib/pages/form-view/form-view.ts +1622 -0
  154. package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
  155. package/src/lib/pages/list-view/list-view.ts +882 -0
  156. package/src/lib/pages/login/login.spec.ts +109 -0
  157. package/src/lib/pages/login/login.ts +227 -0
  158. package/src/lib/pages/resource-page/resource-page.ts +234 -0
  159. package/src/lib/shared/components/badge/badge.ts +35 -0
  160. package/src/lib/shared/components/button/button.ts +65 -0
  161. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
  162. package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
  163. package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
  164. package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
  165. package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
  166. package/src/lib/shared/components/drawer/drawer.ts +172 -0
  167. package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
  168. package/src/lib/shared/components/modal/modal.spec.ts +149 -0
  169. package/src/lib/shared/components/modal/modal.ts +145 -0
  170. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
  171. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
  172. package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
  173. package/src/lib/shared/components/toast/toast.ts +58 -0
  174. package/src/lib/shared/utils/col-span.ts +25 -0
  175. package/src/lib/shared/utils/condition-evaluator.ts +53 -0
  176. package/src/lib/shared/utils/depends-on.ts +48 -0
  177. package/src/lib/shared/utils/dialog-focus.ts +28 -0
  178. package/src/lib/shared/utils/field-path.ts +49 -0
  179. package/src/lib/shared/utils/gap-sorting.ts +144 -0
  180. package/src/lib/shared/utils/seed.spec.ts +64 -0
  181. package/src/lib/shared/utils/seed.ts +149 -0
  182. package/src/lib/shared/utils/slugify.ts +13 -0
  183. package/src/lib/shared/utils/validators.ts +41 -0
  184. package/src/lib/shell/layout/layout.ts +63 -0
  185. package/src/lib/shell/shell.routes.spec.ts +59 -0
  186. package/src/lib/shell/shell.routes.ts +122 -0
  187. package/src/lib/shell/sidebar/sidebar.ts +139 -0
  188. package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
  189. package/src/lib/views/calendar-view/calendar-model.ts +426 -0
  190. package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
  191. package/src/lib/views/calendar-view/calendar-view.ts +568 -0
  192. package/src/lib/views/composer/composer-host.spec.ts +301 -0
  193. package/src/lib/views/composer/composer-host.ts +248 -0
  194. package/src/lib/views/composer/composer-imports.spec.ts +54 -0
  195. package/src/lib/views/composer/view-context.spec.ts +117 -0
  196. package/src/lib/views/composer/view-context.ts +109 -0
  197. package/src/lib/views/composer/view-slot.spec.ts +130 -0
  198. package/src/lib/views/composer/view-slot.ts +178 -0
  199. package/src/lib/views/descriptors.ts +46 -0
  200. package/src/lib/views/kanban-view/kanban-model.ts +173 -0
  201. package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
  202. package/src/lib/views/kanban-view/kanban-view.ts +327 -0
  203. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
  204. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
  205. package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
  206. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
  207. package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
  208. package/src/lib/widgets/chart-line/chart-line.ts +219 -0
  209. package/src/lib/widgets/descriptors.ts +29 -0
  210. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
  211. package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
  212. package/src/lib/widgets/metric-card/metric-card.ts +48 -0
  213. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
  214. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
  215. package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
  216. package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
  217. package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
  218. package/src/lib/widgets/record-list/record-list.ts +115 -0
  219. package/src/lib/widgets/series.spec.ts +42 -0
  220. package/src/lib/widgets/series.ts +56 -0
  221. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
  222. package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
  223. package/src/lib/widgets/status-progress/status-progress.ts +104 -0
  224. package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
  225. package/src/lib/widgets/widget-host/widget-host.ts +99 -0
  226. package/src/public-api.ts +104 -0
  227. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  228. package/tsconfig.lib.json +13 -0
  229. package/tsconfig.lib.prod.json +11 -0
  230. package/tsconfig.spec.json +10 -0
  231. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
  232. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
  233. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
  234. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
  235. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
  236. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
  237. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
  238. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
  239. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
  240. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
  241. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
  242. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
  243. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
  244. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
  245. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
  246. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
  247. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
  248. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
  249. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
  250. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
  251. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
  252. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
  253. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
  254. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
  255. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
  256. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
  257. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
  258. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
  259. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
  260. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
  261. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
  262. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
  263. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
  264. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
  265. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
  266. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
  267. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
  268. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
  269. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
  270. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
  271. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
  272. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
  273. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
  274. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
  275. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
  276. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
  277. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
  278. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
  279. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
  280. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
  281. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
  282. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
  283. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
  284. package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
  285. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
  286. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
  287. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
  288. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
  289. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
  290. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
  291. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
  292. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
  293. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
  294. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
  295. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
  296. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
  297. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
  298. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
  299. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
  300. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
  301. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
  302. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
  303. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
  304. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
  305. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
  306. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
  307. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
  308. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
  309. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
  310. package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
  311. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
  312. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
  313. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
  314. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
  315. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
  316. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
  317. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
  318. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
  319. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
  320. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
  321. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
  322. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
  323. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
  324. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
  325. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
  326. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
  327. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
  328. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
  329. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
  330. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
  331. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
  332. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
  333. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
  334. package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
  335. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
  336. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
  337. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
  338. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
  339. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
  340. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
  341. package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
  342. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
  343. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
  344. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
  345. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
  346. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
  347. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
  348. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
  349. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
  350. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
  351. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
  352. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
  353. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
  354. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
  355. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
  356. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
  357. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
  358. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
  359. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
  360. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
  361. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
  362. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
  363. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
  364. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
  365. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
  366. package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
  367. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
  368. package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
@@ -0,0 +1,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
+ }
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Re-exporta `FirebaseAuthAdapter` desde `tokens.ts` (única fuente de verdad).
3
+ * Mantiene este archivo como punto de importación estable para consumidores
4
+ * que ya referencian esta ruta, sin duplicar la definición de la interfaz.
5
+ */
6
+ export type { FirebaseAuthAdapter } from '../tokens';
@@ -0,0 +1,88 @@
1
+ import { provideHttpClient } from '@angular/common/http';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { ActivatedRouteSnapshot, Router, UrlTree, provideRouter } from '@angular/router';
4
+
5
+ import { API_URL } from '../tokens';
6
+ import { SESSION_TOKEN_KEY, SESSION_USER_KEY } from '../services/auth.service';
7
+ import { authGuard, roleGuard } from './auth.guard';
8
+
9
+ /**
10
+ * R2.10 — guards de sesión (auth.md §5.3).
11
+ *
12
+ * No son una barrera (el back re-verifica desde R2.6): son el filtro de
13
+ * pantalla. Por eso aquí sólo se comprueba HACIA DÓNDE mandan, no qué
14
+ * protegen.
15
+ */
16
+ describe('authGuard / roleGuard — filtro de pantalla (R2.10)', () => {
17
+ afterEach(() => sessionStorage.clear());
18
+
19
+ /** `role === null` ⇒ sin sesión: no se siembra nada en el storage. */
20
+ function setup(role: string | null = 'admin') {
21
+ sessionStorage.clear();
22
+ if (role !== null) {
23
+ sessionStorage.setItem(SESSION_TOKEN_KEY, 'tok');
24
+ sessionStorage.setItem(SESSION_USER_KEY, JSON.stringify({ id: 'u_1', role }));
25
+ }
26
+
27
+ TestBed.configureTestingModule({
28
+ providers: [provideHttpClient(), provideRouter([]), { provide: API_URL, useValue: '/api' }],
29
+ });
30
+ return { authGuard, roleGuard, router: TestBed.inject(Router) };
31
+ }
32
+
33
+ /** `route` mínimo con un solo parámetro de ruta. */
34
+ function routeWithId(id: string | null): ActivatedRouteSnapshot {
35
+ return {
36
+ paramMap: {
37
+ get: (key: string) => (key === 'id' ? id : null),
38
+ },
39
+ } as unknown as ActivatedRouteSnapshot;
40
+ }
41
+
42
+ const isUrlTree = (r: unknown): r is UrlTree => r instanceof UrlTree;
43
+
44
+ /**
45
+ * El router invoca un guard DENTRO de un contexto de inyección; una llamada
46
+ * directa desde el spec no lo tiene, y `inject(AuthService)` revienta.
47
+ * `runInInjectionContext` reproducía el contexto real del router.
48
+ */
49
+ function run(guard: typeof authGuard, route: ActivatedRouteSnapshot): boolean | UrlTree {
50
+ const result = TestBed.runInInjectionContext(() => guard(route, {} as never));
51
+ // Los dos guards son síncronos: `MaybeAsync` sólo es ruido de tipos acá.
52
+ return result as boolean | UrlTree;
53
+ }
54
+
55
+ it('con sesión deja pasar', () => {
56
+ const { authGuard: guard } = setup();
57
+ expect(run(guard, {} as ActivatedRouteSnapshot)).toBe(true);
58
+ });
59
+
60
+ it('sin sesión manda a /login', () => {
61
+ const { authGuard: guard, router } = setup(null);
62
+ const result = run(guard, {} as ActivatedRouteSnapshot);
63
+ expect(isUrlTree(result)).toBe(true);
64
+ expect(router.serializeUrl(result as UrlTree)).toBe('/login');
65
+ });
66
+
67
+ it('sin sesión, un `:id` también manda a /login (redundancia barata)', () => {
68
+ const { roleGuard: guard, router } = setup(null);
69
+ const result = run(guard, routeWithId('orders'));
70
+ expect(isUrlTree(result)).toBe(true);
71
+ expect(router.serializeUrl(result as UrlTree)).toBe('/login');
72
+ });
73
+
74
+ it('un `:id` desconocido deja que responda el back (no inventamos política)', () => {
75
+ const { roleGuard: guard } = setup();
76
+ expect(run(guard, routeWithId('no-existe'))).toBe(true);
77
+ });
78
+
79
+ it('una ruta sin `:id` no decide nada', () => {
80
+ const { roleGuard: guard } = setup();
81
+ expect(run(guard, routeWithId(null))).toBe(true);
82
+ });
83
+
84
+ it('un recurso sin `access` declarado se ve con sesión', () => {
85
+ const { roleGuard: guard } = setup('staff');
86
+ expect(run(guard, routeWithId('dashboard'))).toBe(true);
87
+ });
88
+ });
@@ -0,0 +1,48 @@
1
+ import { inject } from '@angular/core';
2
+ import type { CanActivateFn } from '@angular/router';
3
+ import { Router } from '@angular/router';
4
+
5
+ import { getSchema } from '../registry/schema-catalog';
6
+ import { AuthService, LOGIN_PATH } from '../services/auth.service';
7
+
8
+ /**
9
+ * Guards de sesión (auth.md §5.3).
10
+ *
11
+ * Dos puertas y ninguna es una barrera de seguridad: el back re-verifica
12
+ * `accessLevel` en **cada** petición desde R2.6. Éstas sólo evitan pintar
13
+ * una pantalla que no le toca al usuario — enseñarle un formulario que va
14
+ * a recibir 403 es mala UX, no un agujero.
15
+ *
16
+ * `authGuard` va en la ruta madre (todo el shell); `roleGuard` en cada
17
+ * `:id`, que es donde aparece el recurso que puede tener `access`.
18
+ */
19
+
20
+ /** Sin sesión → login. Corre ANTES que cualquier ruta del shell. */
21
+ export const authGuard: CanActivateFn = () => {
22
+ const auth = inject(AuthService);
23
+ if (auth.isAuthenticated()) return true;
24
+ return inject(Router).createUrlTree([LOGIN_PATH]);
25
+ };
26
+
27
+ /**
28
+ * El recurso declara `access` y esta sesión no lo cumple → se cancela la
29
+ * navegación. No hay pantalla de 403 (tampoco la pide §5.3) y el menú ya
30
+ * oculta la entrada, así que queda sólo el caso de alguien tecleando la URL.
31
+ */
32
+ export const roleGuard: CanActivateFn = (route) => {
33
+ const auth = inject(AuthService);
34
+ const router = inject(Router);
35
+
36
+ // Redundante con `authGuard` (la madre ya filtra) pero barato: un hijo
37
+ // nunca debe dejar una sesión muerta navegando sola.
38
+ if (!auth.isAuthenticated()) return router.createUrlTree([LOGIN_PATH]);
39
+
40
+ const id = route.data?.['resourceId'] ?? route.paramMap.get('id');
41
+ // Ruta sin recurso declarado, o id que no existe en el registry → que
42
+ // responda el back (404). No inventar una política sobre datos ajenos.
43
+ if (id === null) return true;
44
+ const schema = getSchema(String(id));
45
+ if (schema === undefined) return true;
46
+
47
+ return auth.canAccess(schema.access);
48
+ };
@@ -0,0 +1,16 @@
1
+ import { CanDeactivateFn } from '@angular/router';
2
+
3
+ /** Componente que expone si se puede salir (base.md §13.9: cambios sin guardar). */
4
+ export interface CanLeaveWhenDirty {
5
+ canDeactivate(): boolean | Promise<boolean>;
6
+ }
7
+
8
+ /**
9
+ * Avisa antes de salir con cambios sin guardar. El componente decide:
10
+ * `canDeactivate()` devuelve `true` para salir limpio o una `Promise` que
11
+ * resuelve según la respuesta del usuario (vía `NotificationService.confirm`).
12
+ */
13
+ export const unsavedChangesGuard: CanDeactivateFn<unknown> = (component) => {
14
+ const candidate = component as Partial<CanLeaveWhenDirty> | null | undefined;
15
+ return candidate?.canDeactivate?.() ?? true;
16
+ };