@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,56 @@
1
+ /**
2
+ * Resuelve una plantilla de texto reemplazando marcadores `{key}` o `{nested.key}`
3
+ * con los valores presentes en el objeto `data` o en el contexto adicional `extraContext`.
4
+ *
5
+ * Si un valor es `null` o `undefined`, se reemplaza por una cadena vacía.
6
+ * No arroja excepciones ante claves no encontradas ni ante objetos nulos.
7
+ */
8
+ export function interpolateTemplate(
9
+ template: string,
10
+ data: Record<string, unknown> = {},
11
+ extraContext: Record<string, unknown> = {},
12
+ ): string {
13
+ if (!template) return '';
14
+
15
+ return template.replace(/\{([a-zA-Z0-9_.]+)\}/g, (match, key: string) => {
16
+ const val = getDeepValue(data, key) ?? getDeepValue(extraContext, key);
17
+ if (val === null || val === undefined) {
18
+ return '';
19
+ }
20
+ return String(val);
21
+ });
22
+ }
23
+
24
+ function getDeepValue(obj: Record<string, unknown>, path: string): unknown {
25
+ if (!obj || typeof obj !== 'object') return undefined;
26
+ const parts = path.split('.');
27
+ let current: unknown = obj;
28
+ for (const part of parts) {
29
+ if (current === null || current === undefined || typeof current !== 'object') {
30
+ return undefined;
31
+ }
32
+ current = (current as Record<string, unknown>)[part];
33
+ }
34
+ return current;
35
+ }
36
+
37
+ export interface ParsedRoute {
38
+ path: string;
39
+ queryParams: Record<string, string>;
40
+ }
41
+
42
+ /**
43
+ * Descompone una ruta relativa o absoluta en path y queryParams para routerLink.
44
+ */
45
+ export function parseNavigationUrl(url: string): ParsedRoute {
46
+ if (!url) return { path: '', queryParams: {} };
47
+ const [path, queryString] = url.split('?');
48
+ const queryParams: Record<string, string> = {};
49
+ if (queryString) {
50
+ const params = new URLSearchParams(queryString);
51
+ params.forEach((value, key) => {
52
+ queryParams[key] = value;
53
+ });
54
+ }
55
+ return { path: path || '', queryParams };
56
+ }
@@ -0,0 +1,207 @@
1
+ import { setSchemaCatalog } from '../registry/schema-catalog';
2
+ import { testCatalog } from '../testing/test-catalog';
3
+
4
+ setSchemaCatalog(testCatalog);
5
+
6
+ import { Component } from '@angular/core';
7
+ import { TestBed } from '@angular/core/testing';
8
+ import { provideRouter, Router } from '@angular/router';
9
+ import { describe, expect, it, vi } from 'vitest';
10
+
11
+ import { roleGuard } from '../guards/auth.guard';
12
+ import type { NavigationTarget, SelectionBinding } from '../models/schema.model';
13
+ import { AuthService } from '../services/auth.service';
14
+ import {
15
+ buildSegments,
16
+ targetRoutePattern,
17
+ ViewNavigationService,
18
+ } from './view-navigation.service';
19
+
20
+ @Component({ standalone: true, template: 'lista' })
21
+ class ListaStub {}
22
+
23
+ @Component({ standalone: true, template: 'detalle' })
24
+ class DetalleStub {}
25
+
26
+ /** Target declarado sobre el schema real `orders` (registry front). */
27
+ const target: NavigationTarget = { resource: 'orders', view: 'detail', params: ['key'] };
28
+
29
+ function rutaBinding(mode: 'route'): SelectionBinding {
30
+ return { on: 'open', mode, target };
31
+ }
32
+
33
+ describe('buildSegments (key-only, sin concat de strings)', () => {
34
+ it('sustituye :key por la clave COMPLETA sin partirla (clave A/B)', () => {
35
+ expect(buildSegments(['orders', ':key'], 'A/B')).toEqual(['orders', 'A/B']);
36
+ });
37
+
38
+ it('sustituye params en posición media y mantiene literales', () => {
39
+ expect(buildSegments(['orders', ':key', 'edit'], '7')).toEqual(['orders', '7', 'edit']);
40
+ });
41
+
42
+ it('param distinto de :key ⇒ falla ruido (sin fuente declarada)', () => {
43
+ expect(() => buildSegments(['orders', ':otro'], 'x')).toThrow('sin fuente declarada');
44
+ });
45
+ });
46
+
47
+ describe('targetRoutePattern (misma fuente que el validador)', () => {
48
+ it('resuelve el patrón de la ruta destino desde la registry', () => {
49
+ expect(targetRoutePattern({ resource: 'orders', view: 'detail', params: ['key'] })).toEqual([
50
+ 'orders',
51
+ ':key',
52
+ ]);
53
+ });
54
+
55
+ it('recurso o vista destino inexistente ⇒ undefined (nada navega)', () => {
56
+ expect(targetRoutePattern({ resource: 'nope', view: 'detail' })).toBeUndefined();
57
+ expect(targetRoutePattern({ resource: 'orders', view: 'nope' })).toBeUndefined();
58
+ });
59
+ });
60
+
61
+ describe('ViewNavigationService.dispatch', () => {
62
+ function servicio(auth = { isAuthenticated: () => true, canAccess: () => true }) {
63
+ TestBed.configureTestingModule({
64
+ providers: [
65
+ provideRouter([
66
+ {
67
+ path: 'orders',
68
+ component: ListaStub,
69
+ data: { resourceId: 'orders' },
70
+ canActivate: [roleGuard],
71
+ },
72
+ {
73
+ path: 'orders/:key',
74
+ component: DetalleStub,
75
+ data: { resourceId: 'orders' },
76
+ canActivate: [roleGuard],
77
+ },
78
+ ]),
79
+ { provide: AuthService, useValue: auth },
80
+ ],
81
+ });
82
+ return {
83
+ nav: TestBed.inject(ViewNavigationService),
84
+ router: TestBed.inject(Router),
85
+ };
86
+ }
87
+
88
+ it('target declarado ⇒ navega con la clave codificada y el guard corre', async () => {
89
+ const { nav, router } = servicio();
90
+ const ok = await nav.dispatch(rutaBinding('route'), {
91
+ resource: 'orders',
92
+ key: 'A/B',
93
+ action: 'open',
94
+ });
95
+ expect(ok).toBe(true);
96
+ // El router serializa :key ⇒ '/' de la clave queda codificado, sin partir
97
+ expect(router.url).toBe('/orders/A%2FB');
98
+ });
99
+
100
+ it('patrón sin params navega a los segmentos literales', async () => {
101
+ const { nav, router } = servicio();
102
+ const ok = await nav.dispatch(
103
+ { on: 'open', mode: 'route', target: { resource: 'orders', view: 'create' } },
104
+ { resource: 'orders', key: 'ignorada', action: 'open' },
105
+ );
106
+ expect(ok).toBe(true);
107
+ expect(router.url).toBe('/orders/new');
108
+ });
109
+
110
+ it('acción que no es la declarada ⇒ sin navegación (scope del target)', async () => {
111
+ const { nav, router } = servicio();
112
+ const espia = vi.spyOn(router, 'navigate');
113
+ await nav.dispatch(rutaBinding('route'), { resource: 'orders', key: '1', action: 'select' });
114
+ expect(espia).not.toHaveBeenCalled();
115
+ expect(router.url).toBe('/');
116
+ });
117
+
118
+ it('sin binding declarado ⇒ inerte aunque el evento pida mutar', async () => {
119
+ const { nav, router } = servicio();
120
+ const espia = vi.spyOn(router, 'navigate');
121
+ await nav.dispatch(undefined, { resource: 'orders', key: '1', action: 'move' });
122
+ expect(espia).not.toHaveBeenCalled();
123
+ });
124
+
125
+ it('mode inline/none ⇒ sin navegación (comportamiento local del leaf)', async () => {
126
+ const { nav, router } = servicio();
127
+ const espia = vi.spyOn(router, 'navigate');
128
+ await nav.dispatch(
129
+ { on: 'open', mode: 'inline' },
130
+ { resource: 'orders', key: '1', action: 'open' },
131
+ );
132
+ await nav.dispatch(
133
+ { on: 'open', mode: 'none' },
134
+ { resource: 'orders', key: '1', action: 'open' },
135
+ );
136
+ expect(espia).not.toHaveBeenCalled();
137
+ });
138
+
139
+ it('target sin recurso/vista declarado en el registry ⇒ falla ruido, no navega', async () => {
140
+ const { nav, router } = servicio();
141
+ const espia = vi.spyOn(router, 'navigate');
142
+ await expect(
143
+ nav.dispatch(
144
+ { on: 'open', mode: 'route', target: { resource: 'no-existe', view: 'x' } },
145
+ { resource: 'no-existe', key: '1', action: 'open' },
146
+ ),
147
+ ).rejects.toThrow('no existe');
148
+ expect(espia).not.toHaveBeenCalled();
149
+ });
150
+
151
+ it('guard bloquea (sin permiso) ⇒ URL intacta y sin payload del registro', async () => {
152
+ const { nav, router } = servicio({ isAuthenticated: () => true, canAccess: () => false });
153
+ const espia = vi.spyOn(router, 'navigate');
154
+ const ok = await nav.dispatch(rutaBinding('route'), {
155
+ resource: 'orders',
156
+ key: 'A/B',
157
+ action: 'open',
158
+ });
159
+ expect(ok).toBe(false);
160
+ expect(espia).toHaveBeenCalledWith(['/', 'orders', 'A/B']);
161
+ expect(router.url).toBe('/'); // ningún dato del registro llegó a la URL
162
+ // guard bloqueado ⇒ el origen NO queda registrado (nada que volver a ver)
163
+ await nav.back();
164
+ expect(router.url).toBe('/');
165
+ });
166
+ });
167
+
168
+ describe('ViewNavigationService.back (D4)', () => {
169
+ function servicio() {
170
+ TestBed.configureTestingModule({
171
+ providers: [
172
+ provideRouter([
173
+ { path: 'orders', component: ListaStub, data: { resourceId: 'orders' } },
174
+ { path: 'orders/:key', component: DetalleStub, data: { resourceId: 'orders' } },
175
+ ]),
176
+ { provide: AuthService, useValue: { isAuthenticated: () => true, canAccess: () => true } },
177
+ ],
178
+ });
179
+ return {
180
+ nav: TestBed.inject(ViewNavigationService),
181
+ router: TestBed.inject(Router),
182
+ };
183
+ }
184
+
185
+ it('con dispatch previo ⇒ vuelve al origen registrado', async () => {
186
+ const { nav, router } = servicio();
187
+ await nav.dispatch(rutaBinding('route'), { resource: 'orders', key: '9', action: 'open' });
188
+ expect(router.url).toBe('/orders/9');
189
+ await nav.back();
190
+ expect(router.url).toBe('/');
191
+ });
192
+
193
+ it('returnPath declarado ⇒ navega ahí (gana sobre el origen)', async () => {
194
+ const { nav, router } = servicio();
195
+ await nav.dispatch(rutaBinding('route'), { resource: 'orders', key: '9', action: 'open' });
196
+ await nav.back('orders');
197
+ expect(router.url).toBe('/orders');
198
+ });
199
+
200
+ it('deep link sin dispatch ⇒ cae a la raíz del recurso de la ruta', async () => {
201
+ const { nav, router } = servicio();
202
+ await router.navigate(['/orders/XYZ']); // entrada directa: no hay origen en memoria
203
+ expect(router.url).toBe('/orders/XYZ');
204
+ await nav.back();
205
+ expect(router.url).toBe('/orders'); // raíz declarada del resourceId de la ruta
206
+ });
207
+ });
@@ -0,0 +1,132 @@
1
+ import { Injectable, inject } from '@angular/core';
2
+ import { Router, type ActivatedRouteSnapshot } from '@angular/router';
3
+
4
+ import type { NavigationTarget, SelectionBinding } from '../models/schema.model';
5
+ import { getSchema } from '../registry/schema-catalog';
6
+ import type { SelectionEvent } from '../../views/composer/view-context';
7
+
8
+ /**
9
+ * R9.9 · D4 — Servicio de navegación declarativa.
10
+ *
11
+ * Un leaf (hoja) sólo NAVEGA cuando hay `select` declarado; el motor vive
12
+ * aquí, no en cada hoja. El flujo completo es:
13
+ *
14
+ * `ctx.emit/leaf → dispatch → buildSegments(pattern, {key}) →
15
+ * router.navigate(matriz de comandos) → roleGuard`
16
+ *
17
+ * Invariantes:
18
+ *
19
+ * - **key-only**: la ÚNICA identidad que viaja es `event.key`. Los comandos
20
+ * son una matriz de segmentos (nunca concat de strings) ⇒ el Router
21
+ * codifica la clave (una clave `A/B` queda `A%2FB` en la URL) y ningún
22
+ * dato del registro llega a la URL.
23
+ * - **Fail closed**: sin binding, con acción distinta a la declarada o con
24
+ * `mode` distinto de `route`, NO hay navegación — simplemente no ocurre.
25
+ * - **Fail loud**: un target que el registry no declara lanza ruido antes de
26
+ * navegar (el validador pre-render ya lo garantiza; esto es defensa en
27
+ * profundidad).
28
+ * - **Guards reales**: `router.navigate` ⇒ `roleGuard` corre igual que en
29
+ * cualquier navegación declarada. Si el guard bloquea, no se registra
30
+ * origen (no hay «atrás» que una sesión sin permiso deba volver a ver).
31
+ */
32
+
33
+ /**
34
+ * Patrón de segmentos de la ruta destino (`['orders', ':key']`). Usa la
35
+ * MISMA fuente que el validador (`getSchema`) para que lo validado en build
36
+ * sea exactamente lo navegado en runtime. Inexistente ⇒ `undefined` (la
37
+ * defensa en profundidad: el validador pre-render garantiza que esto no
38
+ * ocurre en schemas válidos).
39
+ */
40
+ export function targetRoutePattern(target: NavigationTarget): string[] | undefined {
41
+ const destino = getSchema(target.resource);
42
+ const vista = destino?.uiViews?.[target.view];
43
+ if (!destino || !vista) return undefined;
44
+ const relativa = vista.path ?? '';
45
+ return [destino.id, ...(relativa === '' ? [] : relativa.split('/'))];
46
+ }
47
+
48
+ /**
49
+ * Segmentos de la ruta destino con la clave sustituida. La clave es SIEMPRE
50
+ * un segmento completo (no se parte en `/`); un param distinto de `:key` no
51
+ * tiene fuente declarada ⇒ falla ruido, no navega a medias.
52
+ */
53
+ export function buildSegments(pattern: readonly string[], key: string): string[] {
54
+ const commands: string[] = [];
55
+ for (const segmento of pattern) {
56
+ if (segmento.startsWith(':')) {
57
+ const nombre = segmento.slice(1);
58
+ if (nombre !== 'key') {
59
+ throw new Error(`Destino ':${nombre}' sin fuente declarada en SelectionEvent`);
60
+ }
61
+ commands.push(key);
62
+ } else {
63
+ commands.push(segmento);
64
+ }
65
+ }
66
+ return commands;
67
+ }
68
+
69
+ @Injectable({ providedIn: 'root' })
70
+ export class ViewNavigationService {
71
+ private readonly router = inject(Router);
72
+
73
+ /** URL desde la que se navegó la última selección válida (origen del «atrás»). */
74
+ private origen: string | undefined;
75
+
76
+ /**
77
+ * Transición declarada de un leaf. Inerte si no hay binding, si la acción
78
+ * no es la declarada o si el modo no es `route`; falla ruido si el target
79
+ * no existe en el registry.
80
+ *
81
+ * @returns `true` si el Router completó la navegación (guards aceptados).
82
+ */
83
+ async dispatch(binding: SelectionBinding | undefined, event: SelectionEvent): Promise<boolean> {
84
+ if (binding === undefined) return false; // sin declaración ⇒ inerte
85
+ if (binding.on !== event.action) return false; // fuera del scope declarado
86
+ if (binding.mode !== 'route') return false; // comportamiento local del leaf
87
+
88
+ const pattern = targetRoutePattern(binding.target);
89
+ if (pattern === undefined) {
90
+ throw new Error(
91
+ `Destino '${binding.target.resource}/${binding.target.view}' no existe en el registry`,
92
+ );
93
+ }
94
+
95
+ const urlOrigen = this.router.url;
96
+ const ok = await this.router.navigate(['/', ...buildSegments(pattern, event.key)]);
97
+ if (ok) this.origen = urlOrigen;
98
+ return ok;
99
+ }
100
+
101
+ /**
102
+ * Forma de volver (D4). Orden: `returnPath` declarado por el leaf ⇒
103
+ * origen registrado por `dispatch` ⇒ raíz del recurso declarado en la ruta
104
+ * actual (entrada directa a la URL: no hay origen en memoria). Fallback
105
+ * final: la raíz de la aplicación.
106
+ */
107
+ async back(returnPath?: string): Promise<boolean> {
108
+ if (returnPath !== undefined) return this.router.navigate([returnPath]);
109
+
110
+ if (this.origen !== undefined) {
111
+ const destino = this.origen;
112
+ this.origen = undefined;
113
+ return this.router.navigateByUrl(destino);
114
+ }
115
+
116
+ const recurso = this.recursoActual();
117
+ return recurso === undefined
118
+ ? this.router.navigate(['/'])
119
+ : this.router.navigate(['/', recurso]);
120
+ }
121
+
122
+ /** Primer `resourceId` declarado en el árbol de rutas activo. */
123
+ private recursoActual(): string | undefined {
124
+ let nodo: ActivatedRouteSnapshot | null = this.router.routerState.snapshot.root;
125
+ while (nodo !== null) {
126
+ const id = nodo.data?.['resourceId'];
127
+ if (id !== undefined) return String(id);
128
+ nodo = nodo.firstChild;
129
+ }
130
+ return undefined;
131
+ }
132
+ }
@@ -0,0 +1,11 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `boolean` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'boolean',
7
+ since: '1.0',
8
+ load: () => import('./boolean').then((module) => module.FieldBoolean),
9
+ seed: 'boolean',
10
+ surfaces: ['form', 'display'],
11
+ };
@@ -0,0 +1,40 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { BooleanField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /**
9
+ * Interruptor booleano (base.md §5.1) con `trueLabel`/`falseLabel`.
10
+ * Checkbox nativo + `formField` (se estiliza como switch con `peer-checked`).
11
+ */
12
+ @Component({
13
+ selector: 'app-field-boolean',
14
+ imports: [FieldAria, FormField],
15
+ template: `
16
+ <label class="inline-flex cursor-pointer items-center gap-2">
17
+ <input type="checkbox" class="peer sr-only" [id]="state().name()" [formField]="node()" />
18
+ <span
19
+ class="relative h-5 w-9 shrink-0 rounded-full bg-neutral/30 transition-colors peer-checked:bg-primary after:absolute after:top-0.5 after:left-0.5 after:h-4 after:w-4 after:rounded-full after:bg-white after:transition-transform after:content-[''] peer-checked:after:translate-x-4"
20
+ ></span>
21
+ <span class="text-sm">{{ currentLabel() }}</span>
22
+ </label>
23
+ `,
24
+ })
25
+ export class FieldBoolean {
26
+ readonly field = input.required<BooleanField>();
27
+ readonly tree = input.required<RootTree>();
28
+
29
+ protected readonly node = computed(() => childTree<boolean>(this.tree(), this.field().key));
30
+ protected readonly state = computed(() => this.node()());
31
+
32
+ protected readonly currentLabel = computed(() => {
33
+ const on = this.state().value() === true;
34
+ const field = this.field();
35
+ if (on) {
36
+ return field.trueLabel ?? 'Sí';
37
+ }
38
+ return field.falseLabel ?? 'No';
39
+ });
40
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `color` (selector de color). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'color',
7
+ since: '1.0',
8
+ load: () => import('./color').then((module) => module.FieldColor),
9
+ // Nace como cadena (hexadecimal #RRGGBB).
10
+ seed: 'string',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,34 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { ColorField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /**
9
+ * Selector de color (`<input type="color">`, base.md §5.1 `color`).
10
+ *
11
+ * Devuelve un string hexadecimal `#RRGGBB`. El navegador muestra su propia
12
+ * paleta de colores; no hay validación de formato extra porque el input
13
+ * `type="color"` ya garantiza el formato válido.
14
+ */
15
+ @Component({
16
+ selector: 'app-field-color',
17
+ imports: [FieldAria, FormField],
18
+ template: `
19
+ <input
20
+ type="color"
21
+ class="field-input h-10 w-full p-0 border-0 bg-transparent focus-visible:outline-0 cursor-pointer"
22
+ [id]="state().name()"
23
+ [formField]="node()"
24
+ [title]="field().label"
25
+ />
26
+ `,
27
+ })
28
+ export class FieldColor {
29
+ readonly field = input.required<ColorField>();
30
+ readonly tree = input.required<RootTree>();
31
+
32
+ protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
33
+ protected readonly state = computed(() => this.node()());
34
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `currency` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'currency',
7
+ since: '1.0',
8
+ load: () => import('./currency').then((module) => module.FieldCurrency),
9
+ // Nace como `null`: no puede ser `0`, que sería un valor ya cargado.
10
+ seed: 'number',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,67 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { CurrencyField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /**
9
+ * Número con prefijo de moneda (base.md §5.1). El valor sigue siendo número
10
+ * puro; el prefijo sale del campo `currencyFrom` (código ISO, ej. `PEN`)
11
+ * traducido a símbolo con Intl.
12
+ */
13
+ @Component({
14
+ selector: 'app-field-currency',
15
+ imports: [FieldAria, FormField],
16
+ template: `
17
+ <div class="flex items-center gap-2">
18
+ @if (symbol(); as symbol) {
19
+ <span class="shrink-0 text-sm text-neutral" data-testid="currency-symbol">{{
20
+ symbol
21
+ }}</span>
22
+ }
23
+ <input
24
+ type="number"
25
+ step="any"
26
+ class="field-input"
27
+ [id]="state().name()"
28
+ [formField]="node()"
29
+ [placeholder]="field().placeholder ?? ''"
30
+ />
31
+ </div>
32
+ `,
33
+ })
34
+ export class FieldCurrency {
35
+ readonly field = input.required<CurrencyField>();
36
+ readonly tree = input.required<RootTree>();
37
+
38
+ protected readonly node = computed(() => childTree<number>(this.tree(), this.field().key));
39
+ protected readonly state = computed(() => this.node()());
40
+
41
+ /** Código ISO de la moneda desde el campo hermano (`currencyFrom`). */
42
+ private readonly currencyCode = computed(() => {
43
+ const from = this.field().currencyFrom;
44
+ if (!from) {
45
+ return null;
46
+ }
47
+ const value = childTree<unknown>(this.tree(), from)().value();
48
+ return typeof value === 'string' && value !== '' ? value : null;
49
+ });
50
+
51
+ /** Símbolo localizado (`S/` para PEN); el código crudo si Intl lo rechaza. */
52
+ protected readonly symbol = computed(() => {
53
+ const code = this.currencyCode();
54
+ if (code === null) {
55
+ return null;
56
+ }
57
+ try {
58
+ const parts = new Intl.NumberFormat('es-PE', {
59
+ style: 'currency',
60
+ currency: code,
61
+ }).formatToParts(0);
62
+ return parts.find((part) => part.type === 'currency')?.value ?? code;
63
+ } catch {
64
+ return code;
65
+ }
66
+ });
67
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `date` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'date',
7
+ since: '1.0',
8
+ load: () => import('./date').then((module) => module.FieldDate),
9
+ // Nace como cadena: los textos, fechas, urls, selects y relacionales.
10
+ seed: 'string',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,36 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { DateField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /**
9
+ * Selector de fecha ISO `YYYY-MM-DD` (base.md §5.1).
10
+ *
11
+ * `min`/`max` del schema se validan con reglas propias en
12
+ * `pages/form-view/form-schema.ts`: `minDate`/`maxDate` de Signal Forms
13
+ * exigen valores `Date` y este modelo guarda strings. `FormField` solo setea
14
+ * los atributos nativos `min`/`max` cuando existen las reglas `min()`/`max()`
15
+ * (que aplican a números).
16
+ */
17
+ @Component({
18
+ selector: 'app-field-date',
19
+ imports: [FieldAria, FormField],
20
+ template: `
21
+ <input
22
+ type="date"
23
+ class="field-input"
24
+ [id]="state().name()"
25
+ [formField]="node()"
26
+ [placeholder]="field().placeholder ?? ''"
27
+ />
28
+ `,
29
+ })
30
+ export class FieldDate {
31
+ readonly field = input.required<DateField>();
32
+ readonly tree = input.required<RootTree>();
33
+
34
+ protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
35
+ protected readonly state = computed(() => this.node()());
36
+ }