@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,222 @@
1
+ import { Component, computed, inject, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+ import {
4
+ CdkDrag,
5
+ CdkDragDrop,
6
+ CdkDragHandle,
7
+ CdkDropList,
8
+ moveItemInArray,
9
+ } from '@angular/cdk/drag-drop';
10
+
11
+ import { LucideArrowDown, LucideArrowUp, LucideGripVertical, LucideTrash2 } from '@lucide/angular';
12
+ import type { StringListField } from '../../core/models/schema.model';
13
+ import { NotificationService } from '../../core/services/notification.service';
14
+ import { Button } from '../../shared/components/button/button';
15
+ import { FieldAria } from '../field-aria';
16
+ import { childTree, stringItemTree, type RootTree } from '../field-node';
17
+
18
+ /**
19
+ * Lista de cadenas (base.md §9: `text`, `url` o `email` por ítem).
20
+ *
21
+ * Hoja: renderiza sus propios `<input [formField]>` (el nodo de cada ítem es
22
+ * `FieldTree<string>`) y no toca FieldHost. Los ítems escalares no pueden
23
+ * llevar `position`: si el schema la declara, el orden viaja como orden del
24
+ * arreglo y el backend renumera (api.md R3).
25
+ */
26
+ @Component({
27
+ selector: 'app-field-string-list',
28
+ imports: [
29
+ FieldAria,
30
+ Button,
31
+ CdkDrag,
32
+ CdkDragHandle,
33
+ CdkDropList,
34
+ FormField,
35
+ LucideArrowDown,
36
+ LucideArrowUp,
37
+ LucideGripVertical,
38
+ LucideTrash2,
39
+ ],
40
+ template: `
41
+ @if (items().length === 0) {
42
+ <p
43
+ class="rounded-lg border border-dashed border-neutral/30 px-4 py-6 text-center text-sm text-neutral"
44
+ >
45
+ {{ field().emptyText ?? 'Sin elementos.' }}
46
+ </p>
47
+ } @else {
48
+ <div
49
+ cdkDropList
50
+ class="space-y-2"
51
+ [cdkDropListData]="items()"
52
+ [cdkDropListDisabled]="!sortable()"
53
+ (cdkDropListDropped)="onDrop($event)"
54
+ >
55
+ @for (item of items(); track $index; let i = $index) {
56
+ <div class="flex items-center gap-2" cdkDrag [cdkDragDisabled]="!sortable()">
57
+ @if (sortable()) {
58
+ <button
59
+ type="button"
60
+ app-button
61
+ variant="ghost"
62
+ size="icon"
63
+ class="cursor-grab"
64
+ cdkDragHandle
65
+ [attr.aria-label]="'Reordenar elemento ' + (i + 1)"
66
+ title="Arrastrar para reordenar"
67
+ >
68
+ <svg lucideGripVertical size="16" />
69
+ </button>
70
+ }
71
+ <input
72
+ class="field-input min-w-0 grow"
73
+ [type]="inputType()"
74
+ [placeholder]="field().placeholder ?? placeholderFor()"
75
+ [attr.aria-label]="field().label + ', elemento ' + (i + 1)"
76
+ [id]="idPrefix() + '-' + i"
77
+ [formField]="itemNode(i)"
78
+ />
79
+ <span class="flex shrink-0 items-center gap-2 lg:gap-1">
80
+ <button
81
+ type="button"
82
+ app-button
83
+ variant="ghost"
84
+ size="icon"
85
+ [disabled]="i === 0"
86
+ [attr.aria-label]="'Subir elemento ' + (i + 1)"
87
+ (click)="move(i, -1)"
88
+ >
89
+ <svg lucideArrowUp size="16" />
90
+ </button>
91
+ <button
92
+ type="button"
93
+ app-button
94
+ variant="ghost"
95
+ size="icon"
96
+ [disabled]="i === items().length - 1"
97
+ [attr.aria-label]="'Bajar elemento ' + (i + 1)"
98
+ (click)="move(i, 1)"
99
+ >
100
+ <svg lucideArrowDown size="16" />
101
+ </button>
102
+ <button
103
+ type="button"
104
+ app-button
105
+ variant="ghost"
106
+ size="icon"
107
+ [disabled]="!canRemove()"
108
+ [attr.aria-label]="'Eliminar elemento ' + (i + 1)"
109
+ (click)="remove(i)"
110
+ >
111
+ <svg lucideTrash2 size="16" />
112
+ </button>
113
+ </span>
114
+ </div>
115
+ }
116
+ </div>
117
+ }
118
+
119
+ <div class="mt-3">
120
+ <button app-button variant="outline" type="button" [disabled]="!canAdd()" (click)="add()">
121
+ + {{ field().addLabel ?? 'Agregar elemento' }}
122
+ </button>
123
+ </div>
124
+ `,
125
+ })
126
+ export class FieldStringList {
127
+ readonly field = input.required<StringListField>();
128
+ readonly tree = input.required<RootTree>();
129
+
130
+ private readonly notifications = inject(NotificationService);
131
+
132
+ private readonly itemsNode = computed(() => childTree<string[]>(this.tree(), this.field().key));
133
+ private readonly state = computed(() => this.itemsNode()());
134
+ /** Prefijo estable del campo para los id de cada ítem (§8). */
135
+ protected readonly idPrefix = computed(() => this.state().name());
136
+ protected readonly items = computed(() => this.state().value() ?? []);
137
+
138
+ protected readonly sortable = computed(() => this.field().sortable ?? true);
139
+ protected readonly inputType = computed(() => this.field().itemType ?? 'text');
140
+
141
+ protected itemNode(index: number): ReturnType<typeof stringItemTree> {
142
+ return stringItemTree(this.itemsNode(), index);
143
+ }
144
+
145
+ protected canAdd(): boolean {
146
+ const max = this.field().max;
147
+ return max === undefined || this.items().length < max;
148
+ }
149
+
150
+ protected canRemove(): boolean {
151
+ return this.items().length > (this.field().min ?? 0);
152
+ }
153
+
154
+ protected add(): void {
155
+ if (!this.canAdd()) {
156
+ return;
157
+ }
158
+ this.updateItems((next) => {
159
+ next.push('');
160
+ });
161
+ }
162
+
163
+ protected move(index: number, direction: -1 | 1): void {
164
+ const target = index + direction;
165
+ if (target < 0 || target >= this.items().length) {
166
+ return;
167
+ }
168
+ this.updateItems((next) => {
169
+ moveItemInArray(next, index, target);
170
+ });
171
+ }
172
+
173
+ protected onDrop(event: CdkDragDrop<string[]>): void {
174
+ if (event.previousIndex === event.currentIndex) {
175
+ return;
176
+ }
177
+ this.updateItems((next) => {
178
+ moveItemInArray(next, event.previousIndex, event.currentIndex);
179
+ });
180
+ }
181
+
182
+ protected async remove(index: number): Promise<void> {
183
+ if (!this.canRemove()) {
184
+ return;
185
+ }
186
+ const value = this.items()[index] ?? '';
187
+ if (value.trim() !== '') {
188
+ const accepted = await this.notifications.confirm({
189
+ title: `Eliminar elemento ${index + 1}`,
190
+ message: 'Esta acción no se puede deshacer.',
191
+ confirmLabel: 'Eliminar',
192
+ cancelLabel: 'Cancelar',
193
+ });
194
+ if (!accepted) {
195
+ return;
196
+ }
197
+ }
198
+ this.updateItems((next) => {
199
+ next.splice(index, 1);
200
+ });
201
+ }
202
+
203
+ private updateItems(mutator: (next: string[]) => void): void {
204
+ this.state().controlValue.update((current) => {
205
+ const next = [...current];
206
+ mutator(next);
207
+ return next;
208
+ });
209
+ }
210
+
211
+ private placeholderFor(): string {
212
+ switch (this.field().itemType) {
213
+ case 'url':
214
+ return 'https://…';
215
+ case 'email':
216
+ return 'nombre@ejemplo.com';
217
+ default:
218
+ // §2: placeholder siempre ejemplo, nunca instrucción.
219
+ return 'Ej. Opción 1';
220
+ }
221
+ }
222
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `text` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'text',
7
+ since: '1.0',
8
+ load: () => import('./text').then((module) => module.FieldText),
9
+ // Nace como cadena: los textos, fechas, urls, selects y relacionales.
10
+ seed: 'string',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,35 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { TextField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /**
9
+ * Input de una línea (base.md §5.1 `text`).
10
+ *
11
+ * `readonly`, `maxLength` y `pattern` NO se bindean como atributos: el
12
+ * directive `FormField` los gestiona desde el estado del campo y el compilador
13
+ * lo exige (NG8022). La Fase 10 los traduce a reglas (`readonly()`,
14
+ * `maxLength()`, `pattern()`) en el schema del formulario.
15
+ */
16
+ @Component({
17
+ selector: 'app-field-text',
18
+ imports: [FieldAria, FormField],
19
+ template: `
20
+ <input
21
+ type="text"
22
+ class="field-input"
23
+ [id]="state().name()"
24
+ [formField]="node()"
25
+ [placeholder]="field().placeholder ?? ''"
26
+ />
27
+ `,
28
+ })
29
+ export class FieldText {
30
+ readonly field = input.required<TextField>();
31
+ readonly tree = input.required<RootTree>();
32
+
33
+ protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
34
+ protected readonly state = computed(() => this.node()());
35
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `textarea` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'textarea',
7
+ since: '1.0',
8
+ load: () => import('./textarea').then((module) => module.FieldTextarea),
9
+ // Nace como cadena: los textos, fechas, urls, selects y relacionales.
10
+ seed: 'string',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,117 @@
1
+ import { Component, EnvironmentInjector, signal } from '@angular/core';
2
+ import { TestBed } from '@angular/core/testing';
3
+ import { form } from '@angular/forms/signals';
4
+
5
+ import type { TextareaField } from '../../core/models/schema.model';
6
+ import { type RootTree } from '../field-node';
7
+ import { FieldTextarea } from './textarea';
8
+
9
+ /**
10
+ * El anfitrión NO lleva `[formField]`: `FieldTextarea` delega en el
11
+ * `<textarea>` nativo de su propio template y ahí es donde vive la directiva
12
+ * `FormField`. Pasarla en el host revienta con NG01914 («host must be a
13
+ * native form control»). El host sólo monta el campo y le da el `tree`
14
+ * (mismo criterio que `multiselect.spec.ts`, salvo que ese campo sí es
15
+ * control propio con `model()`).
16
+ */
17
+ @Component({
18
+ template: `<app-field-textarea [field]="field" [tree]="tree" />`,
19
+ imports: [FieldTextarea],
20
+ })
21
+ class Host {
22
+ field!: TextareaField;
23
+ tree!: RootTree;
24
+ }
25
+
26
+ describe('Campo textarea', () => {
27
+ beforeEach(async () => {
28
+ await TestBed.configureTestingModule({ imports: [Host] }).compileComponents();
29
+ });
30
+
31
+ async function create(field: TextareaField, initial = '') {
32
+ const fixture = TestBed.createComponent(Host);
33
+ const model = signal<Record<string, unknown>>({ [field.key]: initial });
34
+ const tree = form(model, () => {}, {
35
+ injector: TestBed.inject(EnvironmentInjector),
36
+ }) as unknown as RootTree;
37
+ fixture.componentInstance.field = field;
38
+ fixture.componentInstance.tree = tree;
39
+ fixture.detectChanges();
40
+ return { fixture, model };
41
+ }
42
+
43
+ const area = (fixture: { nativeElement: HTMLElement }) =>
44
+ fixture.nativeElement.querySelector('textarea') as HTMLTextAreaElement;
45
+
46
+ const chips = (fixture: { nativeElement: HTMLElement }): HTMLButtonElement[] =>
47
+ Array.from(fixture.nativeElement.querySelectorAll('[data-testid="textarea-tokens"] button'));
48
+
49
+ const PLANTILLA: TextareaField = {
50
+ type: 'textarea',
51
+ key: 'share_message_template',
52
+ label: 'Plantilla',
53
+ tokens: [
54
+ { key: 'ref', label: 'Código del pedido' },
55
+ { key: 'pin', label: 'PIN' },
56
+ ],
57
+ };
58
+
59
+ it('sin tokens no pinta la fila de chips', async () => {
60
+ const { fixture } = await create({ type: 'textarea', key: 'msg', label: 'Msg' });
61
+ expect(chips(fixture)).toHaveLength(0);
62
+ });
63
+
64
+ it('con tokens pinta un chip por token, con el rótulo humano', async () => {
65
+ const { fixture } = await create(PLANTILLA);
66
+ const labels = chips(fixture).map((b) => b.textContent?.trim());
67
+ expect(labels).toEqual(['Código del pedido', 'PIN']);
68
+ });
69
+
70
+ it('inserta {key} al final cuando el textarea está vacío', async () => {
71
+ const { fixture, model } = await create(PLANTILLA);
72
+ chips(fixture)[0].click();
73
+ fixture.detectChanges();
74
+ expect(model()[PLANTILLA.key]).toBe('{ref}');
75
+ });
76
+
77
+ it('inserta el token en la posición del cursor, no al final', async () => {
78
+ const { fixture, model } = await create(PLANTILLA, 'Hola ');
79
+ const a = area(fixture);
80
+ // Cursor justo después de "Hola ".
81
+ a.setSelectionRange(6, 6);
82
+ chips(fixture)[1].click();
83
+ fixture.detectChanges();
84
+ expect(model()[PLANTILLA.key]).toBe('Hola {pin}');
85
+ });
86
+
87
+ it('reemplaza la selección con el token', async () => {
88
+ const { fixture, model } = await create(PLANTILLA, 'borrar-esto');
89
+ const a = area(fixture);
90
+ a.setSelectionRange(0, 11);
91
+ chips(fixture)[0].click();
92
+ fixture.detectChanges();
93
+ expect(model()[PLANTILLA.key]).toBe('{ref}');
94
+ });
95
+
96
+ it('devuelve el foco al textarea tras insertar', async () => {
97
+ const { fixture } = await create(PLANTILLA);
98
+ chips(fixture)[0].click();
99
+ fixture.detectChanges();
100
+ expect(fixture.nativeElement.ownerDocument.activeElement).toBe(area(fixture));
101
+ });
102
+
103
+ it('acumula varios tokens escribiendo entre clics', async () => {
104
+ const { fixture, model } = await create({ ...PLANTILLA, key: 'm' });
105
+ chips(fixture)[0].click();
106
+ fixture.detectChanges();
107
+ // Escribir " con PIN " después del primer token.
108
+ const a = area(fixture);
109
+ a.setSelectionRange((model()['m'] as string).length, (model()['m'] as string).length);
110
+ a.value = `${model()['m']} con PIN `;
111
+ a.dispatchEvent(new Event('input'));
112
+ fixture.detectChanges();
113
+ chips(fixture)[1].click();
114
+ fixture.detectChanges();
115
+ expect(model()['m']).toBe('{ref} con PIN {pin}');
116
+ });
117
+ });
@@ -0,0 +1,126 @@
1
+ import { Component, computed, ElementRef, input, viewChild } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { TextareaField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+ import { Button } from '../../shared/components/button/button';
8
+
9
+ /**
10
+ * Área multilínea con contador de caracteres/palabras (base.md §5.1).
11
+ * Los límites salen de `validators.*`, con `minWords`/`maxWords` del tipo
12
+ * como respaldo, y coinciden con las reglas de `form-view/form-schema.ts`.
13
+ *
14
+ * Cuando el campo declara `tokens`, se pinta una fila de chips debajo: cada
15
+ * chip escribe `{key}` **en la posición del cursor** (no al final) y devuelve
16
+ * el foco, así el operador escribe → apreta → sigue escribiendo. Es la UI de
17
+ * la plantilla de compartir; el motor back no se entera.
18
+ */
19
+ @Component({
20
+ selector: 'app-field-textarea',
21
+ imports: [FieldAria, FormField, Button],
22
+ template: `
23
+ <div>
24
+ <textarea
25
+ class="field-input"
26
+ [id]="state().name()"
27
+ [formField]="node()"
28
+ [placeholder]="field().placeholder ?? ''"
29
+ [attr.rows]="field().rows ?? 4"
30
+ #area
31
+ ></textarea>
32
+ @if (field().tokens?.length) {
33
+ <div class="mt-1.5 flex flex-wrap gap-2 lg:gap-1.5" data-testid="textarea-tokens">
34
+ @for (token of field().tokens; track token.key) {
35
+ <button
36
+ app-button
37
+ type="button"
38
+ variant="outline"
39
+ size="sm"
40
+ [attr.title]="'Inserta {' + token.key + '} en el texto'"
41
+ (click)="insertToken(token.key)"
42
+ >
43
+ {{ token.label }}
44
+ </button>
45
+ }
46
+ </div>
47
+ }
48
+ @if (counter(); as counter) {
49
+ <div class="mt-1 flex justify-right text-xs text-neutral" data-testid="textarea-counter">
50
+ {{ counter }}
51
+ </div>
52
+ }
53
+ </div>
54
+ `,
55
+ })
56
+ export class FieldTextarea {
57
+ readonly field = input.required<TextareaField>();
58
+ readonly tree = input.required<RootTree>();
59
+
60
+ private readonly area = viewChild<ElementRef<HTMLTextAreaElement>>('area');
61
+
62
+ protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
63
+ protected readonly state = computed(() => this.node()());
64
+
65
+ /**
66
+ * Escribe `{key}` donde está el cursor (o reemplaza la selección) y
67
+ * reposiciona el cursor DESPUÉS del token insertado, para seguir escribiendo.
68
+ *
69
+ * Si el textarea aún no está montado (o no hay cursor válido) cae al
70
+ * final del texto: nunca revienta por un click temprano.
71
+ */
72
+ protected insertToken(key: string): void {
73
+ const area = this.area()?.nativeElement;
74
+ const token = `{${key}}`;
75
+ const current =
76
+ typeof this.state().value() === 'string' ? (this.state().value() as string) : '';
77
+
78
+ let next: string;
79
+ let caret: number;
80
+ if (area) {
81
+ const start = area.selectionStart ?? current.length;
82
+ const end = area.selectionEnd ?? start;
83
+ next = current.slice(0, start) + token + current.slice(end);
84
+ caret = start + token.length;
85
+ } else {
86
+ next = current + token;
87
+ caret = next.length;
88
+ }
89
+
90
+ this.state().value.set(next);
91
+
92
+ if (area) {
93
+ area.focus();
94
+ area.setSelectionRange(caret, caret);
95
+ }
96
+ }
97
+
98
+ /** Contador `n/m …`; `null` cuando el campo no declara límites. */
99
+ protected readonly counter = computed<string | null>(() => {
100
+ const field = this.field();
101
+ const value = this.state().value();
102
+ const text = typeof value === 'string' ? value : '';
103
+ const chars = text.length;
104
+ const words = text.trim() === '' ? 0 : text.trim().split(/\s+/).length;
105
+
106
+ const parts: string[] = [];
107
+ const maxChars = field.validators?.maxLength;
108
+ if (maxChars !== undefined) {
109
+ parts.push(`${chars}/${maxChars} caracteres`);
110
+ }
111
+ // `validators` tiene prioridad sobre las opciones propias del tipo
112
+ // (mismo criterio que las reglas de `form-schema.ts`).
113
+ const maxWords = field.validators?.maxWords ?? field.maxWords;
114
+ const minWords = field.validators?.minWords ?? field.minWords;
115
+ if (maxWords !== undefined) {
116
+ parts.push(
117
+ minWords !== undefined
118
+ ? `${words}/${maxWords} palabras (mín. ${minWords})`
119
+ : `${words}/${maxWords} palabras`,
120
+ );
121
+ } else if (minWords !== undefined) {
122
+ parts.push(`${words} palabras (mín. ${minWords})`);
123
+ }
124
+ return parts.length > 0 ? parts.join(' · ') : null;
125
+ });
126
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `time` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'time',
7
+ since: '1.0',
8
+ load: () => import('./time').then((module) => module.FieldTime),
9
+ // Nace como cadena: los textos, fechas, urls, selects y relacionales.
10
+ seed: 'string',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,28 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { TimeField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /** Selector de hora `HH:mm` (base.md §5.1). */
9
+ @Component({
10
+ selector: 'app-field-time',
11
+ imports: [FieldAria, FormField],
12
+ template: `
13
+ <input
14
+ type="time"
15
+ class="field-input"
16
+ [id]="state().name()"
17
+ [formField]="node()"
18
+ [placeholder]="field().placeholder ?? ''"
19
+ />
20
+ `,
21
+ })
22
+ export class FieldTime {
23
+ readonly field = input.required<TimeField>();
24
+ readonly tree = input.required<RootTree>();
25
+
26
+ protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
27
+ protected readonly state = computed(() => this.node()());
28
+ }
@@ -0,0 +1,49 @@
1
+ import { Component, input } from '@angular/core';
2
+
3
+ import { LucideInfo } from '@lucide/angular';
4
+
5
+ /**
6
+ * Degradación controlada de un tipo de campo sin renderizador (R1.10).
7
+ *
8
+ * El `@switch` de `field-host` NO tiene `@default`: un `type` fuera de
9
+ * `SUPPORTED_FIELD_TYPE_LIST` no revienta el template, simplemente no pinta
10
+ * nada. Eso cumple «nunca pantalla rota», pero deja el formulario con un
11
+ * hueco mudo donde el operador no sabe si le falta un campo o si lo escribió
12
+ * mal.
13
+ *
14
+ * Este componente es el aviso de **desarrollo**: se renderiza sólo con
15
+ * `isDevMode()`, nombra el tipo y el campo, y deja claro que el VALOR existe
16
+ * en el schema y se conserva — no se pierde, sólo no se pinta. En producción
17
+ * `field-host` lo omite y deja un `console.warn`.
18
+ *
19
+ * No dibuja control ni etiqueta asociada: no hay `id` al que apuntar un
20
+ * `<label for>` (mismo criterio que el bloque de `composite()` en el host).
21
+ */
22
+ @Component({
23
+ selector: 'app-unknown-render',
24
+ imports: [LucideInfo],
25
+ template: `
26
+ <div
27
+ class="mb-4 rounded-lg border border-amber-400/50 bg-amber-500/10 px-3 py-2"
28
+ data-field-unknown
29
+ >
30
+ <p class="flex items-center gap-1.5 text-xs font-medium text-amber-600">
31
+ <svg lucideInfo size="14" class="shrink-0" aria-hidden="true" />
32
+ Campo sin renderizador en esta versión
33
+ </p>
34
+ <p class="mt-1 text-help text-neutral">
35
+ Tipo <code class="rounded bg-amber-500/20 px-1 font-mono">{{ type() }}</code>
36
+ @if (label()) {
37
+ del campo «{{ label() }}»
38
+ }
39
+ — el valor existe en el schema y se conserva; sólo no se dibuja.
40
+ </p>
41
+ </div>
42
+ `,
43
+ })
44
+ export class UnknownRender {
45
+ /** `type` declarado en el schema y fuera de `SUPPORTED_FIELD_TYPE_LIST`. */
46
+ readonly type = input.required<string>();
47
+ /** Rótulo del campo, para que el aviso sea ubicable en el formulario. */
48
+ readonly label = input('');
49
+ }
@@ -0,0 +1,12 @@
1
+ import type { RenderDescriptor } from '../../core/registry/render-registry';
2
+
3
+ /** Descriptor del campo `url` (R3.3). */
4
+ export const descriptor: RenderDescriptor = {
5
+ kind: 'field',
6
+ type: 'url',
7
+ since: '1.0',
8
+ load: () => import('./url').then((module) => module.FieldUrl),
9
+ // Nace como cadena: los textos, fechas, urls, selects y relacionales.
10
+ seed: 'string',
11
+ surfaces: ['form', 'display'],
12
+ };
@@ -0,0 +1,53 @@
1
+ import { Component, computed, input } from '@angular/core';
2
+ import { FormField } from '@angular/forms/signals';
3
+
4
+ import type { UrlField } from '../../core/models/schema.model';
5
+ import { FieldAria } from '../field-aria';
6
+ import { childTree, type RootTree } from '../field-node';
7
+
8
+ /**
9
+ * Input con validación de URL y botón «abrir» (base.md §5.1). La validación
10
+ * de formato la aporta Fase 10 (`pattern`/`validate`); acá solo se enlaza el
11
+ * valor cuando es http(s).
12
+ */
13
+ @Component({
14
+ selector: 'app-field-url',
15
+ imports: [FieldAria, FormField],
16
+ template: `
17
+ <div class="flex items-start gap-2">
18
+ <input
19
+ type="url"
20
+ class="field-input"
21
+ [id]="state().name()"
22
+ [formField]="node()"
23
+ [placeholder]="field().placeholder ?? 'https://…'"
24
+ />
25
+ @if (href(); as href) {
26
+ <!-- Mismo lenguaje que la variante outline de app-button (el
27
+ selector del componente es button[app-button], un ancla no
28
+ puede usarlo): borde /40, hover con fondo, 44 px en mobile. -->
29
+ <a
30
+ class="min-h-11 shrink-0 rounded-lg border border-neutral/40 bg-surface px-3 py-2 text-sm text-neutral hover:bg-neutral/10 lg:min-h-0"
31
+ [href]="href"
32
+ target="_blank"
33
+ rel="noopener noreferrer"
34
+ >
35
+ Abrir
36
+ </a>
37
+ }
38
+ </div>
39
+ `,
40
+ })
41
+ export class FieldUrl {
42
+ readonly field = input.required<UrlField>();
43
+ readonly tree = input.required<RootTree>();
44
+
45
+ protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
46
+ protected readonly state = computed(() => this.node()());
47
+
48
+ /** Solo http(s): evita enlazar `javascript:` u otros esquemas. */
49
+ protected readonly href = computed(() => {
50
+ const value = this.state().value();
51
+ return typeof value === 'string' && /^https?:\/\//i.test(value) ? value : null;
52
+ });
53
+ }