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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (368) hide show
  1. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +84 -0
  2. package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +1 -0
  3. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +140 -0
  4. package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +1 -0
  5. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +113 -0
  6. package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +1 -0
  7. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +163 -0
  8. package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +1 -0
  9. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +1027 -0
  10. package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +1 -0
  11. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +278 -0
  12. package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +1 -0
  13. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +107 -0
  14. package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +1 -0
  15. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +101 -0
  16. package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +1 -0
  17. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +141 -0
  18. package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +1 -0
  19. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +88 -0
  20. package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +1 -0
  21. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +107 -0
  22. package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +1 -0
  23. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +140 -0
  24. package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +1 -0
  25. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +119 -0
  26. package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +1 -0
  27. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +125 -0
  28. package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +1 -0
  29. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +152 -0
  30. package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +1 -0
  31. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +211 -0
  32. package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +1 -0
  33. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +116 -0
  34. package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +1 -0
  35. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +111 -0
  36. package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +1 -0
  37. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +122 -0
  38. package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +1 -0
  39. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +107 -0
  40. package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +1 -0
  41. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +109 -0
  42. package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +1 -0
  43. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +45 -0
  44. package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +1 -0
  45. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +50 -0
  46. package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +1 -0
  47. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +24 -0
  48. package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +1 -0
  49. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +27 -0
  50. package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +1 -0
  51. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +408 -0
  52. package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +1 -0
  53. package/fesm2022/tinya-code-gas-admin-ui-form-view-CEe7aZ4m.mjs +4516 -0
  54. package/fesm2022/tinya-code-gas-admin-ui-form-view-CEe7aZ4m.mjs.map +1 -0
  55. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +82 -0
  56. package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +1 -0
  57. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +105 -0
  58. package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +1 -0
  59. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +65 -0
  60. package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +1 -0
  61. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +65 -0
  62. package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +1 -0
  63. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +65 -0
  64. package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +1 -0
  65. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +59 -0
  66. package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +1 -0
  67. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +94 -0
  68. package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +1 -0
  69. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +72 -0
  70. package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +1 -0
  71. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +238 -0
  72. package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +1 -0
  73. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +416 -0
  74. package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +1 -0
  75. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +439 -0
  76. package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +1 -0
  77. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +513 -0
  78. package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +1 -0
  79. package/fesm2022/tinya-code-gas-admin-ui-list-DTtCYS4d.mjs +637 -0
  80. package/fesm2022/tinya-code-gas-admin-ui-list-DTtCYS4d.mjs.map +1 -0
  81. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +127 -0
  82. package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +1 -0
  83. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +251 -0
  84. package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +1 -0
  85. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +66 -0
  86. package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +1 -0
  87. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +114 -0
  88. package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +1 -0
  89. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +116 -0
  90. package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +1 -0
  91. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +235 -0
  92. package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +1 -0
  93. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +8 -0
  94. package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +1 -0
  95. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +124 -0
  96. package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +1 -0
  97. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +249 -0
  98. package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +1 -0
  99. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +39 -0
  100. package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +1 -0
  101. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +309 -0
  102. package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +1 -0
  103. package/fesm2022/tinya-code-gas-admin-ui-render-registry-B88Pme-y.mjs +576 -0
  104. package/fesm2022/tinya-code-gas-admin-ui-render-registry-B88Pme-y.mjs.map +1 -0
  105. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +161 -0
  106. package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +1 -0
  107. package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +11 -0
  108. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +685 -0
  109. package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +1 -0
  110. package/fesm2022/tinya-code-gas-admin-ui-seed-DkVZbxPP.mjs +80 -0
  111. package/fesm2022/tinya-code-gas-admin-ui-seed-DkVZbxPP.mjs.map +1 -0
  112. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +296 -0
  113. package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +1 -0
  114. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +119 -0
  115. package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +1 -0
  116. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +158 -0
  117. package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +1 -0
  118. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +26 -0
  119. package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +1 -0
  120. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +205 -0
  121. package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +1 -0
  122. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +432 -0
  123. package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +1 -0
  124. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +36 -0
  125. package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +1 -0
  126. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +107 -0
  127. package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +1 -0
  128. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +223 -0
  129. package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +1 -0
  130. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +107 -0
  131. package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +1 -0
  132. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +60 -0
  133. package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +1 -0
  134. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +141 -0
  135. package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +1 -0
  136. package/fesm2022/tinya-code-gas-admin-ui.mjs +4738 -0
  137. package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -0
  138. package/package.json +15 -3
  139. package/types/tinya-code-gas-admin-ui.d.ts +3634 -0
  140. package/ng-package.json +0 -7
  141. package/src/lib/actions/action-registry.spec.ts +0 -52
  142. package/src/lib/actions/action-registry.ts +0 -126
  143. package/src/lib/actions/action-service.spec.ts +0 -215
  144. package/src/lib/actions/action-service.ts +0 -87
  145. package/src/lib/actions/descriptors.spec.ts +0 -155
  146. package/src/lib/actions/descriptors.ts +0 -154
  147. package/src/lib/columns/column-cell.ts +0 -33
  148. package/src/lib/columns/column-defaults.spec.ts +0 -99
  149. package/src/lib/columns/column-defaults.ts +0 -84
  150. package/src/lib/columns/column-format.spec.ts +0 -274
  151. package/src/lib/columns/descriptors.ts +0 -113
  152. package/src/lib/columns/format-badge.ts +0 -51
  153. package/src/lib/columns/format-boolean.ts +0 -58
  154. package/src/lib/columns/format-currency.ts +0 -30
  155. package/src/lib/columns/format-date.ts +0 -35
  156. package/src/lib/columns/format-number.ts +0 -32
  157. package/src/lib/columns/format-text.ts +0 -23
  158. package/src/lib/columns/format-thumbnail.ts +0 -63
  159. package/src/lib/core/adapters/firebase-auth.adapter.ts +0 -6
  160. package/src/lib/core/guards/auth.guard.spec.ts +0 -88
  161. package/src/lib/core/guards/auth.guard.ts +0 -48
  162. package/src/lib/core/guards/unsaved-changes.guard.ts +0 -16
  163. package/src/lib/core/interceptors/api-error.interceptor.spec.ts +0 -182
  164. package/src/lib/core/interceptors/api-error.interceptor.ts +0 -87
  165. package/src/lib/core/models/api.model.ts +0 -104
  166. package/src/lib/core/models/schema-descriptions.ts +0 -118
  167. package/src/lib/core/models/schema.model.ts +0 -1097
  168. package/src/lib/core/registry/composition-validator.spec.ts +0 -358
  169. package/src/lib/core/registry/composition-validator.ts +0 -270
  170. package/src/lib/core/registry/field-validators.spec.ts +0 -199
  171. package/src/lib/core/registry/field-validators.ts +0 -440
  172. package/src/lib/core/registry/render-host.spec.ts +0 -208
  173. package/src/lib/core/registry/render-host.ts +0 -646
  174. package/src/lib/core/registry/render-registry.spec.ts +0 -96
  175. package/src/lib/core/registry/render-registry.ts +0 -312
  176. package/src/lib/core/registry/schema-catalog.ts +0 -156
  177. package/src/lib/core/services/api.service.spec.ts +0 -1142
  178. package/src/lib/core/services/api.service.ts +0 -371
  179. package/src/lib/core/services/auth.service.spec.ts +0 -653
  180. package/src/lib/core/services/auth.service.ts +0 -503
  181. package/src/lib/core/services/config.service.spec.ts +0 -121
  182. package/src/lib/core/services/config.service.ts +0 -119
  183. package/src/lib/core/services/data-source.service.spec.ts +0 -179
  184. package/src/lib/core/services/data-source.service.ts +0 -142
  185. package/src/lib/core/services/error-mapper.service.spec.ts +0 -50
  186. package/src/lib/core/services/error-mapper.service.ts +0 -49
  187. package/src/lib/core/services/notification.service.ts +0 -73
  188. package/src/lib/core/services/relation-drawer.service.ts +0 -41
  189. package/src/lib/core/services/theme.service.spec.ts +0 -106
  190. package/src/lib/core/services/theme.service.ts +0 -86
  191. package/src/lib/core/services/transport.spec.ts +0 -198
  192. package/src/lib/core/services/transport.ts +0 -164
  193. package/src/lib/core/services/upload.service.ts +0 -81
  194. package/src/lib/core/testing/categories.schema.ts +0 -125
  195. package/src/lib/core/testing/orders.schema.ts +0 -372
  196. package/src/lib/core/testing/products.schema.ts +0 -258
  197. package/src/lib/core/testing/site.schema.ts +0 -195
  198. package/src/lib/core/testing/test-catalog.ts +0 -21
  199. package/src/lib/core/tokens.ts +0 -112
  200. package/src/lib/core/utils/clipboard.spec.ts +0 -55
  201. package/src/lib/core/utils/clipboard.ts +0 -90
  202. package/src/lib/core/utils/context-resolver.spec.ts +0 -95
  203. package/src/lib/core/utils/context-resolver.ts +0 -101
  204. package/src/lib/core/utils/response-unwrapper.spec.ts +0 -70
  205. package/src/lib/core/utils/response-unwrapper.ts +0 -103
  206. package/src/lib/core/utils/source-options.spec.ts +0 -96
  207. package/src/lib/core/utils/source-options.ts +0 -54
  208. package/src/lib/core/utils/template-interpolator.spec.ts +0 -59
  209. package/src/lib/core/utils/template-interpolator.ts +0 -56
  210. package/src/lib/core/view-navigation/view-navigation.service.spec.ts +0 -207
  211. package/src/lib/core/view-navigation/view-navigation.service.ts +0 -132
  212. package/src/lib/fields/boolean/boolean.descriptor.ts +0 -11
  213. package/src/lib/fields/boolean/boolean.ts +0 -40
  214. package/src/lib/fields/color/color.descriptor.ts +0 -12
  215. package/src/lib/fields/color/color.ts +0 -34
  216. package/src/lib/fields/currency/currency.descriptor.ts +0 -12
  217. package/src/lib/fields/currency/currency.ts +0 -67
  218. package/src/lib/fields/date/date.descriptor.ts +0 -12
  219. package/src/lib/fields/date/date.ts +0 -36
  220. package/src/lib/fields/descriptors.ts +0 -122
  221. package/src/lib/fields/display/display-badge.ts +0 -65
  222. package/src/lib/fields/display/display-date.ts +0 -39
  223. package/src/lib/fields/display/display-defaults.ts +0 -84
  224. package/src/lib/fields/display/display-file.ts +0 -41
  225. package/src/lib/fields/display/display-image.ts +0 -54
  226. package/src/lib/fields/display/display-list.ts +0 -107
  227. package/src/lib/fields/display/display-number.ts +0 -41
  228. package/src/lib/fields/display/display-text.ts +0 -42
  229. package/src/lib/fields/display/display.spec.ts +0 -242
  230. package/src/lib/fields/dropdown/dropdown.descriptor.ts +0 -12
  231. package/src/lib/fields/dropdown/dropdown.ts +0 -45
  232. package/src/lib/fields/email/email.descriptor.ts +0 -12
  233. package/src/lib/fields/email/email.ts +0 -28
  234. package/src/lib/fields/field-aria.ts +0 -60
  235. package/src/lib/fields/field-host/field-host.spec.ts +0 -118
  236. package/src/lib/fields/field-host/field-host.ts +0 -47
  237. package/src/lib/fields/field-node.ts +0 -88
  238. package/src/lib/fields/file/file.descriptor.ts +0 -14
  239. package/src/lib/fields/file/file.spec.ts +0 -134
  240. package/src/lib/fields/file/file.ts +0 -281
  241. package/src/lib/fields/group/group.descriptor.ts +0 -21
  242. package/src/lib/fields/group/group.ts +0 -110
  243. package/src/lib/fields/image/image.descriptor.ts +0 -12
  244. package/src/lib/fields/image/image.spec.ts +0 -117
  245. package/src/lib/fields/image/image.ts +0 -281
  246. package/src/lib/fields/key-value/key-value.descriptor.ts +0 -11
  247. package/src/lib/fields/key-value/key-value.ts +0 -283
  248. package/src/lib/fields/list/list.descriptor.ts +0 -21
  249. package/src/lib/fields/list/list.ts +0 -376
  250. package/src/lib/fields/multiselect/multiselect.descriptor.ts +0 -11
  251. package/src/lib/fields/multiselect/multiselect.spec.ts +0 -142
  252. package/src/lib/fields/multiselect/multiselect.ts +0 -152
  253. package/src/lib/fields/number/number.descriptor.ts +0 -12
  254. package/src/lib/fields/number/number.ts +0 -40
  255. package/src/lib/fields/phone/phone.descriptor.ts +0 -12
  256. package/src/lib/fields/phone/phone.ts +0 -46
  257. package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +0 -12
  258. package/src/lib/fields/readonly-text/readonly-text.ts +0 -58
  259. package/src/lib/fields/relation/relation.descriptor.ts +0 -12
  260. package/src/lib/fields/relation/relation.spec.ts +0 -193
  261. package/src/lib/fields/relation/relation.ts +0 -224
  262. package/src/lib/fields/rich-select/rich-select.descriptor.ts +0 -12
  263. package/src/lib/fields/rich-select/rich-select.ts +0 -175
  264. package/src/lib/fields/select/radio-mode.ts +0 -25
  265. package/src/lib/fields/select/select.descriptor.ts +0 -12
  266. package/src/lib/fields/select/select.spec.ts +0 -162
  267. package/src/lib/fields/select/select.ts +0 -210
  268. package/src/lib/fields/slug/slug.descriptor.ts +0 -19
  269. package/src/lib/fields/slug/slug.ts +0 -67
  270. package/src/lib/fields/string-list/string-list.descriptor.ts +0 -11
  271. package/src/lib/fields/string-list/string-list.ts +0 -222
  272. package/src/lib/fields/text/text.descriptor.ts +0 -12
  273. package/src/lib/fields/text/text.ts +0 -35
  274. package/src/lib/fields/textarea/textarea.descriptor.ts +0 -12
  275. package/src/lib/fields/textarea/textarea.spec.ts +0 -117
  276. package/src/lib/fields/textarea/textarea.ts +0 -126
  277. package/src/lib/fields/time/time.descriptor.ts +0 -12
  278. package/src/lib/fields/time/time.ts +0 -28
  279. package/src/lib/fields/unknown-render/unknown-render.ts +0 -49
  280. package/src/lib/fields/url/url.descriptor.ts +0 -12
  281. package/src/lib/fields/url/url.ts +0 -53
  282. package/src/lib/gas-admin-ui.spec.ts +0 -21
  283. package/src/lib/gas-admin-ui.ts +0 -9
  284. package/src/lib/layouts/descriptors.ts +0 -9
  285. package/src/lib/pages/dashboard/dashboard.ts +0 -167
  286. package/src/lib/pages/detail-view/detail-view.spec.ts +0 -333
  287. package/src/lib/pages/detail-view/detail-view.ts +0 -570
  288. package/src/lib/pages/drawer-form/drawer-form.ts +0 -228
  289. package/src/lib/pages/form-view/form-model.ts +0 -88
  290. package/src/lib/pages/form-view/form-schema.ts +0 -255
  291. package/src/lib/pages/form-view/form-view.ts +0 -1622
  292. package/src/lib/pages/list-view/list-view.formats.spec.ts +0 -214
  293. package/src/lib/pages/list-view/list-view.ts +0 -882
  294. package/src/lib/pages/login/login.spec.ts +0 -109
  295. package/src/lib/pages/login/login.ts +0 -227
  296. package/src/lib/pages/resource-page/resource-page.ts +0 -234
  297. package/src/lib/shared/components/badge/badge.ts +0 -35
  298. package/src/lib/shared/components/button/button.ts +0 -65
  299. package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +0 -48
  300. package/src/lib/shared/components/copy-button/copy-button.ts +0 -56
  301. package/src/lib/shared/components/copy-share/copy-share.spec.ts +0 -122
  302. package/src/lib/shared/components/copy-share/copy-share.ts +0 -107
  303. package/src/lib/shared/components/drawer/drawer.spec.ts +0 -127
  304. package/src/lib/shared/components/drawer/drawer.ts +0 -172
  305. package/src/lib/shared/components/empty-state/empty-state.ts +0 -30
  306. package/src/lib/shared/components/modal/modal.spec.ts +0 -149
  307. package/src/lib/shared/components/modal/modal.ts +0 -145
  308. package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +0 -165
  309. package/src/lib/shared/components/post-create-modal/post-create-modal.ts +0 -159
  310. package/src/lib/shared/components/skeleton/skeleton.ts +0 -31
  311. package/src/lib/shared/components/toast/toast.ts +0 -58
  312. package/src/lib/shared/utils/col-span.ts +0 -25
  313. package/src/lib/shared/utils/condition-evaluator.ts +0 -53
  314. package/src/lib/shared/utils/depends-on.ts +0 -48
  315. package/src/lib/shared/utils/dialog-focus.ts +0 -28
  316. package/src/lib/shared/utils/field-path.ts +0 -49
  317. package/src/lib/shared/utils/gap-sorting.ts +0 -144
  318. package/src/lib/shared/utils/seed.spec.ts +0 -64
  319. package/src/lib/shared/utils/seed.ts +0 -149
  320. package/src/lib/shared/utils/slugify.ts +0 -13
  321. package/src/lib/shared/utils/validators.ts +0 -41
  322. package/src/lib/shell/layout/layout.ts +0 -63
  323. package/src/lib/shell/shell.routes.spec.ts +0 -59
  324. package/src/lib/shell/shell.routes.ts +0 -122
  325. package/src/lib/shell/sidebar/sidebar.ts +0 -139
  326. package/src/lib/shell/theme-toggle/theme-toggle.ts +0 -74
  327. package/src/lib/views/calendar-view/calendar-model.ts +0 -426
  328. package/src/lib/views/calendar-view/calendar-view.spec.ts +0 -557
  329. package/src/lib/views/calendar-view/calendar-view.ts +0 -568
  330. package/src/lib/views/composer/composer-host.spec.ts +0 -301
  331. package/src/lib/views/composer/composer-host.ts +0 -248
  332. package/src/lib/views/composer/composer-imports.spec.ts +0 -54
  333. package/src/lib/views/composer/view-context.spec.ts +0 -117
  334. package/src/lib/views/composer/view-context.ts +0 -109
  335. package/src/lib/views/composer/view-slot.spec.ts +0 -130
  336. package/src/lib/views/composer/view-slot.ts +0 -178
  337. package/src/lib/views/descriptors.ts +0 -46
  338. package/src/lib/views/kanban-view/kanban-model.ts +0 -173
  339. package/src/lib/views/kanban-view/kanban-view.spec.ts +0 -540
  340. package/src/lib/views/kanban-view/kanban-view.ts +0 -327
  341. package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +0 -17
  342. package/src/lib/widgets/bar-chart/bar-chart.spec.ts +0 -66
  343. package/src/lib/widgets/bar-chart/bar-chart.ts +0 -76
  344. package/src/lib/widgets/chart-line/chart-line.descriptor.ts +0 -17
  345. package/src/lib/widgets/chart-line/chart-line.spec.ts +0 -113
  346. package/src/lib/widgets/chart-line/chart-line.ts +0 -219
  347. package/src/lib/widgets/descriptors.ts +0 -29
  348. package/src/lib/widgets/metric-card/metric-card.descriptor.ts +0 -41
  349. package/src/lib/widgets/metric-card/metric-card.spec.ts +0 -52
  350. package/src/lib/widgets/metric-card/metric-card.ts +0 -48
  351. package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +0 -13
  352. package/src/lib/widgets/quick-actions/quick-actions.spec.ts +0 -61
  353. package/src/lib/widgets/quick-actions/quick-actions.ts +0 -100
  354. package/src/lib/widgets/record-list/record-list.descriptor.ts +0 -16
  355. package/src/lib/widgets/record-list/record-list.spec.ts +0 -79
  356. package/src/lib/widgets/record-list/record-list.ts +0 -115
  357. package/src/lib/widgets/series.spec.ts +0 -42
  358. package/src/lib/widgets/series.ts +0 -56
  359. package/src/lib/widgets/status-progress/status-progress.descriptor.ts +0 -19
  360. package/src/lib/widgets/status-progress/status-progress.spec.ts +0 -63
  361. package/src/lib/widgets/status-progress/status-progress.ts +0 -104
  362. package/src/lib/widgets/widget-host/widget-host.spec.ts +0 -179
  363. package/src/lib/widgets/widget-host/widget-host.ts +0 -99
  364. package/src/public-api.ts +0 -104
  365. package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
  366. package/tsconfig.lib.json +0 -13
  367. package/tsconfig.lib.prod.json +0 -11
  368. package/tsconfig.spec.json +0 -10
@@ -1,48 +0,0 @@
1
- import { Component, input, output } from '@angular/core';
2
-
3
- import { Button } from '../button/button';
4
- import { Modal } from '../modal/modal';
5
-
6
- /**
7
- * Opciones del diálogo. Es estructuralmente compatible con
8
- * `core.NotificationService['confirmation']` (mismos campos) — `shared` no
9
- * importa `core`, quien une ambos lados es el shell (Fase 7).
10
- */
11
- export interface ConfirmDialogOptions {
12
- title: string;
13
- message: string;
14
- confirmLabel?: string;
15
- cancelLabel?: string;
16
- }
17
-
18
- @Component({
19
- selector: 'app-confirm-dialog',
20
- imports: [Modal, Button],
21
- template: `
22
- @if (options(); as opts) {
23
- <app-modal [open]="true" [title]="opts.title" (close)="cancelled.emit()">
24
- <p class="text-sm text-neutral">{{ opts.message }}</p>
25
- <div class="mt-6 flex justify-end gap-3">
26
- <button app-button variant="outline" type="button" (click)="cancelled.emit()">
27
- {{ opts.cancelLabel ?? 'Cancel' }}
28
- </button>
29
- <button
30
- app-button
31
- type="button"
32
- [variant]="danger() ? 'danger' : 'primary'"
33
- (click)="confirmed.emit()"
34
- >
35
- {{ opts.confirmLabel ?? 'Confirm' }}
36
- </button>
37
- </div>
38
- </app-modal>
39
- }
40
- `,
41
- })
42
- export class ConfirmDialog {
43
- /** `null` = oculto (viene de `NotificationService.confirmation`). */
44
- readonly options = input<ConfirmDialogOptions | null>(null);
45
- readonly danger = input(false);
46
- readonly confirmed = output<void>();
47
- readonly cancelled = output<void>();
48
- }
@@ -1,56 +0,0 @@
1
- import { Component, inject, input, signal } from '@angular/core';
2
- import { LucideCopy, LucideCheck } from '@lucide/angular';
3
-
4
- import { writeClipboard } from '../../../core/utils/clipboard';
5
- import { NotificationService } from '../../../core/services/notification.service';
6
- import { Button } from '../button/button';
7
-
8
- /**
9
- * Botón para copiar un valor al portapapeles.
10
- *
11
- * Muestra un ícono de copiar que se reemplaza por una marca de éxito tras
12
- * copiar. Usa el mismo canal de portapapeles que `CopyShare` para consistencia.
13
- */
14
- @Component({
15
- selector: 'app-copy-button',
16
- imports: [Button, LucideCopy, LucideCheck],
17
- template: `
18
- <button
19
- app-button
20
- type="button"
21
- variant="ghost"
22
- size="icon"
23
- [disabled]="copied()"
24
- (click)="copy()"
25
- [title]="'Copiar ' + value()"
26
- class="text-neutral hover:text-primary"
27
- >
28
- @if (copied()) {
29
- <svg lucideCheck size="16" class="text-primary" />
30
- } @else {
31
- <svg lucideCopy size="16" />
32
- }
33
- </button>
34
- `,
35
- })
36
- export class CopyButton {
37
- /** Valor a copiar al portapapeles. */
38
- readonly value = input.required<unknown>();
39
-
40
- private readonly notify = inject(NotificationService);
41
- private readonly copied = signal(false);
42
-
43
- protected readonly copied$ = this.copied.asReadonly();
44
-
45
- protected async copy(): Promise<void> {
46
- const text = String(this.value() ?? '');
47
- const ok = await writeClipboard(text);
48
- if (ok) {
49
- this.copied.set(true);
50
- this.notify.success('Copiado al portapapeles.');
51
- setTimeout(() => this.copied.set(false), 1500);
52
- } else {
53
- this.notify.error('No se pudo copiar.');
54
- }
55
- }
56
- }
@@ -1,122 +0,0 @@
1
- import { ComponentFixture, TestBed } from '@angular/core/testing';
2
- import { vi } from 'vitest';
3
-
4
- import { NotificationService } from '../../../core/services/notification.service';
5
- import { CopyShare } from './copy-share';
6
-
7
- const URL_UNDER_TEST = 'https://ejemplo.test/p/abc123';
8
-
9
- /**
10
- * `navigator.clipboard` / `navigator.share` no existen en el DOM de prueba y
11
- * son propiedades del prototipo en los navegadores reales: se tapan con una
12
- * propiedad propia configurable y se restaura el descriptor original.
13
- */
14
- const OWN = new Map<string, PropertyDescriptor | undefined>();
15
-
16
- function stubNavigator(key: 'clipboard' | 'share', value: unknown): void {
17
- if (!OWN.has(key)) {
18
- OWN.set(key, Object.getOwnPropertyDescriptor(navigator, key));
19
- }
20
- Object.defineProperty(navigator, key, { value, configurable: true, writable: true });
21
- }
22
-
23
- function clearNavigator(key: 'clipboard' | 'share'): void {
24
- if (!OWN.has(key)) {
25
- OWN.set(key, Object.getOwnPropertyDescriptor(navigator, key));
26
- }
27
- Object.defineProperty(navigator, key, { value: undefined, configurable: true, writable: true });
28
- }
29
-
30
- function restoreNavigator(): void {
31
- for (const [key, descriptor] of OWN) {
32
- if (descriptor) {
33
- Object.defineProperty(navigator, key, descriptor);
34
- } else {
35
- delete (navigator as unknown as Record<string, unknown>)[key];
36
- }
37
- }
38
- OWN.clear();
39
- }
40
-
41
- describe('CopyShare', () => {
42
- let fixture: ComponentFixture<CopyShare>;
43
- let notifications: NotificationService;
44
-
45
- beforeEach(async () => {
46
- await TestBed.configureTestingModule({ imports: [CopyShare] }).compileComponents();
47
- fixture = TestBed.createComponent(CopyShare);
48
- // `canShare()` es un computed SIN dependencias reactivas: se cachea en el
49
- // primer render. Los tests tapan `navigator` ANTES del primer detectChanges.
50
- fixture.componentRef.setInput('url', URL_UNDER_TEST);
51
- notifications = TestBed.inject(NotificationService);
52
- });
53
-
54
- afterEach(() => restoreNavigator());
55
-
56
- const root = (): HTMLElement => fixture.nativeElement as HTMLElement;
57
-
58
- const button = (text: string): HTMLButtonElement =>
59
- [...root().querySelectorAll<HTMLButtonElement>('button')].find((el) =>
60
- el.textContent.trim().startsWith(text),
61
- )!;
62
-
63
- const messages = (): string[] => notifications.toasts().map((toast) => toast.message);
64
-
65
- it('copia el enlace al portapapeles y dispara la notificación de éxito', async () => {
66
- const writeText = vi.fn().mockResolvedValue(undefined);
67
- stubNavigator('clipboard', { writeText });
68
- fixture.detectChanges();
69
-
70
- button('Copiar enlace').click();
71
- await fixture.whenStable();
72
-
73
- expect(writeText).toHaveBeenCalledWith(URL_UNDER_TEST);
74
- expect(messages()).toContain('Mensaje copiado.');
75
- });
76
-
77
- it('copia el MENSAJE formateado completo cuando hay shareText', async () => {
78
- const writeText = vi.fn().mockResolvedValue(undefined);
79
- stubNavigator('clipboard', { writeText });
80
- const message = `Hola Ana, puede retirar su pedido A-100 aquí: ${URL_UNDER_TEST}`;
81
- fixture.componentRef.setInput('shareText', message);
82
- fixture.detectChanges();
83
-
84
- button('Copiar enlace').click();
85
- await fixture.whenStable();
86
-
87
- // El bug: sólo se escribía la URL y el saludo/referencia/PIN se perdían.
88
- expect(writeText).toHaveBeenCalledWith(message);
89
- expect(writeText).not.toHaveBeenCalledWith(URL_UNDER_TEST);
90
- });
91
-
92
- it('sin clipboard no revienta: cae al fallback y avisa si tampoco funciona', async () => {
93
- clearNavigator('clipboard');
94
- fixture.detectChanges();
95
-
96
- expect(() => button('Copiar enlace').click()).not.toThrow();
97
- await fixture.whenStable();
98
-
99
- expect(messages()).toContain('No se pudo copiar el mensaje.');
100
- });
101
-
102
- it('sin navigator.share no se muestra la acción de compartir nativa', () => {
103
- clearNavigator('share');
104
- fixture.detectChanges();
105
-
106
- const labels = [...root().querySelectorAll('button')].map((el) => el.textContent.trim());
107
- expect(labels).toContain('WhatsApp');
108
- expect(labels).not.toContain('Compartir');
109
- });
110
-
111
- it('con navigator.share se ofrece Compartir y delega en la API nativa', async () => {
112
- const share = vi.fn().mockResolvedValue(undefined);
113
- stubNavigator('share', share);
114
- fixture.detectChanges();
115
-
116
- button('Compartir').click();
117
- await fixture.whenStable();
118
-
119
- expect(share).toHaveBeenCalledWith({ url: URL_UNDER_TEST, text: '' });
120
- expect(button('WhatsApp')).toBeUndefined();
121
- });
122
- });
@@ -1,107 +0,0 @@
1
- import { Component, computed, inject, input } from '@angular/core';
2
-
3
- import { LucideCopy, LucideShare } from '@lucide/angular';
4
-
5
- import { buildShareMessage, writeClipboard } from '../../../core/utils/clipboard';
6
- import { NotificationService } from '../../../core/services/notification.service';
7
- import { Button } from '../button/button';
8
-
9
- /**
10
- * Copia el enlace de un registro al portapapeles y ofrece compartirlo.
11
- *
12
- * Es lo que hace útil `public_base_url` (Etapa 2, S5): con este botón el
13
- * operador entrega el enlace público del pedido.
14
- *
15
- * Tres caminos, todos degradando en vez de romper:
16
- *
17
- * 1. **Copiar** — `navigator.clipboard.writeText`; si el navegador no lo
18
- * tiene (contexto no seguro) cae a un `textarea` + `execCommand('copy')`.
19
- * 2. **Compartir** — `navigator.share`, sólo renderizado si existe
20
- * (`'share' in navigator`; no está en desktop).
21
- * 3. **WhatsApp** — el reemplazo del 2 cuando no hay `navigator.share`:
22
- * `wa.me/?text=` con `shareText` + `url`.
23
- *
24
- * `navigator.share` se llama con `{ url, text }`: no se envía `title` porque
25
- * no hay input dedicado y `label` es el rótulo del botón de copiar, no un
26
- * título de compartir.
27
- */
28
- @Component({
29
- selector: 'app-copy-share',
30
- imports: [Button, LucideCopy, LucideShare],
31
- template: `
32
- <div class="flex flex-wrap items-center gap-2">
33
- <button app-button type="button" variant="outline" (click)="copy()">
34
- <svg lucideCopy size="16" />
35
- {{ label() }}
36
- </button>
37
-
38
- @if (canShare()) {
39
- <button app-button type="button" variant="ghost" (click)="share()">
40
- <svg lucideShare size="16" />
41
- Compartir
42
- </button>
43
- } @else {
44
- <button app-button type="button" variant="ghost" (click)="shareWhatsApp()">
45
- <svg lucideShare size="16" />
46
- WhatsApp
47
- </button>
48
- }
49
- </div>
50
- `,
51
- })
52
- export class CopyShare {
53
- /** Enlace público a copiar/compartir. */
54
- readonly url = input.required<string>();
55
- /** Rótulo del botón de copiar. */
56
- readonly label = input('Copiar enlace');
57
- /** Texto que viaja junto al enlace al compartir (nativo o WhatsApp). */
58
- readonly shareText = input('');
59
-
60
- private readonly notify = inject(NotificationService);
61
-
62
- /**
63
- * El share nativo no existe en desktop: sin él se ofrece WhatsApp.
64
- *
65
- * Se comprueba `typeof … === 'function'` y no `'share' in navigator` como
66
- * dice el plan: el `in` da `true` también para una propiedad propia con
67
- * valor `undefined` (polyfills y stubs hacen exactamente eso), y eso
68
- * montaría un botón que revienta al pulsarlo.
69
- */
70
- protected readonly canShare = computed(
71
- () => typeof navigator !== 'undefined' && typeof navigator.share === 'function',
72
- );
73
-
74
- protected readonly waUrl = computed(() => {
75
- // Mismo texto que copia el botón: `buildShareMessage` evita pegar el
76
- // enlace DOS veces cuando el `shareTextTemplate` ya lo trae escrito.
77
- const text = buildShareMessage(this.url(), this.shareText());
78
- return `https://wa.me/?text=${encodeURIComponent(text)}`;
79
- });
80
-
81
- protected async copy(): Promise<void> {
82
- // El mensaje formateado completo (saludo + ref + enlace + PIN), no sólo
83
- // la URL: es lo que el operador pega en la conversación con el cliente.
84
- const copied = await writeClipboard(buildShareMessage(this.url(), this.shareText()));
85
- if (copied) {
86
- this.notify.success('Mensaje copiado.');
87
- } else {
88
- this.notify.error('No se pudo copiar el mensaje.');
89
- }
90
- }
91
-
92
- protected async share(): Promise<void> {
93
- if (!this.canShare()) {
94
- this.shareWhatsApp();
95
- return;
96
- }
97
- try {
98
- await navigator.share({ url: this.url(), text: this.shareText() });
99
- } catch {
100
- // Cancelar la hoja de compartir dispara AbortError: no es un error.
101
- }
102
- }
103
-
104
- protected shareWhatsApp(): void {
105
- window.open(this.waUrl(), '_blank', 'noopener,noreferrer');
106
- }
107
- }
@@ -1,127 +0,0 @@
1
- import { Component, signal } from '@angular/core';
2
- import { ComponentFixture, TestBed } from '@angular/core/testing';
3
- import { vi } from 'vitest';
4
-
5
- import { Drawer } from './drawer';
6
-
7
- /** Disparador real: el foco tiene que volver a él al cerrar. */
8
- @Component({
9
- template: `
10
- <button type="button" data-testid="trigger">Abrir</button>
11
- <app-drawer [open]="open()" [title]="title()" (close)="open.set(false)">
12
- <button type="button" data-testid="body-action">Dentro del cuerpo</button>
13
- <div drawer-actions>
14
- <button type="button" data-testid="footer-action">Guardar</button>
15
- </div>
16
- </app-drawer>
17
- `,
18
- imports: [Drawer],
19
- })
20
- class DrawerHost {
21
- readonly open = signal(false);
22
- readonly title = signal('Panel de prueba');
23
- }
24
-
25
- describe('Drawer', () => {
26
- let fixture: ComponentFixture<DrawerHost>;
27
-
28
- beforeEach(async () => {
29
- await TestBed.configureTestingModule({ imports: [DrawerHost] }).compileComponents();
30
- fixture = TestBed.createComponent(DrawerHost);
31
- fixture.detectChanges();
32
- });
33
-
34
- const dialog = (): HTMLElement | null => fixture.nativeElement.querySelector('[role="dialog"]');
35
- const trigger = (): HTMLButtonElement =>
36
- fixture.nativeElement.querySelector('[data-testid="trigger"]');
37
-
38
- const open = async (): Promise<void> => {
39
- trigger().focus();
40
- fixture.componentInstance.open.set(true);
41
- fixture.detectChanges();
42
- await fixture.whenStable();
43
- fixture.detectChanges();
44
- };
45
-
46
- it('cerrado no hay diálogo; abierto hay uno con nombre y aria-modal', async () => {
47
- expect(dialog()).toBeNull();
48
-
49
- await open();
50
-
51
- const panel = dialog();
52
- expect(panel).not.toBeNull();
53
- expect(panel!.getAttribute('aria-modal')).toBe('true');
54
- const labelledBy = panel!.getAttribute('aria-labelledby');
55
- expect(document.getElementById(labelledBy!)?.textContent).toContain('Panel de prueba');
56
- });
57
-
58
- it('Escape y el clic en el fondo piden cerrar', async () => {
59
- await open();
60
-
61
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
62
- fixture.detectChanges();
63
- expect(fixture.componentInstance.open()).toBe(false);
64
-
65
- await open();
66
- (fixture.nativeElement.querySelector('.absolute.inset-0') as HTMLElement).click();
67
- fixture.detectChanges();
68
- expect(fixture.componentInstance.open()).toBe(false);
69
- });
70
-
71
- it('el foco entra al abrir y vuelve al disparador al cerrar', async () => {
72
- await open();
73
- expect(dialog()!.contains(document.activeElement)).toBe(true);
74
-
75
- fixture.componentInstance.open.set(false);
76
- fixture.detectChanges();
77
- await fixture.whenStable();
78
-
79
- expect(document.activeElement).toBe(trigger());
80
- });
81
-
82
- it('destruirse con el panel abierto devuelve el foco (no se pierde detrás del fondo)', async () => {
83
- await open();
84
- expect(dialog()!.contains(document.activeElement)).toBe(true);
85
-
86
- // Es lo que pasa cuando el consumidor lo monta bajo un @if y borra el
87
- // nodo sin que la señal pase por `false`.
88
- fixture.componentInstance.open.set(false);
89
- fixture.destroy();
90
-
91
- expect(document.activeElement).toBe(trigger());
92
- });
93
-
94
- it('el Tab queda atrapado: del último salta al primero', async () => {
95
- await open();
96
-
97
- const focusable = Array.from(
98
- dialog()!.querySelectorAll<HTMLElement>('button, [href], input, select, textarea'),
99
- );
100
- const last = focusable[focusable.length - 1];
101
- last.focus();
102
- expect(document.activeElement).toBe(last);
103
-
104
- document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Tab', bubbles: true }));
105
- expect(document.activeElement).toBe(focusable[0]);
106
- });
107
-
108
- it('el Shift+Tab queda atrapado: del primero salta al último', async () => {
109
- await open();
110
-
111
- const focusable = Array.from(
112
- dialog()!.querySelectorAll<HTMLElement>('button, [href], input, select, textarea'),
113
- );
114
- expect(focusable.length).toBeGreaterThan(1);
115
-
116
- // Angular iguala `fullKey` contra la tecla armada con los modificadores:
117
- // sin el binding `keydown.shift.tab` este evento no llega al trap.
118
- const first = focusable[0];
119
- first.focus();
120
- expect(document.activeElement).toBe(first);
121
-
122
- document.dispatchEvent(
123
- new KeyboardEvent('keydown', { key: 'Tab', bubbles: true, shiftKey: true }),
124
- );
125
- expect(document.activeElement).toBe(focusable[focusable.length - 1]);
126
- });
127
- });
@@ -1,172 +0,0 @@
1
- import {
2
- Component,
3
- ElementRef,
4
- OnDestroy,
5
- afterRenderEffect,
6
- computed,
7
- input,
8
- output,
9
- viewChild,
10
- } from '@angular/core';
11
- import { LucideX } from '@lucide/angular';
12
-
13
- import { FOCUSABLE, isVisibleNow } from '../../utils/dialog-focus';
14
-
15
- /** Ancho del panel: `sm` para 1–3 campos, `md`/`lg` para formularios. */
16
- export type DrawerSize = 'sm' | 'md' | 'lg';
17
-
18
- const WIDTHS: Record<DrawerSize, string> = {
19
- sm: 'max-w-sm',
20
- md: 'max-w-lg',
21
- lg: 'max-w-2xl',
22
- };
23
-
24
- let titleCounter = 0;
25
-
26
- /**
27
- * Panel lateral (drawer) — guía §5: «edición rápida de pocos campos: panel
28
- * lateral en lugar de pantalla nueva, para no perder contexto».
29
- *
30
- * Sigue el mismo contrato que `modal`: `role="dialog"` con nombre por
31
- * `aria-labelledby`, foco al abrirse y restaurado al cerrarse, Escape y el
32
- * fondo cierran. Además ATRAPA el Tab dentro del panel: sin eso un modal se
33
- * puede tabular hacia la página de atrás y el foco se pierde detrás del
34
- * fondo oscuro.
35
- *
36
- * Componente tonto: `open`/`title` entran por `input()` y el estado vive
37
- * fuera. El cuerpo se proyecta en el área con scroll y el pie con el
38
- * atributo `drawer-actions`.
39
- */
40
- @Component({
41
- selector: 'app-drawer',
42
- imports: [LucideX],
43
- host: {
44
- '(document:keydown.escape)': 'onEscape()',
45
- // Angular arma la tecla CON sus modificadores y exige igualdad exacta:
46
- // sin esta segunda entrada, Shift+Tab llega como `shift.tab`, no casa con
47
- // `tab` y el foco escapa del panel hacia atrás.
48
- '(document:keydown.tab)': 'onTab($event)',
49
- '(document:keydown.shift.tab)': 'onTab($event)',
50
- },
51
- template: `
52
- @if (open()) {
53
- <div class="fixed inset-0 z-50">
54
- <div class="absolute inset-0 bg-black/50" aria-hidden="true" (click)="close.emit()"></div>
55
- <section
56
- #dialog
57
- role="dialog"
58
- aria-modal="true"
59
- [attr.aria-labelledby]="titleId"
60
- tabindex="-1"
61
- class="absolute inset-y-0 right-0 flex w-full flex-col bg-surface-raised shadow-2xl focus:outline-none"
62
- [class]="widthClass()"
63
- >
64
- <header
65
- class="flex items-start justify-between gap-4 border-b border-neutral/20 px-5 py-4"
66
- >
67
- <div>
68
- <h2 [id]="titleId" class="font-display text-section font-semibold">{{ title() }}</h2>
69
- @if (subtitle(); as subtitleText) {
70
- <p class="mt-1 text-sm text-neutral/70">{{ subtitleText }}</p>
71
- }
72
- </div>
73
- <button
74
- type="button"
75
- class="flex min-h-11 min-w-11 items-center justify-center rounded text-neutral hover:bg-neutral/10 focus-visible:outline-2 focus-visible:outline-primary lg:min-h-0 lg:min-w-0"
76
- [attr.aria-label]="closeLabel()"
77
- (click)="close.emit()"
78
- >
79
- <svg lucideX size="18" />
80
- </button>
81
- </header>
82
-
83
- <div class="flex-1 overflow-y-auto px-5 py-4">
84
- <ng-content />
85
- </div>
86
-
87
- <div class="border-t border-neutral/20 px-5 py-4">
88
- <ng-content select="[drawer-actions]" />
89
- </div>
90
- </section>
91
- </div>
92
- }
93
- `,
94
- })
95
- export class Drawer implements OnDestroy {
96
- readonly open = input(false);
97
- readonly title = input('Editar');
98
- readonly subtitle = input<string | null>(null);
99
- readonly size = input<DrawerSize>('md');
100
- readonly closeLabel = input('Cerrar');
101
- readonly close = output<void>();
102
-
103
- readonly titleId = `drawer-title-${++titleCounter}`;
104
- readonly widthClass = computed(() => WIDTHS[this.size()]);
105
-
106
- private readonly dialogRef = viewChild<ElementRef<HTMLElement>>('dialog');
107
- private lastFocused: HTMLElement | null = null;
108
-
109
- private readonly focusEffect = afterRenderEffect(() => {
110
- const open = this.open();
111
- const dialog = this.dialogRef();
112
- if (open && dialog) {
113
- const active = document.activeElement;
114
- if (active !== dialog.nativeElement && this.lastFocused === null) {
115
- this.lastFocused = active instanceof HTMLElement ? active : null;
116
- }
117
- dialog.nativeElement.focus();
118
- } else if (!open && this.lastFocused !== null) {
119
- this.lastFocused.focus();
120
- this.lastFocused = null;
121
- }
122
- });
123
-
124
- /**
125
- * El consumidor puede destruir el componente con el panel todavía abierto
126
- * (se monta bajo un `@if`): en ese caso el effect nunca ve la transición a
127
- * `false` y el foco se quedaría perdido en el body.
128
- */
129
- ngOnDestroy(): void {
130
- if (this.lastFocused?.isConnected) {
131
- this.lastFocused.focus();
132
- }
133
- this.lastFocused = null;
134
- }
135
-
136
- onEscape(): void {
137
- if (this.open()) {
138
- this.close.emit();
139
- }
140
- }
141
-
142
- /**
143
- * Mantiene el Tab dentro del panel mientras el diálogo está abierto.
144
- * Recibe `Event` porque los host listeners de Angular tipean `$event` así;
145
- * el teclado se relee acá dentro.
146
- */
147
- onTab(rawEvent: Event): void {
148
- const event = rawEvent as KeyboardEvent;
149
- const dialog = this.dialogRef()?.nativeElement;
150
- if (!this.open() || !dialog) {
151
- return;
152
- }
153
- const focusable = Array.from(dialog.querySelectorAll<HTMLElement>(FOCUSABLE)).filter(
154
- (element) => isVisibleNow(element),
155
- );
156
- if (focusable.length === 0) {
157
- event.preventDefault();
158
- dialog.focus();
159
- return;
160
- }
161
- const first = focusable[0];
162
- const last = focusable[focusable.length - 1];
163
- const active = document.activeElement;
164
- if (event.shiftKey && (active === first || active === dialog)) {
165
- event.preventDefault();
166
- last.focus();
167
- } else if (!event.shiftKey && active === last) {
168
- event.preventDefault();
169
- first.focus();
170
- }
171
- }
172
- }
@@ -1,30 +0,0 @@
1
- import { Component, input } from '@angular/core';
2
- import { LucideInbox } from '@lucide/angular';
3
-
4
- /**
5
- * Estado vacío universal (structure.md §6). El contenido proyectado es la
6
- * acción opcional (normalmente un `button[app-button]`).
7
- */
8
- @Component({
9
- selector: 'app-empty-state',
10
- imports: [LucideInbox],
11
- host: { role: 'status' },
12
- template: `
13
- <div
14
- class="flex flex-col items-center gap-2 rounded-xl border border-dashed border-neutral/30 px-6 py-12 text-center"
15
- >
16
- <svg lucideInbox size="32" class="text-neutral" />
17
- <h2 class="font-display text-base font-semibold">{{ title() }}</h2>
18
- @if (message()) {
19
- <p class="text-sm text-neutral">{{ message() }}</p>
20
- }
21
- <div class="mt-2">
22
- <ng-content />
23
- </div>
24
- </div>
25
- `,
26
- })
27
- export class EmptyState {
28
- readonly title = input.required<string>();
29
- readonly message = input('');
30
- }