@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.
- package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +84 -0
- package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +140 -0
- package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +113 -0
- package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +163 -0
- package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +1027 -0
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +278 -0
- package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +107 -0
- package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +101 -0
- package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +141 -0
- package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +88 -0
- package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +107 -0
- package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +140 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +119 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +125 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +152 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +211 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +116 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +111 -0
- package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +122 -0
- package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +107 -0
- package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +109 -0
- package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +45 -0
- package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +50 -0
- package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +24 -0
- package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +27 -0
- package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +408 -0
- package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-form-view-CEe7aZ4m.mjs +4516 -0
- package/fesm2022/tinya-code-gas-admin-ui-form-view-CEe7aZ4m.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +82 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +105 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +65 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +65 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +65 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +59 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +94 -0
- package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +72 -0
- package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +238 -0
- package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +416 -0
- package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +439 -0
- package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +513 -0
- package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-list-DTtCYS4d.mjs +637 -0
- package/fesm2022/tinya-code-gas-admin-ui-list-DTtCYS4d.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +127 -0
- package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +251 -0
- package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +66 -0
- package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +114 -0
- package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +116 -0
- package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +235 -0
- package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +8 -0
- package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +124 -0
- package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +249 -0
- package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +39 -0
- package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +309 -0
- package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-render-registry-B88Pme-y.mjs +576 -0
- package/fesm2022/tinya-code-gas-admin-ui-render-registry-B88Pme-y.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +161 -0
- package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +11 -0
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +685 -0
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-seed-DkVZbxPP.mjs +80 -0
- package/fesm2022/tinya-code-gas-admin-ui-seed-DkVZbxPP.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +296 -0
- package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +119 -0
- package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +158 -0
- package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +26 -0
- package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +205 -0
- package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +432 -0
- package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +36 -0
- package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +107 -0
- package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +223 -0
- package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +107 -0
- package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +60 -0
- package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +141 -0
- package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +1 -0
- package/fesm2022/tinya-code-gas-admin-ui.mjs +4738 -0
- package/fesm2022/tinya-code-gas-admin-ui.mjs.map +1 -0
- package/package.json +15 -3
- package/types/tinya-code-gas-admin-ui.d.ts +3634 -0
- package/ng-package.json +0 -7
- package/src/lib/actions/action-registry.spec.ts +0 -52
- package/src/lib/actions/action-registry.ts +0 -126
- package/src/lib/actions/action-service.spec.ts +0 -215
- package/src/lib/actions/action-service.ts +0 -87
- package/src/lib/actions/descriptors.spec.ts +0 -155
- package/src/lib/actions/descriptors.ts +0 -154
- package/src/lib/columns/column-cell.ts +0 -33
- package/src/lib/columns/column-defaults.spec.ts +0 -99
- package/src/lib/columns/column-defaults.ts +0 -84
- package/src/lib/columns/column-format.spec.ts +0 -274
- package/src/lib/columns/descriptors.ts +0 -113
- package/src/lib/columns/format-badge.ts +0 -51
- package/src/lib/columns/format-boolean.ts +0 -58
- package/src/lib/columns/format-currency.ts +0 -30
- package/src/lib/columns/format-date.ts +0 -35
- package/src/lib/columns/format-number.ts +0 -32
- package/src/lib/columns/format-text.ts +0 -23
- package/src/lib/columns/format-thumbnail.ts +0 -63
- package/src/lib/core/adapters/firebase-auth.adapter.ts +0 -6
- package/src/lib/core/guards/auth.guard.spec.ts +0 -88
- package/src/lib/core/guards/auth.guard.ts +0 -48
- package/src/lib/core/guards/unsaved-changes.guard.ts +0 -16
- package/src/lib/core/interceptors/api-error.interceptor.spec.ts +0 -182
- package/src/lib/core/interceptors/api-error.interceptor.ts +0 -87
- package/src/lib/core/models/api.model.ts +0 -104
- package/src/lib/core/models/schema-descriptions.ts +0 -118
- package/src/lib/core/models/schema.model.ts +0 -1097
- package/src/lib/core/registry/composition-validator.spec.ts +0 -358
- package/src/lib/core/registry/composition-validator.ts +0 -270
- package/src/lib/core/registry/field-validators.spec.ts +0 -199
- package/src/lib/core/registry/field-validators.ts +0 -440
- package/src/lib/core/registry/render-host.spec.ts +0 -208
- package/src/lib/core/registry/render-host.ts +0 -646
- package/src/lib/core/registry/render-registry.spec.ts +0 -96
- package/src/lib/core/registry/render-registry.ts +0 -312
- package/src/lib/core/registry/schema-catalog.ts +0 -156
- package/src/lib/core/services/api.service.spec.ts +0 -1142
- package/src/lib/core/services/api.service.ts +0 -371
- package/src/lib/core/services/auth.service.spec.ts +0 -653
- package/src/lib/core/services/auth.service.ts +0 -503
- package/src/lib/core/services/config.service.spec.ts +0 -121
- package/src/lib/core/services/config.service.ts +0 -119
- package/src/lib/core/services/data-source.service.spec.ts +0 -179
- package/src/lib/core/services/data-source.service.ts +0 -142
- package/src/lib/core/services/error-mapper.service.spec.ts +0 -50
- package/src/lib/core/services/error-mapper.service.ts +0 -49
- package/src/lib/core/services/notification.service.ts +0 -73
- package/src/lib/core/services/relation-drawer.service.ts +0 -41
- package/src/lib/core/services/theme.service.spec.ts +0 -106
- package/src/lib/core/services/theme.service.ts +0 -86
- package/src/lib/core/services/transport.spec.ts +0 -198
- package/src/lib/core/services/transport.ts +0 -164
- package/src/lib/core/services/upload.service.ts +0 -81
- package/src/lib/core/testing/categories.schema.ts +0 -125
- package/src/lib/core/testing/orders.schema.ts +0 -372
- package/src/lib/core/testing/products.schema.ts +0 -258
- package/src/lib/core/testing/site.schema.ts +0 -195
- package/src/lib/core/testing/test-catalog.ts +0 -21
- package/src/lib/core/tokens.ts +0 -112
- package/src/lib/core/utils/clipboard.spec.ts +0 -55
- package/src/lib/core/utils/clipboard.ts +0 -90
- package/src/lib/core/utils/context-resolver.spec.ts +0 -95
- package/src/lib/core/utils/context-resolver.ts +0 -101
- package/src/lib/core/utils/response-unwrapper.spec.ts +0 -70
- package/src/lib/core/utils/response-unwrapper.ts +0 -103
- package/src/lib/core/utils/source-options.spec.ts +0 -96
- package/src/lib/core/utils/source-options.ts +0 -54
- package/src/lib/core/utils/template-interpolator.spec.ts +0 -59
- package/src/lib/core/utils/template-interpolator.ts +0 -56
- package/src/lib/core/view-navigation/view-navigation.service.spec.ts +0 -207
- package/src/lib/core/view-navigation/view-navigation.service.ts +0 -132
- package/src/lib/fields/boolean/boolean.descriptor.ts +0 -11
- package/src/lib/fields/boolean/boolean.ts +0 -40
- package/src/lib/fields/color/color.descriptor.ts +0 -12
- package/src/lib/fields/color/color.ts +0 -34
- package/src/lib/fields/currency/currency.descriptor.ts +0 -12
- package/src/lib/fields/currency/currency.ts +0 -67
- package/src/lib/fields/date/date.descriptor.ts +0 -12
- package/src/lib/fields/date/date.ts +0 -36
- package/src/lib/fields/descriptors.ts +0 -122
- package/src/lib/fields/display/display-badge.ts +0 -65
- package/src/lib/fields/display/display-date.ts +0 -39
- package/src/lib/fields/display/display-defaults.ts +0 -84
- package/src/lib/fields/display/display-file.ts +0 -41
- package/src/lib/fields/display/display-image.ts +0 -54
- package/src/lib/fields/display/display-list.ts +0 -107
- package/src/lib/fields/display/display-number.ts +0 -41
- package/src/lib/fields/display/display-text.ts +0 -42
- package/src/lib/fields/display/display.spec.ts +0 -242
- package/src/lib/fields/dropdown/dropdown.descriptor.ts +0 -12
- package/src/lib/fields/dropdown/dropdown.ts +0 -45
- package/src/lib/fields/email/email.descriptor.ts +0 -12
- package/src/lib/fields/email/email.ts +0 -28
- package/src/lib/fields/field-aria.ts +0 -60
- package/src/lib/fields/field-host/field-host.spec.ts +0 -118
- package/src/lib/fields/field-host/field-host.ts +0 -47
- package/src/lib/fields/field-node.ts +0 -88
- package/src/lib/fields/file/file.descriptor.ts +0 -14
- package/src/lib/fields/file/file.spec.ts +0 -134
- package/src/lib/fields/file/file.ts +0 -281
- package/src/lib/fields/group/group.descriptor.ts +0 -21
- package/src/lib/fields/group/group.ts +0 -110
- package/src/lib/fields/image/image.descriptor.ts +0 -12
- package/src/lib/fields/image/image.spec.ts +0 -117
- package/src/lib/fields/image/image.ts +0 -281
- package/src/lib/fields/key-value/key-value.descriptor.ts +0 -11
- package/src/lib/fields/key-value/key-value.ts +0 -283
- package/src/lib/fields/list/list.descriptor.ts +0 -21
- package/src/lib/fields/list/list.ts +0 -376
- package/src/lib/fields/multiselect/multiselect.descriptor.ts +0 -11
- package/src/lib/fields/multiselect/multiselect.spec.ts +0 -142
- package/src/lib/fields/multiselect/multiselect.ts +0 -152
- package/src/lib/fields/number/number.descriptor.ts +0 -12
- package/src/lib/fields/number/number.ts +0 -40
- package/src/lib/fields/phone/phone.descriptor.ts +0 -12
- package/src/lib/fields/phone/phone.ts +0 -46
- package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +0 -12
- package/src/lib/fields/readonly-text/readonly-text.ts +0 -58
- package/src/lib/fields/relation/relation.descriptor.ts +0 -12
- package/src/lib/fields/relation/relation.spec.ts +0 -193
- package/src/lib/fields/relation/relation.ts +0 -224
- package/src/lib/fields/rich-select/rich-select.descriptor.ts +0 -12
- package/src/lib/fields/rich-select/rich-select.ts +0 -175
- package/src/lib/fields/select/radio-mode.ts +0 -25
- package/src/lib/fields/select/select.descriptor.ts +0 -12
- package/src/lib/fields/select/select.spec.ts +0 -162
- package/src/lib/fields/select/select.ts +0 -210
- package/src/lib/fields/slug/slug.descriptor.ts +0 -19
- package/src/lib/fields/slug/slug.ts +0 -67
- package/src/lib/fields/string-list/string-list.descriptor.ts +0 -11
- package/src/lib/fields/string-list/string-list.ts +0 -222
- package/src/lib/fields/text/text.descriptor.ts +0 -12
- package/src/lib/fields/text/text.ts +0 -35
- package/src/lib/fields/textarea/textarea.descriptor.ts +0 -12
- package/src/lib/fields/textarea/textarea.spec.ts +0 -117
- package/src/lib/fields/textarea/textarea.ts +0 -126
- package/src/lib/fields/time/time.descriptor.ts +0 -12
- package/src/lib/fields/time/time.ts +0 -28
- package/src/lib/fields/unknown-render/unknown-render.ts +0 -49
- package/src/lib/fields/url/url.descriptor.ts +0 -12
- package/src/lib/fields/url/url.ts +0 -53
- package/src/lib/gas-admin-ui.spec.ts +0 -21
- package/src/lib/gas-admin-ui.ts +0 -9
- package/src/lib/layouts/descriptors.ts +0 -9
- package/src/lib/pages/dashboard/dashboard.ts +0 -167
- package/src/lib/pages/detail-view/detail-view.spec.ts +0 -333
- package/src/lib/pages/detail-view/detail-view.ts +0 -570
- package/src/lib/pages/drawer-form/drawer-form.ts +0 -228
- package/src/lib/pages/form-view/form-model.ts +0 -88
- package/src/lib/pages/form-view/form-schema.ts +0 -255
- package/src/lib/pages/form-view/form-view.ts +0 -1622
- package/src/lib/pages/list-view/list-view.formats.spec.ts +0 -214
- package/src/lib/pages/list-view/list-view.ts +0 -882
- package/src/lib/pages/login/login.spec.ts +0 -109
- package/src/lib/pages/login/login.ts +0 -227
- package/src/lib/pages/resource-page/resource-page.ts +0 -234
- package/src/lib/shared/components/badge/badge.ts +0 -35
- package/src/lib/shared/components/button/button.ts +0 -65
- package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +0 -48
- package/src/lib/shared/components/copy-button/copy-button.ts +0 -56
- package/src/lib/shared/components/copy-share/copy-share.spec.ts +0 -122
- package/src/lib/shared/components/copy-share/copy-share.ts +0 -107
- package/src/lib/shared/components/drawer/drawer.spec.ts +0 -127
- package/src/lib/shared/components/drawer/drawer.ts +0 -172
- package/src/lib/shared/components/empty-state/empty-state.ts +0 -30
- package/src/lib/shared/components/modal/modal.spec.ts +0 -149
- package/src/lib/shared/components/modal/modal.ts +0 -145
- package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +0 -165
- package/src/lib/shared/components/post-create-modal/post-create-modal.ts +0 -159
- package/src/lib/shared/components/skeleton/skeleton.ts +0 -31
- package/src/lib/shared/components/toast/toast.ts +0 -58
- package/src/lib/shared/utils/col-span.ts +0 -25
- package/src/lib/shared/utils/condition-evaluator.ts +0 -53
- package/src/lib/shared/utils/depends-on.ts +0 -48
- package/src/lib/shared/utils/dialog-focus.ts +0 -28
- package/src/lib/shared/utils/field-path.ts +0 -49
- package/src/lib/shared/utils/gap-sorting.ts +0 -144
- package/src/lib/shared/utils/seed.spec.ts +0 -64
- package/src/lib/shared/utils/seed.ts +0 -149
- package/src/lib/shared/utils/slugify.ts +0 -13
- package/src/lib/shared/utils/validators.ts +0 -41
- package/src/lib/shell/layout/layout.ts +0 -63
- package/src/lib/shell/shell.routes.spec.ts +0 -59
- package/src/lib/shell/shell.routes.ts +0 -122
- package/src/lib/shell/sidebar/sidebar.ts +0 -139
- package/src/lib/shell/theme-toggle/theme-toggle.ts +0 -74
- package/src/lib/views/calendar-view/calendar-model.ts +0 -426
- package/src/lib/views/calendar-view/calendar-view.spec.ts +0 -557
- package/src/lib/views/calendar-view/calendar-view.ts +0 -568
- package/src/lib/views/composer/composer-host.spec.ts +0 -301
- package/src/lib/views/composer/composer-host.ts +0 -248
- package/src/lib/views/composer/composer-imports.spec.ts +0 -54
- package/src/lib/views/composer/view-context.spec.ts +0 -117
- package/src/lib/views/composer/view-context.ts +0 -109
- package/src/lib/views/composer/view-slot.spec.ts +0 -130
- package/src/lib/views/composer/view-slot.ts +0 -178
- package/src/lib/views/descriptors.ts +0 -46
- package/src/lib/views/kanban-view/kanban-model.ts +0 -173
- package/src/lib/views/kanban-view/kanban-view.spec.ts +0 -540
- package/src/lib/views/kanban-view/kanban-view.ts +0 -327
- package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +0 -17
- package/src/lib/widgets/bar-chart/bar-chart.spec.ts +0 -66
- package/src/lib/widgets/bar-chart/bar-chart.ts +0 -76
- package/src/lib/widgets/chart-line/chart-line.descriptor.ts +0 -17
- package/src/lib/widgets/chart-line/chart-line.spec.ts +0 -113
- package/src/lib/widgets/chart-line/chart-line.ts +0 -219
- package/src/lib/widgets/descriptors.ts +0 -29
- package/src/lib/widgets/metric-card/metric-card.descriptor.ts +0 -41
- package/src/lib/widgets/metric-card/metric-card.spec.ts +0 -52
- package/src/lib/widgets/metric-card/metric-card.ts +0 -48
- package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +0 -13
- package/src/lib/widgets/quick-actions/quick-actions.spec.ts +0 -61
- package/src/lib/widgets/quick-actions/quick-actions.ts +0 -100
- package/src/lib/widgets/record-list/record-list.descriptor.ts +0 -16
- package/src/lib/widgets/record-list/record-list.spec.ts +0 -79
- package/src/lib/widgets/record-list/record-list.ts +0 -115
- package/src/lib/widgets/series.spec.ts +0 -42
- package/src/lib/widgets/series.ts +0 -56
- package/src/lib/widgets/status-progress/status-progress.descriptor.ts +0 -19
- package/src/lib/widgets/status-progress/status-progress.spec.ts +0 -63
- package/src/lib/widgets/status-progress/status-progress.ts +0 -104
- package/src/lib/widgets/widget-host/widget-host.spec.ts +0 -179
- package/src/lib/widgets/widget-host/widget-host.ts +0 -99
- package/src/public-api.ts +0 -104
- package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
- package/tsconfig.lib.json +0 -13
- package/tsconfig.lib.prod.json +0 -11
- package/tsconfig.spec.json +0 -10
|
@@ -1,301 +0,0 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import { TestBed } from '@angular/core/testing';
|
|
3
|
-
import { ActivatedRoute } from '@angular/router';
|
|
4
|
-
import { afterEach, describe, expect, it } from 'vitest';
|
|
5
|
-
import { of } from 'rxjs';
|
|
6
|
-
|
|
7
|
-
import type { ResourceSchema, UiViewDescriptor, UiViewTabs } from '../../core/models/schema.model';
|
|
8
|
-
import { register, unregister } from '../../core/registry/render-registry';
|
|
9
|
-
import { ComposerHost } from './composer-host';
|
|
10
|
-
|
|
11
|
-
/** Hoja ligera para los tests (se registra y se desregistra por test). */
|
|
12
|
-
@Component({
|
|
13
|
-
standalone: true,
|
|
14
|
-
selector: 'app-hoja-h',
|
|
15
|
-
template: `<p data-hoja>{{ etiqueta }}</p>`,
|
|
16
|
-
})
|
|
17
|
-
class HojaH {
|
|
18
|
-
readonly etiqueta: string;
|
|
19
|
-
constructor(route: ActivatedRoute) {
|
|
20
|
-
let v: Record<string, unknown> = {};
|
|
21
|
-
route.data.subscribe((d) => (v = d as Record<string, unknown>));
|
|
22
|
-
this.etiqueta = String(v['uiView'] ?? '');
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
function tabs(children: UiViewTabs['config']['children'], initial?: string): UiViewTabs {
|
|
27
|
-
return { type: 'tabs', config: { children, ...(initial ? { initial } : {}) } };
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
function base(): ResourceSchema {
|
|
31
|
-
return {
|
|
32
|
-
id: 'demo',
|
|
33
|
-
label: 'Demo',
|
|
34
|
-
labelPlural: 'Demos',
|
|
35
|
-
kind: 'collection',
|
|
36
|
-
endpoint: {},
|
|
37
|
-
keyField: 'id',
|
|
38
|
-
titleField: 'name',
|
|
39
|
-
listColumns: [],
|
|
40
|
-
fields: [],
|
|
41
|
-
uiViews: {},
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function esquemaCon(vista: UiViewDescriptor): ResourceSchema {
|
|
46
|
-
return { ...base(), uiViews: { panel: vista } };
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
type Fixture = ReturnType<TestBed['createComponent']>;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* Dos ciclos estables: el primero resuelve el `load()` del slot activo, el
|
|
53
|
-
* segundo monta su componente; el tercero cubre un nivel de anidación.
|
|
54
|
-
*/
|
|
55
|
-
async function flush(fixture: Fixture, cond?: () => boolean): Promise<Fixture> {
|
|
56
|
-
const ciclosMax = cond === undefined ? 4 : 30;
|
|
57
|
-
for (let ciclo = 0; ciclo < ciclosMax; ciclo += 1) {
|
|
58
|
-
if (cond?.()) break;
|
|
59
|
-
// Ceder la macrotarea: el `import()` dinámico de un descriptor no es
|
|
60
|
-
// microtask puro y `whenStable` en TestBed no lo espera. Con `cond`
|
|
61
|
-
// además se espera hasta que el DOM realmente avance (la suite completa
|
|
62
|
-
// satura el worker y el import tarda más ciclos).
|
|
63
|
-
await new Promise((resolve) => setTimeout(resolve, 0));
|
|
64
|
-
await fixture.whenStable();
|
|
65
|
-
fixture.detectChanges();
|
|
66
|
-
}
|
|
67
|
-
return fixture;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
async function montar(schema: ResourceSchema, uiView = 'panel'): Promise<Fixture> {
|
|
71
|
-
const datos = { schema, resourceId: schema.id, uiView, viewType: 'tabs' };
|
|
72
|
-
TestBed.configureTestingModule({
|
|
73
|
-
providers: [{ provide: ActivatedRoute, useValue: { data: of(datos) } }],
|
|
74
|
-
});
|
|
75
|
-
const fixture = TestBed.createComponent(ComposerHost);
|
|
76
|
-
fixture.detectChanges();
|
|
77
|
-
await flush(fixture);
|
|
78
|
-
return fixture;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
describe('ComposerHost', () => {
|
|
82
|
-
afterEach(() => unregister('view', 'hoja-h'));
|
|
83
|
-
|
|
84
|
-
it('renderiza los hijos en orden declarado con tablist accesible', async () => {
|
|
85
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
86
|
-
const fixture = await montar(
|
|
87
|
-
esquemaCon(
|
|
88
|
-
tabs([
|
|
89
|
-
{ id: 'a', label: 'Uno', type: 'hoja-h' },
|
|
90
|
-
{ id: 'b', label: 'Dos', type: 'hoja-h' },
|
|
91
|
-
{ id: 'c', label: 'Tres', type: 'hoja-h' },
|
|
92
|
-
]),
|
|
93
|
-
),
|
|
94
|
-
);
|
|
95
|
-
const tabsDom = [...fixture.nativeElement.querySelectorAll('[role="tab"]')];
|
|
96
|
-
expect(tabsDom.map((t) => t.textContent?.trim())).toEqual(['Uno', 'Dos', 'Tres']);
|
|
97
|
-
expect(fixture.nativeElement.querySelector('[role="tablist"]')).toBeTruthy();
|
|
98
|
-
// el primer hijo está activo por defecto: sólo su panel está montado
|
|
99
|
-
expect(fixture.nativeElement.querySelectorAll('[role="tabpanel"]').length).toBe(1);
|
|
100
|
-
expect(tabsDom[0].getAttribute('aria-selected')).toBe('true');
|
|
101
|
-
expect(tabsDom[0].getAttribute('tabindex')).toBe('0');
|
|
102
|
-
expect(tabsDom[1].getAttribute('tabindex')).toBe('-1');
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
it('respeta `initial` para la pestaña activa al montar', async () => {
|
|
106
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
107
|
-
const fixture = await montar(
|
|
108
|
-
esquemaCon(
|
|
109
|
-
tabs(
|
|
110
|
-
[
|
|
111
|
-
{ id: 'a', label: 'Uno', type: 'hoja-h' },
|
|
112
|
-
{ id: 'b', label: 'Dos', type: 'hoja-h' },
|
|
113
|
-
],
|
|
114
|
-
'b',
|
|
115
|
-
),
|
|
116
|
-
),
|
|
117
|
-
);
|
|
118
|
-
const selected = [...fixture.nativeElement.querySelectorAll('[role="tab"]')].filter(
|
|
119
|
-
(t) => t.getAttribute('aria-selected') === 'true',
|
|
120
|
-
);
|
|
121
|
-
expect(selected.map((t) => t.textContent?.trim())).toEqual(['Dos']);
|
|
122
|
-
expect(fixture.nativeElement.querySelector('[role="tabpanel"]')?.textContent).toContain(
|
|
123
|
-
'panel/b',
|
|
124
|
-
);
|
|
125
|
-
});
|
|
126
|
-
|
|
127
|
-
it('clic en pestaña activa el panel y destruye el no retenido', async () => {
|
|
128
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
129
|
-
const fixture = await montar(
|
|
130
|
-
esquemaCon(
|
|
131
|
-
tabs([
|
|
132
|
-
{ id: 'a', label: 'Uno', type: 'hoja-h' },
|
|
133
|
-
{ id: 'b', label: 'Dos', type: 'hoja-h' },
|
|
134
|
-
]),
|
|
135
|
-
),
|
|
136
|
-
);
|
|
137
|
-
const panels = () => fixture.nativeElement.querySelectorAll('[role="tabpanel"]');
|
|
138
|
-
expect(panels().length).toBe(1);
|
|
139
|
-
expect(panels()[0].textContent).toContain('panel/a');
|
|
140
|
-
[...fixture.nativeElement.querySelectorAll('[role="tab"]')][1].click();
|
|
141
|
-
await flush(fixture);
|
|
142
|
-
expect(panels().length).toBe(1);
|
|
143
|
-
expect(panels()[0].textContent).toContain('panel/b');
|
|
144
|
-
// no retenido: el panel de 'a' ya no existe en el DOM
|
|
145
|
-
expect(panels()[0].textContent).not.toContain('panel/a');
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
it('retain:true conserva el panel montado pero oculto tras cambiar de pestaña', async () => {
|
|
149
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
150
|
-
const fixture = await montar(
|
|
151
|
-
esquemaCon(
|
|
152
|
-
tabs([
|
|
153
|
-
{ id: 'a', label: 'Uno', type: 'hoja-h', retain: true },
|
|
154
|
-
{ id: 'b', label: 'Dos', type: 'hoja-h' },
|
|
155
|
-
]),
|
|
156
|
-
),
|
|
157
|
-
);
|
|
158
|
-
[...fixture.nativeElement.querySelectorAll('[role="tab"]')][1].click();
|
|
159
|
-
await flush(fixture);
|
|
160
|
-
const panels = [...fixture.nativeElement.querySelectorAll('[role="tabpanel"]')];
|
|
161
|
-
expect(panels.length).toBe(2);
|
|
162
|
-
const oculto = panels.find((p) => p.textContent?.includes('panel/a'));
|
|
163
|
-
expect(oculto).toBeTruthy();
|
|
164
|
-
expect(oculto!.classList.contains('hidden')).toBe(true);
|
|
165
|
-
const visible = panels.find((p) => p.textContent?.includes('panel/b'));
|
|
166
|
-
expect(visible!.classList.contains('hidden')).toBe(false);
|
|
167
|
-
});
|
|
168
|
-
|
|
169
|
-
it('flechas/Home/End mueven la selección y el tabindex (roving)', async () => {
|
|
170
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
171
|
-
const fixture = await montar(
|
|
172
|
-
esquemaCon(
|
|
173
|
-
tabs([
|
|
174
|
-
{ id: 'a', label: 'Uno', type: 'hoja-h' },
|
|
175
|
-
{ id: 'b', label: 'Dos', type: 'hoja-h' },
|
|
176
|
-
{ id: 'c', label: 'Tres', type: 'hoja-h' },
|
|
177
|
-
]),
|
|
178
|
-
),
|
|
179
|
-
);
|
|
180
|
-
const tablist = fixture.nativeElement.querySelector('[role="tablist"]');
|
|
181
|
-
const tabsDom = [...tablist.querySelectorAll('[role="tab"]')];
|
|
182
|
-
const tecla = (key: string) => {
|
|
183
|
-
tablist.dispatchEvent(new KeyboardEvent('keydown', { key, bubbles: true }));
|
|
184
|
-
fixture.detectChanges();
|
|
185
|
-
};
|
|
186
|
-
tecla('ArrowRight');
|
|
187
|
-
expect(tabsDom[1].getAttribute('aria-selected')).toBe('true');
|
|
188
|
-
expect(tabsDom[1].getAttribute('tabindex')).toBe('0');
|
|
189
|
-
expect(tabsDom[0].getAttribute('tabindex')).toBe('-1');
|
|
190
|
-
tecla('End');
|
|
191
|
-
expect(tabsDom[2].getAttribute('aria-selected')).toBe('true');
|
|
192
|
-
tecla('Home');
|
|
193
|
-
expect(tabsDom[0].getAttribute('aria-selected')).toBe('true');
|
|
194
|
-
tecla('ArrowLeft'); // desde el primero ⇒ envuelve al último
|
|
195
|
-
expect(tabsDom[2].getAttribute('aria-selected')).toBe('true');
|
|
196
|
-
});
|
|
197
|
-
|
|
198
|
-
it('config inválida (no-tabs) ⇒ estado de error, sin montar hijos', async () => {
|
|
199
|
-
const fixture = await montar(esquemaCon({ type: 'list' }));
|
|
200
|
-
expect(fixture.nativeElement.textContent).toContain('inválida');
|
|
201
|
-
expect(fixture.nativeElement.querySelector('[role="tablist"]')).toBeFalsy();
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
it('ciclo de composición ⇒ error validado antes del render (D2)', async () => {
|
|
205
|
-
const conRef: UiViewTabs = {
|
|
206
|
-
type: 'tabs',
|
|
207
|
-
config: { children: [{ id: 'loop', label: 'Loop', ref: 'panel' }] },
|
|
208
|
-
};
|
|
209
|
-
const fixture = await montar(esquemaCon(conRef));
|
|
210
|
-
expect(fixture.nativeElement.textContent).toContain('ciclo');
|
|
211
|
-
expect(fixture.nativeElement.querySelector('[role="tablist"]')).toBeFalsy();
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
it('profundidad sobre el límite ⇒ error validado antes del render (D2)', async () => {
|
|
215
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
216
|
-
const schema: ResourceSchema = {
|
|
217
|
-
...base(),
|
|
218
|
-
uiViews: {
|
|
219
|
-
a: tabs([{ id: 'n1', label: 'N1', ref: 'b' }]),
|
|
220
|
-
b: tabs([{ id: 'n2', label: 'N2', ref: 'c' }]),
|
|
221
|
-
c: tabs([{ id: 'n3', label: 'N3', ref: 'd' }]),
|
|
222
|
-
d: tabs([{ id: 'n4', label: 'N4', type: 'hoja-h' }]),
|
|
223
|
-
},
|
|
224
|
-
};
|
|
225
|
-
const fixture = await montar(schema, 'a');
|
|
226
|
-
expect(fixture.nativeElement.textContent).toContain('profundidad');
|
|
227
|
-
expect(fixture.nativeElement.querySelector('[role="tablist"]')).toBeFalsy();
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
it('visibleWhen sin record evalúa contra vacío: empty pasa, eq no', async () => {
|
|
231
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
232
|
-
const fixture = await montar(
|
|
233
|
-
esquemaCon(
|
|
234
|
-
tabs([
|
|
235
|
-
{ id: 'a', label: 'Siempre', type: 'hoja-h' },
|
|
236
|
-
{
|
|
237
|
-
id: 'b',
|
|
238
|
-
label: 'Condicional',
|
|
239
|
-
type: 'hoja-h',
|
|
240
|
-
visibleWhen: [{ field: 'estado', operator: 'eq', value: 'activo' }],
|
|
241
|
-
},
|
|
242
|
-
{
|
|
243
|
-
id: 'c',
|
|
244
|
-
label: 'Sin filtro',
|
|
245
|
-
type: 'hoja-h',
|
|
246
|
-
visibleWhen: [{ field: 'estado', operator: 'empty' }],
|
|
247
|
-
},
|
|
248
|
-
]),
|
|
249
|
-
),
|
|
250
|
-
);
|
|
251
|
-
const labels = [...fixture.nativeElement.querySelectorAll('[role="tab"]')].map((t) =>
|
|
252
|
-
t.textContent?.trim(),
|
|
253
|
-
);
|
|
254
|
-
expect(labels).toEqual(['Siempre', 'Sin filtro']);
|
|
255
|
-
});
|
|
256
|
-
|
|
257
|
-
it('si la pestaña activa queda oculta, cae visiblemente a la primera visible', async () => {
|
|
258
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
259
|
-
const fixture = await montar(
|
|
260
|
-
esquemaCon(
|
|
261
|
-
tabs([
|
|
262
|
-
{
|
|
263
|
-
id: 'oculta',
|
|
264
|
-
label: 'Oculta',
|
|
265
|
-
type: 'hoja-h',
|
|
266
|
-
visibleWhen: [{ field: 'estado', operator: 'eq', value: 'activo' }],
|
|
267
|
-
},
|
|
268
|
-
{ id: 'visible', label: 'Visible', type: 'hoja-h' },
|
|
269
|
-
]),
|
|
270
|
-
),
|
|
271
|
-
);
|
|
272
|
-
const tabsDom = [...fixture.nativeElement.querySelectorAll('[role="tab"]')];
|
|
273
|
-
expect(tabsDom.map((t) => t.textContent?.trim())).toEqual(['Visible']);
|
|
274
|
-
expect(tabsDom[0].getAttribute('aria-selected')).toBe('true');
|
|
275
|
-
expect(fixture.nativeElement.querySelector('[role="tabpanel"]')?.textContent).toContain(
|
|
276
|
-
'panel/visible',
|
|
277
|
-
);
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
it('anida tabs inline: el hijo contenedor monta sus propios hijos', async () => {
|
|
281
|
-
register('view', { kind: 'view', type: 'hoja-h', since: '9.9', load: async () => HojaH });
|
|
282
|
-
const anidado: UiViewTabs = tabs([
|
|
283
|
-
{
|
|
284
|
-
id: 'hijo',
|
|
285
|
-
label: 'Hijo',
|
|
286
|
-
type: 'tabs',
|
|
287
|
-
config: { children: [{ id: 'nieto', label: 'Nieto', type: 'hoja-h' }] },
|
|
288
|
-
},
|
|
289
|
-
]);
|
|
290
|
-
const fixture = await montar(esquemaCon(anidado));
|
|
291
|
-
await flush(fixture, () => fixture.nativeElement.querySelectorAll('[role="tab"]').length >= 2);
|
|
292
|
-
const labels = [...fixture.nativeElement.querySelectorAll('[role="tab"]')].map((t) =>
|
|
293
|
-
t.textContent?.trim(),
|
|
294
|
-
);
|
|
295
|
-
expect(labels).toEqual(['Hijo', 'Nieto']);
|
|
296
|
-
// el nieto vive bajo instanceId de cadena: panel/hijo (parentInstanceId)
|
|
297
|
-
expect(fixture.nativeElement.querySelector('[role="tabpanel"]')?.textContent).toContain(
|
|
298
|
-
'panel/hijo',
|
|
299
|
-
);
|
|
300
|
-
});
|
|
301
|
-
});
|
|
@@ -1,248 +0,0 @@
|
|
|
1
|
-
import { ChangeDetectionStrategy, Component, computed, inject, signal } from '@angular/core';
|
|
2
|
-
import { ActivatedRoute } from '@angular/router';
|
|
3
|
-
import { toSignal } from '@angular/core/rxjs-interop';
|
|
4
|
-
|
|
5
|
-
import type { ResourceSchema, UiViewTabs, ViewChildNode } from '../../core/models/schema.model';
|
|
6
|
-
import { validateComposition } from '../../core/registry/composition-validator';
|
|
7
|
-
import { getSchema } from '../../core/registry/schema-catalog';
|
|
8
|
-
import { EmptyState } from '../../shared/components/empty-state/empty-state';
|
|
9
|
-
import { evaluateCondition } from '../../shared/utils/condition-evaluator';
|
|
10
|
-
import { ViewSlot } from './view-slot';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* R9.9 · slice 3 — Host genérico de composición (`uiView` tipo `tabs`).
|
|
14
|
-
*
|
|
15
|
-
* Un SOLO componente recursivo renderiza cualquier composición declarada:
|
|
16
|
-
* resuelve cada hijo por `(kind:'view', type)` dentro del `ViewSlot` (D2,
|
|
17
|
-
* sin `switch` de dominio y sin importar hojas), monta la validación del
|
|
18
|
-
* schema ANTES del primer render (fail-loud) y maneja el tablist accesible
|
|
19
|
-
* con roving tabindex (R9.9.5).
|
|
20
|
-
*
|
|
21
|
-
* Decisiones R9.9.5:
|
|
22
|
-
* - Retención: un hijo `retain:true` visitado queda MONTADO pero oculto
|
|
23
|
-
* (`hidden`); sin `retain` se destruye al cambiar de pestaña.
|
|
24
|
-
* - Pestaña activa LOCAL por componente: no hay estado compartido entre
|
|
25
|
-
* instancias de tabs anidadas (documentado en los docs de slice 5).
|
|
26
|
-
* - `visibleWhen` se evalúa sin record (el contenedor no es un formulario):
|
|
27
|
-
* contra registro vacío, TODAS las condiciones deben pasar (AND).
|
|
28
|
-
* Si la pestaña activa queda oculta, la selección cae visiblemente a la
|
|
29
|
-
* primera visible — nunca se pierde en silencio.
|
|
30
|
-
*/
|
|
31
|
-
@Component({
|
|
32
|
-
selector: 'app-composer-host',
|
|
33
|
-
standalone: true,
|
|
34
|
-
imports: [ViewSlot, EmptyState],
|
|
35
|
-
changeDetection: ChangeDetectionStrategy.OnPush,
|
|
36
|
-
template: `
|
|
37
|
-
@if (error() !== null) {
|
|
38
|
-
<app-empty-state title="Composición no disponible" [message]="error()!" />
|
|
39
|
-
} @else if (visibles().length === 0) {
|
|
40
|
-
<app-empty-state
|
|
41
|
-
title="Sin secciones disponibles"
|
|
42
|
-
message="La vista no declara secciones visibles."
|
|
43
|
-
/>
|
|
44
|
-
} @else {
|
|
45
|
-
<div
|
|
46
|
-
role="tablist"
|
|
47
|
-
class="flex flex-wrap gap-1 border-b border-neutral/20 px-1"
|
|
48
|
-
[attr.aria-label]="etiqueta()"
|
|
49
|
-
(keydown)="onKeydown($event)"
|
|
50
|
-
>
|
|
51
|
-
@for (h of visibles(); track h.id) {
|
|
52
|
-
<button
|
|
53
|
-
type="button"
|
|
54
|
-
role="tab"
|
|
55
|
-
[id]="'tab-' + h.id"
|
|
56
|
-
[attr.aria-selected]="h.id === activo()"
|
|
57
|
-
[attr.aria-controls]="montado(h.id) ? 'panel-' + h.id : null"
|
|
58
|
-
[attr.tabindex]="h.id === activo() ? 0 : -1"
|
|
59
|
-
[class]="claseTab(h.id)"
|
|
60
|
-
(click)="seleccionar(h.id, true)"
|
|
61
|
-
>
|
|
62
|
-
{{ h.label }}
|
|
63
|
-
</button>
|
|
64
|
-
}
|
|
65
|
-
</div>
|
|
66
|
-
|
|
67
|
-
@for (h of montados(); track h.id) {
|
|
68
|
-
<div
|
|
69
|
-
role="tabpanel"
|
|
70
|
-
[id]="'panel-' + h.id"
|
|
71
|
-
[attr.aria-labelledby]="'tab-' + h.id"
|
|
72
|
-
[class.hidden]="h.id !== activo()"
|
|
73
|
-
class="py-3 focus:outline-none"
|
|
74
|
-
tabindex="0"
|
|
75
|
-
>
|
|
76
|
-
<app-view-slot
|
|
77
|
-
[schema]="schema()!"
|
|
78
|
-
[node]="h"
|
|
79
|
-
[parentInstanceId]="uiViewName()"
|
|
80
|
-
[depth]="profundidad() + 1"
|
|
81
|
-
/>
|
|
82
|
-
</div>
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
`,
|
|
86
|
-
})
|
|
87
|
-
export class ComposerHost {
|
|
88
|
-
private readonly route = inject(ActivatedRoute);
|
|
89
|
-
private readonly routeData = toSignal(this.route.data, { requireSync: true });
|
|
90
|
-
|
|
91
|
-
/** Schema: explícito en `data` (fixtures/ruta embebida) o vía `resourceId`. */
|
|
92
|
-
readonly schema = computed<ResourceSchema | undefined>(() => {
|
|
93
|
-
const data = this.routeData();
|
|
94
|
-
const explicit = data['schema'];
|
|
95
|
-
if (explicit !== undefined) return explicit as ResourceSchema;
|
|
96
|
-
const resourceId = data['resourceId'];
|
|
97
|
-
return typeof resourceId === 'string' ? getSchema(resourceId) : undefined;
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
/** instanceId de ESTE contenedor (raíz ⇒ nombre de la uiView de ruta). */
|
|
101
|
-
readonly uiViewName = computed(() => {
|
|
102
|
-
const v = this.routeData()['uiView'];
|
|
103
|
-
return typeof v === 'string' ? v : '';
|
|
104
|
-
});
|
|
105
|
-
|
|
106
|
-
/** Profundidad de ESTE contenedor (raíz ⇒ 0; embebido ⇒ payload.depth). */
|
|
107
|
-
readonly profundidad = computed(() => {
|
|
108
|
-
const d = this.routeData()['depth'];
|
|
109
|
-
return typeof d === 'number' ? d : 0;
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
/** Config `tabs` declarada para esta instancia. */
|
|
113
|
-
readonly vista = computed<UiViewTabs | undefined>(() => {
|
|
114
|
-
const schema = this.schema();
|
|
115
|
-
const nombre = this.uiViewName();
|
|
116
|
-
if (schema === undefined || nombre === '') return undefined;
|
|
117
|
-
const vista = schema.uiViews?.[nombre];
|
|
118
|
-
// `'config' in` descarta UiViewBase; el literal descarta calendar/kanban.
|
|
119
|
-
return vista !== undefined && 'config' in vista && vista.type === 'tabs' ? vista : undefined;
|
|
120
|
-
});
|
|
121
|
-
|
|
122
|
-
/** Error determinista de configuración (schema/vista ausente o no-tabs). */
|
|
123
|
-
private readonly errorConfig = computed<string | null>(() => {
|
|
124
|
-
const data = this.routeData();
|
|
125
|
-
const schema = this.schema();
|
|
126
|
-
const nombre = this.uiViewName();
|
|
127
|
-
if (schema === undefined) {
|
|
128
|
-
return `Schema no encontrado para '${String(data['resourceId'] ?? '')}'`;
|
|
129
|
-
}
|
|
130
|
-
if (nombre === '') return 'Ruta de composición sin uiView declarada';
|
|
131
|
-
const vista = schema.uiViews?.[nombre];
|
|
132
|
-
if (vista === undefined) return `Vista '${nombre}' no declarada en ${schema.id}`;
|
|
133
|
-
if (vista.type !== 'tabs') {
|
|
134
|
-
return `Configuración inválida: ${schema.id}.${nombre} declara '${vista.type}', no es una vista tabs`;
|
|
135
|
-
}
|
|
136
|
-
return null;
|
|
137
|
-
});
|
|
138
|
-
|
|
139
|
-
/** Error de validación D2 (se recalcula una sola vez, en el constructor). */
|
|
140
|
-
private readonly errorValidacion = signal<string | null>(null);
|
|
141
|
-
|
|
142
|
-
/** Error mostrado: validación del schema primero, luego configuración. */
|
|
143
|
-
readonly error = computed<string | null>(() => this.errorValidacion() ?? this.errorConfig());
|
|
144
|
-
|
|
145
|
-
/** Todos los hijos declarados (orden de declaración). */
|
|
146
|
-
private readonly hijos = computed<readonly ViewChildNode[]>(
|
|
147
|
-
() => this.vista()?.config.children ?? [],
|
|
148
|
-
);
|
|
149
|
-
|
|
150
|
-
/** Hijos visibles: TODAS sus condiciones `visibleWhen` pasan sin record. */
|
|
151
|
-
readonly visibles = computed<readonly ViewChildNode[]>(() =>
|
|
152
|
-
this.hijos().filter(
|
|
153
|
-
(h) =>
|
|
154
|
-
h.visibleWhen === undefined ||
|
|
155
|
-
h.visibleWhen.every((cond) => evaluateCondition(cond, {}, {})),
|
|
156
|
-
),
|
|
157
|
-
);
|
|
158
|
-
|
|
159
|
-
/** Selección del usuario (intención; vacío ⇒ usar `initial`/primera). */
|
|
160
|
-
private readonly seleccion = signal('');
|
|
161
|
-
|
|
162
|
-
/**
|
|
163
|
-
* Pestaña activa EFECTIVA: derivada pura de la selección y de las
|
|
164
|
-
* visibles — si la selección dejó de existir (u oculta), cae a `initial`
|
|
165
|
-
* o a la primera visible de forma SINCRÓNICA y visible (R9.9.5: nunca
|
|
166
|
-
* perder la vista elegida en silencio). Estado LOCAL por instancia.
|
|
167
|
-
*/
|
|
168
|
-
readonly activo = computed<string>(() => {
|
|
169
|
-
const vis = this.visibles();
|
|
170
|
-
if (vis.length === 0) return '';
|
|
171
|
-
const sel = this.seleccion();
|
|
172
|
-
if (vis.some((h) => h.id === sel)) return sel;
|
|
173
|
-
const inicial = this.vista()?.config.initial;
|
|
174
|
-
return inicial !== undefined && vis.some((h) => h.id === inicial) ? inicial : vis[0].id;
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
/** Ids ya seleccionados alguna vez (sustento de la retención). */
|
|
178
|
-
private readonly visitados = signal<ReadonlySet<string>>(new Set());
|
|
179
|
-
|
|
180
|
-
/** Paneles en DOM: activo + retenidos visitados que siguen visibles. */
|
|
181
|
-
readonly montados = computed<readonly ViewChildNode[]>(() => {
|
|
182
|
-
const visibles = new Set(this.visibles().map((h) => h.id));
|
|
183
|
-
const activo = this.activo();
|
|
184
|
-
const visitados = this.visitados();
|
|
185
|
-
return this.hijos().filter(
|
|
186
|
-
(h) => visibles.has(h.id) && (h.id === activo || (h.retain === true && visitados.has(h.id))),
|
|
187
|
-
);
|
|
188
|
-
});
|
|
189
|
-
|
|
190
|
-
constructor() {
|
|
191
|
-
// D2 — validar TODO el schema antes del primer render (fail-loud).
|
|
192
|
-
const schema = this.schema();
|
|
193
|
-
if (schema !== undefined) {
|
|
194
|
-
try {
|
|
195
|
-
validateComposition(schema);
|
|
196
|
-
} catch (e) {
|
|
197
|
-
this.errorValidacion.set(e instanceof Error ? e.message : String(e));
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
// La pestaña activa al montar cuenta como visitada: al cambiar de
|
|
201
|
-
// pestaña, un hijo `retain:true` que estaba activo conserva su panel.
|
|
202
|
-
const inicial = this.activo();
|
|
203
|
-
if (inicial !== '') this.visitados.set(new Set([inicial]));
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
/** Selecciona una pestaña y, opcionalmente, mueve el foco a su botón. */
|
|
207
|
-
seleccionar(id: string, conFoco: boolean): void {
|
|
208
|
-
this.seleccion.set(id);
|
|
209
|
-
this.visitados.update((v) => new Set(v).add(id));
|
|
210
|
-
if (conFoco) {
|
|
211
|
-
setTimeout(() => document.getElementById(`tab-${id}`)?.focus(), 0);
|
|
212
|
-
}
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
/** Navegación por teclado del tablist: flechas con wrap, Home/End. */
|
|
216
|
-
onKeydown(event: KeyboardEvent): void {
|
|
217
|
-
const vis = this.visibles();
|
|
218
|
-
if (vis.length === 0) return;
|
|
219
|
-
const i = vis.findIndex((h) => h.id === this.activo());
|
|
220
|
-
let next = -1;
|
|
221
|
-
if (event.key === 'ArrowRight') next = (i + 1) % vis.length;
|
|
222
|
-
else if (event.key === 'ArrowLeft') next = (i - 1 + vis.length) % vis.length;
|
|
223
|
-
else if (event.key === 'Home') next = 0;
|
|
224
|
-
else if (event.key === 'End') next = vis.length - 1;
|
|
225
|
-
if (next < 0) return;
|
|
226
|
-
event.preventDefault();
|
|
227
|
-
this.seleccionar(vis[next].id, true);
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
/** El panel de este hijo está montado (⇒ existe su `aria-controls`). */
|
|
231
|
-
montado(id: string): boolean {
|
|
232
|
-
return this.montados().some((h) => h.id === id);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** Clases del botón de pestaña: activo resaltado con token `primary`. */
|
|
236
|
-
claseTab(id: string): string {
|
|
237
|
-
const base =
|
|
238
|
-
'rounded-t-lg px-3 py-2 text-sm transition-colors focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary';
|
|
239
|
-
return id === this.activo()
|
|
240
|
-
? `${base} bg-primary/10 font-medium text-primary`
|
|
241
|
-
: `${base} text-neutral hover:text-primary`;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
/** Etiqueta a11y del tablist (la del recurso). */
|
|
245
|
-
etiqueta(): string {
|
|
246
|
-
return this.schema()?.labelPlural ?? 'Secciones';
|
|
247
|
-
}
|
|
248
|
-
}
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
// @ts-ignore — `node:fs` no tiene tipos en este proyecto (sin @types/node);
|
|
2
|
-
// el runtime de tests corre en Node y el builtin resuelve igual.
|
|
3
|
-
import { readFileSync } from 'node:fs';
|
|
4
|
-
// @ts-ignore — idem para `node:url`.
|
|
5
|
-
import { fileURLToPath } from 'node:url';
|
|
6
|
-
|
|
7
|
-
import { describe, expect, it } from 'vitest';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* R9.9 · slice 3 — Guardia arquitectónica sobre el compositor (D2).
|
|
11
|
-
*
|
|
12
|
-
* El host debe ser GENÉRICO: sin imports de hojas, sin strings de dominio y
|
|
13
|
-
* sin `switch` de tipos — cualquier página nueva declarada en un schema se
|
|
14
|
-
* compone sin tocar el compositor. Este test lee el fuente REAL desde disco
|
|
15
|
-
* (no el bundle) y falla si aparece un acoplamiento al dominio.
|
|
16
|
-
*/
|
|
17
|
-
function fuente(nombre: string): string {
|
|
18
|
-
return readFileSync(fileURLToPath(new URL(nombre, import.meta.url)), 'utf8');
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
const COMPOSITOR = ['composer-host.ts', 'view-slot.ts', 'view-context.ts'];
|
|
22
|
-
|
|
23
|
-
describe('composer source-scan: el compositor no conoce el dominio', () => {
|
|
24
|
-
it('no importa hojas, páginas ni vistas de dominio', () => {
|
|
25
|
-
for (const nombre of COMPOSITOR) {
|
|
26
|
-
const codigo = fuente(nombre);
|
|
27
|
-
expect(codigo, `${nombre} importa una hoja/página de dominio`).not.toMatch(
|
|
28
|
-
/from ['"](\.\.\/)+(pages|views\/(calendar-view|kanban-view))/,
|
|
29
|
-
);
|
|
30
|
-
expect(codigo, `${nombre} referencia una hoja por import dinámico`).not.toMatch(
|
|
31
|
-
/import\(['"](\.\.\/)+(pages|views\/(calendar-view|kanban-view))/,
|
|
32
|
-
);
|
|
33
|
-
expect(codigo, `${nombre} menciona un tipo de dominio`).not.toMatch(
|
|
34
|
-
/'calendar'|'kanban'|'resource-page'|'form-view'|'detail-view'/,
|
|
35
|
-
);
|
|
36
|
-
}
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
it('no contiene un switch de tipos (dispatch exclusivamente por registro)', () => {
|
|
40
|
-
for (const nombre of COMPOSITOR) {
|
|
41
|
-
expect(fuente(nombre), `${nombre} contiene un switch`).not.toMatch(/@?switch\s*\(/);
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('resuelve hijos sólo vía get(kind, type) del registro, nunca resolve()', () => {
|
|
46
|
-
for (const nombre of ['view-slot.ts']) {
|
|
47
|
-
const codigo = fuente(nombre);
|
|
48
|
-
expect(codigo, `${nombre} usa resolve() (fallback silencioso prohibido)`).not.toMatch(
|
|
49
|
-
/\bresolve\(\s*'view'/,
|
|
50
|
-
);
|
|
51
|
-
expect(codigo, `${nombre} no usa get('view', type)`).toMatch(/get\(\s*'view'\s*,/);
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
});
|