@tinya-code/gas-admin-ui 0.2.0-alpha.15 → 0.2.0-alpha.17
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,175 +0,0 @@
|
|
|
1
|
-
import { Component, computed, inject, input } from '@angular/core';
|
|
2
|
-
import { DomSanitizer, SafeHtml } from '@angular/platform-browser';
|
|
3
|
-
import { FormField } from '@angular/forms/signals';
|
|
4
|
-
|
|
5
|
-
import type { RichSelectField } from '../../core/models/schema.model';
|
|
6
|
-
import { FieldAria } from '../field-aria';
|
|
7
|
-
import { childTree, type RootTree } from '../field-node';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Selector visual enriquecido (`rich-select`).
|
|
11
|
-
*
|
|
12
|
-
* Cada opción se renderiza como tarjeta con su `preview` HTML en vez de un
|
|
13
|
-
* `<select>` nativo. El `preview` se declara en el schema y el motor lo pinta
|
|
14
|
-
* tras sanitizarlo con Angular (sólo HTML inseguro se descarta).
|
|
15
|
-
*
|
|
16
|
-
* El valor se guarda como string (el `value` de la opción elegida).
|
|
17
|
-
*
|
|
18
|
-
* Layout responsive:
|
|
19
|
-
* - `cardWidth` (1–12): ancho de cada tarjeta como fracción de la grilla de 12.
|
|
20
|
-
* `cardWidth: 4` ⇒ 3 tarjetas por fila en escritorio; `cardWidth: 6` ⇒ 2;
|
|
21
|
-
* `cardWidth: 3` ⇒ 4.
|
|
22
|
-
* - `cardCols` (1–6): sobrescribe el cálculo y obliga a esa cantidad de
|
|
23
|
-
* columnas en el punto de ruptura `lg`.
|
|
24
|
-
* - En pantallas chicas las tarjetas se apilan (1 columna).
|
|
25
|
-
*/
|
|
26
|
-
@Component({
|
|
27
|
-
selector: 'app-field-rich-select',
|
|
28
|
-
imports: [FieldAria, FormField],
|
|
29
|
-
template: `
|
|
30
|
-
<div
|
|
31
|
-
class="rich-select"
|
|
32
|
-
role="radiogroup"
|
|
33
|
-
[attr.aria-label]="field().label"
|
|
34
|
-
[attr.aria-required]="field().required ? 'true' : null"
|
|
35
|
-
[class]="gridColsClass()"
|
|
36
|
-
>
|
|
37
|
-
@for (option of options(); track option.value) {
|
|
38
|
-
<label
|
|
39
|
-
class="rich-option"
|
|
40
|
-
[class.selected]="isSelected(option.value)"
|
|
41
|
-
[class.disabled]="option.disabled"
|
|
42
|
-
>
|
|
43
|
-
<input
|
|
44
|
-
type="radio"
|
|
45
|
-
class="sr-only"
|
|
46
|
-
[id]="state().name() + '-' + option.value"
|
|
47
|
-
[value]="option.value"
|
|
48
|
-
[formField]="node()"
|
|
49
|
-
/>
|
|
50
|
-
<div class="rich-option-preview" [innerHTML]="sanitize(option.preview ?? '')"></div>
|
|
51
|
-
<span class="rich-option-label">{{ option.label }}</span>
|
|
52
|
-
</label>
|
|
53
|
-
}
|
|
54
|
-
</div>
|
|
55
|
-
`,
|
|
56
|
-
styles: [
|
|
57
|
-
`
|
|
58
|
-
.rich-select {
|
|
59
|
-
display: grid;
|
|
60
|
-
gap: 0.75rem;
|
|
61
|
-
margin-top: 0.5rem;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/* Por defecto: 1 columna en móvil (apilado). */
|
|
65
|
-
.rich-select {
|
|
66
|
-
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/* Escritorio: se aplica la clase generada desde cardWidth / cardCols. */
|
|
70
|
-
.rich-select.cols-1 { grid-template-columns: repeat(1, minmax(0, 1fr)); }
|
|
71
|
-
.rich-select.cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
|
|
72
|
-
.rich-select.cols-3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
|
|
73
|
-
.rich-select.cols-4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
|
|
74
|
-
.rich-select.cols-5 { grid-template-columns: repeat(5, minmax(0, 1fr)); }
|
|
75
|
-
.rich-select.cols-6 { grid-template-columns: repeat(6, minmax(0, 1fr)); }
|
|
76
|
-
|
|
77
|
-
.rich-option {
|
|
78
|
-
display: flex;
|
|
79
|
-
flex-direction: column;
|
|
80
|
-
align-items: center;
|
|
81
|
-
gap: 0.375rem;
|
|
82
|
-
padding: 0.625rem 0.5rem;
|
|
83
|
-
border: 1px solid #e5e7eb;
|
|
84
|
-
border-radius: 0.75rem;
|
|
85
|
-
background: #ffffff;
|
|
86
|
-
cursor: pointer;
|
|
87
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
88
|
-
min-width: 0;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
.rich-option:not(.disabled):hover {
|
|
92
|
-
border-color: #2563eb;
|
|
93
|
-
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.15);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
.rich-option.selected {
|
|
97
|
-
border-color: #2563eb;
|
|
98
|
-
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.25);
|
|
99
|
-
background: #eff6ff;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
.rich-option.disabled {
|
|
103
|
-
opacity: 0.5;
|
|
104
|
-
cursor: not-allowed;
|
|
105
|
-
border-color: #e5e7eb;
|
|
106
|
-
background: #f9fafb;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
.rich-option-preview {
|
|
110
|
-
width: 48px;
|
|
111
|
-
height: 48px;
|
|
112
|
-
display: flex;
|
|
113
|
-
align-items: center;
|
|
114
|
-
justify-content: center;
|
|
115
|
-
overflow: hidden;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
.rich-option-label {
|
|
119
|
-
font-size: 0.875rem;
|
|
120
|
-
font-weight: 500;
|
|
121
|
-
text-align: center;
|
|
122
|
-
color: #111827;
|
|
123
|
-
line-height: 1.25;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
.rich-option.disabled .rich-option-label {
|
|
127
|
-
color: #9ca3af;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.sr-only {
|
|
131
|
-
position: absolute;
|
|
132
|
-
width: 1px;
|
|
133
|
-
height: 1px;
|
|
134
|
-
padding: 0;
|
|
135
|
-
margin: -1px;
|
|
136
|
-
overflow: hidden;
|
|
137
|
-
clip: rect(0, 0, 0, 0);
|
|
138
|
-
border: 0;
|
|
139
|
-
white-space: nowrap;
|
|
140
|
-
}
|
|
141
|
-
`,
|
|
142
|
-
],
|
|
143
|
-
})
|
|
144
|
-
export class FieldRichSelect {
|
|
145
|
-
readonly field = input.required<RichSelectField>();
|
|
146
|
-
readonly tree = input.required<RootTree>();
|
|
147
|
-
|
|
148
|
-
private readonly sanitizer = inject(DomSanitizer);
|
|
149
|
-
|
|
150
|
-
protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
|
|
151
|
-
protected readonly state = computed(() => this.node()());
|
|
152
|
-
|
|
153
|
-
protected readonly options = computed(() => this.field().options);
|
|
154
|
-
|
|
155
|
-
/** Cuántas columnas de tarjetas en escritorio (lg+). */
|
|
156
|
-
protected readonly gridCols = computed(() => {
|
|
157
|
-
const field = this.field();
|
|
158
|
-
if (field.cardCols) {
|
|
159
|
-
return field.cardCols;
|
|
160
|
-
}
|
|
161
|
-
const cardWidth = field.cardWidth ?? 4;
|
|
162
|
-
// 12 columnas de grilla ÷ ancho de cada tarjeta = cuántas caben por fila.
|
|
163
|
-
return Math.max(1, Math.min(6, Math.floor(12 / cardWidth)));
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
protected readonly gridColsClass = computed(() => `cols-${this.gridCols()}`);
|
|
167
|
-
|
|
168
|
-
protected isSelected(value: string): boolean {
|
|
169
|
-
return this.state().value() === value;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
protected sanitize(html: string): SafeHtml {
|
|
173
|
-
return this.sanitizer.bypassSecurityTrustHtml(html);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { SelectField } from '../../core/models/schema.model';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Guía de control (standards §Control selection): 2–4 opciones → radio
|
|
5
|
-
* buttons; 5–7 → dropdown simple; más de 7 → dropdown con búsqueda.
|
|
6
|
-
*
|
|
7
|
-
* Dos limitaciones deliberadas:
|
|
8
|
-
* - **Sólo opciones estáticas** (`source` excluido): una fuente se carga
|
|
9
|
-
* después y su cantidad de filas puede cambiar; el control no debe mutar
|
|
10
|
-
* de `<select>` a radios en mitad de una edición.
|
|
11
|
-
* - **Mínimo 2**: con una sola opción el dropdown (o un único radio) no
|
|
12
|
-
* aporta nada y el caso real (`users.role`) ya existe así.
|
|
13
|
-
*
|
|
14
|
-
* La comparten el campo (`select.ts`, para ramificar su plantilla) y el
|
|
15
|
-
* rótulo del host (`render-host.ts`): sin este mismo cálculo el host
|
|
16
|
-
* dibujaría un `label[for]` apuntando a un id que un grupo de radios no
|
|
17
|
-
* tiene (el ancla la reemplaza `role="radiogroup"` + `aria-label`).
|
|
18
|
-
*/
|
|
19
|
-
export function esGrupoRadio(field: SelectField): boolean {
|
|
20
|
-
if (field.source !== undefined) {
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
23
|
-
const total = field.options?.length ?? 0;
|
|
24
|
-
return total >= 2 && total <= 4;
|
|
25
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
-
|
|
3
|
-
/** Descriptor del campo `select` (R3.3). */
|
|
4
|
-
export const descriptor: RenderDescriptor = {
|
|
5
|
-
kind: 'field',
|
|
6
|
-
type: 'select',
|
|
7
|
-
since: '1.0',
|
|
8
|
-
load: () => import('./select').then((module) => module.FieldSelect),
|
|
9
|
-
// Nace como cadena: los textos, fechas, urls, selects y relacionales.
|
|
10
|
-
seed: 'string',
|
|
11
|
-
surfaces: ['form', 'display'],
|
|
12
|
-
};
|
|
@@ -1,162 +0,0 @@
|
|
|
1
|
-
import { setSchemaCatalog } from '../../core/registry/schema-catalog';
|
|
2
|
-
import { testCatalog } from '../../core/testing/test-catalog';
|
|
3
|
-
|
|
4
|
-
setSchemaCatalog(testCatalog);
|
|
5
|
-
|
|
6
|
-
import { TestBed } from '@angular/core/testing';
|
|
7
|
-
import { EnvironmentInjector, signal } from '@angular/core';
|
|
8
|
-
import { form } from '@angular/forms/signals';
|
|
9
|
-
import { of, throwError } from 'rxjs';
|
|
10
|
-
import { vi } from 'vitest';
|
|
11
|
-
|
|
12
|
-
import type { SelectField } from '../../core/models/schema.model';
|
|
13
|
-
import { ApiService } from '../../core/services/api.service';
|
|
14
|
-
import type { RootTree } from '../field-node';
|
|
15
|
-
import { FieldSelect } from './select';
|
|
16
|
-
|
|
17
|
-
/** Filas mínimas de `categories` (keyField `slug`, titleField `name`). */
|
|
18
|
-
const FILAS = [
|
|
19
|
-
{ slug: 'bebidas', name: 'Bebidas' },
|
|
20
|
-
{ slug: 'pan', name: 'Pan' },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
describe('Campo select', () => {
|
|
24
|
-
const listSpy = vi.fn();
|
|
25
|
-
|
|
26
|
-
beforeEach(async () => {
|
|
27
|
-
listSpy.mockReset();
|
|
28
|
-
listSpy.mockImplementation(() => of(FILAS.map((fila) => ({ ...fila }))));
|
|
29
|
-
await TestBed.configureTestingModule({
|
|
30
|
-
imports: [FieldSelect],
|
|
31
|
-
providers: [{ provide: ApiService, useValue: { list: listSpy } }],
|
|
32
|
-
}).compileComponents();
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
async function create(field: SelectField) {
|
|
36
|
-
const fixture = TestBed.createComponent(FieldSelect);
|
|
37
|
-
// La semilla lleva la clave del campo: `childTree` exige que exista en
|
|
38
|
-
// el árbol (igual que en el formulario real, donde está en el schema).
|
|
39
|
-
const model = signal<Record<string, unknown>>({ [field.key]: '' });
|
|
40
|
-
// `form()` inyecta en el arranque: fuera de un contexto hay que pasarle
|
|
41
|
-
// el injector, igual que hace form-view.
|
|
42
|
-
const tree = form(model, () => {}, {
|
|
43
|
-
injector: TestBed.inject(EnvironmentInjector),
|
|
44
|
-
}) as unknown as RootTree;
|
|
45
|
-
fixture.componentRef.setInput('field', field);
|
|
46
|
-
fixture.componentRef.setInput('tree', tree);
|
|
47
|
-
fixture.detectChanges();
|
|
48
|
-
return { fixture };
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const labels = (fixture: { nativeElement: HTMLElement }): string[] =>
|
|
52
|
-
Array.from(fixture.nativeElement.querySelectorAll('option')).map(
|
|
53
|
-
(option) => option.textContent?.trim() ?? '',
|
|
54
|
-
);
|
|
55
|
-
|
|
56
|
-
/** Texto visible de cada opción: `<option>` en dropdown, `<label>` en radio. */
|
|
57
|
-
const radioLabels = (fixture: { nativeElement: HTMLElement }): string[] =>
|
|
58
|
-
Array.from(fixture.nativeElement.querySelectorAll('[data-testid="select-radios"] > label')).map(
|
|
59
|
-
(label) => label.textContent?.trim() ?? '',
|
|
60
|
-
);
|
|
61
|
-
|
|
62
|
-
const radios = (fixture: { nativeElement: HTMLElement }): HTMLInputElement[] =>
|
|
63
|
-
Array.from(fixture.nativeElement.querySelectorAll('input[type="radio"]'));
|
|
64
|
-
|
|
65
|
-
const CON_SOURCE: SelectField = {
|
|
66
|
-
type: 'select',
|
|
67
|
-
key: 'category_slug',
|
|
68
|
-
label: 'Categoría',
|
|
69
|
-
source: { resource: 'categories', valueField: 'slug', labelField: 'name' },
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
it('2–4 opciones estáticas se pintan como radios (guía §1) sin tocar la red', async () => {
|
|
73
|
-
const { fixture } = await create({
|
|
74
|
-
type: 'select',
|
|
75
|
-
key: 'estado',
|
|
76
|
-
label: 'Estado',
|
|
77
|
-
options: [
|
|
78
|
-
{ value: 'pendiente', label: 'Pendiente' },
|
|
79
|
-
{ value: 'ok', label: 'OK' },
|
|
80
|
-
],
|
|
81
|
-
});
|
|
82
|
-
await fixture.whenStable();
|
|
83
|
-
fixture.detectChanges();
|
|
84
|
-
|
|
85
|
-
expect(radioLabels(fixture)).toEqual(['— Seleccionar —', 'Pendiente', 'OK']);
|
|
86
|
-
// Opcional ⇒ opción vacía para poder deseleccionar (como el `''` del
|
|
87
|
-
// `<select>` anterior); todos comparten el `name` que setea FormField.
|
|
88
|
-
expect(radios(fixture)).toHaveLength(3);
|
|
89
|
-
const group = fixture.nativeElement.querySelector('[role="radiogroup"]');
|
|
90
|
-
expect(group?.getAttribute('aria-label')).toBe('Estado');
|
|
91
|
-
// Sin `source` el resource queda idle: no hay vuelta que esperar (D62).
|
|
92
|
-
expect(listSpy).not.toHaveBeenCalled();
|
|
93
|
-
expect(fixture.nativeElement.querySelector('[role="status"]')).toBeNull();
|
|
94
|
-
expect(fixture.nativeElement.querySelector('[role="alert"]')).toBeNull();
|
|
95
|
-
});
|
|
96
|
-
|
|
97
|
-
it('un select requerido en modo radio no ofrece la opción vacía', async () => {
|
|
98
|
-
const { fixture } = await create({
|
|
99
|
-
type: 'select',
|
|
100
|
-
key: 'estado',
|
|
101
|
-
label: 'Estado',
|
|
102
|
-
required: true,
|
|
103
|
-
options: [
|
|
104
|
-
{ value: 'pendiente', label: 'Pendiente' },
|
|
105
|
-
{ value: 'ok', label: 'OK' },
|
|
106
|
-
],
|
|
107
|
-
});
|
|
108
|
-
await fixture.whenStable();
|
|
109
|
-
fixture.detectChanges();
|
|
110
|
-
|
|
111
|
-
expect(radioLabels(fixture)).toEqual(['Pendiente', 'OK']);
|
|
112
|
-
expect(radios(fixture)).toHaveLength(2);
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
it('con más de 4 opciones sigue siendo dropdown, no radio', async () => {
|
|
116
|
-
const { fixture } = await create({
|
|
117
|
-
type: 'select',
|
|
118
|
-
key: 'estado',
|
|
119
|
-
label: 'Estado',
|
|
120
|
-
options: [
|
|
121
|
-
{ value: 'a', label: 'A' },
|
|
122
|
-
{ value: 'b', label: 'B' },
|
|
123
|
-
{ value: 'c', label: 'C' },
|
|
124
|
-
{ value: 'd', label: 'D' },
|
|
125
|
-
{ value: 'e', label: 'E' },
|
|
126
|
-
],
|
|
127
|
-
});
|
|
128
|
-
await fixture.whenStable();
|
|
129
|
-
fixture.detectChanges();
|
|
130
|
-
|
|
131
|
-
expect(fixture.nativeElement.querySelector('[data-testid="select-radios"]')).toBeNull();
|
|
132
|
-
expect(labels(fixture).length).toBe(6); // vacío + 5
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('con source pide las filas y las mapea con la regla de la relación', async () => {
|
|
136
|
-
const { fixture } = await create(CON_SOURCE);
|
|
137
|
-
await fixture.whenStable();
|
|
138
|
-
fixture.detectChanges();
|
|
139
|
-
|
|
140
|
-
expect(listSpy).toHaveBeenCalledTimes(1);
|
|
141
|
-
expect(labels(fixture)).toEqual(['— Seleccionar —', 'Bebidas', 'Pan']);
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
it('mientras la vuelta no llega se anuncia la carga (no un dropdown vacío)', async () => {
|
|
145
|
-
const { fixture } = await create(CON_SOURCE);
|
|
146
|
-
const aviso = fixture.nativeElement.querySelector('[role="status"]');
|
|
147
|
-
expect(aviso).not.toBeNull();
|
|
148
|
-
expect(aviso.textContent).toContain('Cargando opciones…');
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
it('un fallo de carga se anuncia con role=alert y no inventa opciones', async () => {
|
|
152
|
-
listSpy.mockImplementation(() => throwError(() => new Error('503')));
|
|
153
|
-
const { fixture } = await create(CON_SOURCE);
|
|
154
|
-
await fixture.whenStable();
|
|
155
|
-
fixture.detectChanges();
|
|
156
|
-
|
|
157
|
-
const aviso = fixture.nativeElement.querySelector('[role="alert"]');
|
|
158
|
-
expect(aviso).not.toBeNull();
|
|
159
|
-
expect(aviso.textContent).toContain('No se pudieron cargar las opciones.');
|
|
160
|
-
expect(labels(fixture)).toEqual(['— Seleccionar —']);
|
|
161
|
-
});
|
|
162
|
-
});
|
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { Component, computed, inject, input, resource, signal } from '@angular/core';
|
|
2
|
-
import { FormField } from '@angular/forms/signals';
|
|
3
|
-
|
|
4
|
-
import type { SelectField, SelectOption } from '../../core/models/schema.model';
|
|
5
|
-
import { DataSourceService, dataSourceKey } from '../../core/services/data-source.service';
|
|
6
|
-
import { optionsFromRows } from '../../core/utils/source-options';
|
|
7
|
-
import { getSchema } from '../../core/registry/schema-catalog';
|
|
8
|
-
import { FieldAria } from '../field-aria';
|
|
9
|
-
import { childTree, type RootTree } from '../field-node';
|
|
10
|
-
import { esGrupoRadio } from './radio-mode';
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Desplegable de un solo valor (base.md §5.1).
|
|
14
|
-
*
|
|
15
|
-
* Sus opciones se declaran de dos formas y **exactamente una** (XOR, D62):
|
|
16
|
-
* `options` estáticas en el schema o `source` — una fuente declarada que
|
|
17
|
-
* pide `DataSourceService` con la misma caché que usan las relaciones y los
|
|
18
|
-
* widgets (F8 · R6.5). Mientras la vuelta no llega se anuncia; sin anuncio
|
|
19
|
-
* un desplegable vacío se confundiría con «esto no tiene opciones».
|
|
20
|
-
*/
|
|
21
|
-
@Component({
|
|
22
|
-
selector: 'app-field-select',
|
|
23
|
-
imports: [FieldAria, FormField],
|
|
24
|
-
template: `
|
|
25
|
-
@if (fallo()) {
|
|
26
|
-
<p class="mb-1 text-xs text-danger" role="alert">No se pudieron cargar las opciones.</p>
|
|
27
|
-
} @else if (cargando()) {
|
|
28
|
-
<p class="mb-1 text-xs text-neutral" role="status">Cargando opciones…</p>
|
|
29
|
-
}
|
|
30
|
-
@if (useRadio()) {
|
|
31
|
-
<!-- Guía de control: 2–4 opciones estáticas → radio buttons, no un
|
|
32
|
-
desplegable. Cada opción es un label implícito (texto y control
|
|
33
|
-
juntos) y el grupo se nombra con aria-label: el rótulo del host
|
|
34
|
-
se dibuja como texto porque un grupo no es un único control con
|
|
35
|
-
el que emparejar un for. formField va en CADA input: Signal
|
|
36
|
-
Forms soporta radio (checked = value === element.value) y setea
|
|
37
|
-
name/disabled del grupo por estado de campo. -->
|
|
38
|
-
<div
|
|
39
|
-
class="mt-1 flex flex-col gap-1"
|
|
40
|
-
role="radiogroup"
|
|
41
|
-
[attr.aria-label]="field().label"
|
|
42
|
-
data-testid="select-radios"
|
|
43
|
-
>
|
|
44
|
-
@if (!field().required) {
|
|
45
|
-
<label class="flex w-fit items-center gap-2 text-sm">
|
|
46
|
-
<input
|
|
47
|
-
type="radio"
|
|
48
|
-
class="accent-primary"
|
|
49
|
-
[id]="state().name() + '-__vacio'"
|
|
50
|
-
value=""
|
|
51
|
-
[formField]="node()"
|
|
52
|
-
/>
|
|
53
|
-
<span class="text-neutral">{{ field().placeholder ?? '— Seleccionar —' }}</span>
|
|
54
|
-
</label>
|
|
55
|
-
}
|
|
56
|
-
@for (option of options(); track option.value) {
|
|
57
|
-
<label class="flex w-fit items-center gap-2 text-sm">
|
|
58
|
-
<input
|
|
59
|
-
type="radio"
|
|
60
|
-
class="accent-primary"
|
|
61
|
-
[id]="state().name() + '-' + option.value"
|
|
62
|
-
[value]="option.value"
|
|
63
|
-
[formField]="node()"
|
|
64
|
-
/>
|
|
65
|
-
<span>{{ option.label }}</span>
|
|
66
|
-
</label>
|
|
67
|
-
}
|
|
68
|
-
</div>
|
|
69
|
-
} @else {
|
|
70
|
-
@if (useSearch()) {
|
|
71
|
-
<!-- Guía §1: más de 7 opciones → dropdown con búsqueda. Se filtra el
|
|
72
|
-
select nativo en vez de construir un combobox: el teclado, el
|
|
73
|
-
anuncio de la opción activa y el valor elegido siguen siendo los
|
|
74
|
-
del navegador. -->
|
|
75
|
-
<input
|
|
76
|
-
type="search"
|
|
77
|
-
class="mb-1 w-full rounded-lg border border-neutral/30 bg-surface px-2 py-1 text-sm focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
78
|
-
placeholder="Buscar…"
|
|
79
|
-
aria-label="Buscar entre las opciones"
|
|
80
|
-
[attr.aria-controls]="state().name()"
|
|
81
|
-
[value]="query()"
|
|
82
|
-
(input)="onQuery($event)"
|
|
83
|
-
/>
|
|
84
|
-
}
|
|
85
|
-
<select class="field-input" [id]="state().name()" [formField]="node()">
|
|
86
|
-
@if (!field().required) {
|
|
87
|
-
<option value="">{{ field().placeholder ?? '— Seleccionar —' }}</option>
|
|
88
|
-
}
|
|
89
|
-
@for (option of visibleOptions(); track option.value) {
|
|
90
|
-
<option [value]="option.value" [disabled]="option.disabled ?? false">
|
|
91
|
-
{{ option.label }}
|
|
92
|
-
</option>
|
|
93
|
-
}
|
|
94
|
-
</select>
|
|
95
|
-
@if (noMatches()) {
|
|
96
|
-
<p class="mt-1 text-xs text-neutral" role="status">Sin resultados para «{{ query() }}».</p>
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
`,
|
|
100
|
-
})
|
|
101
|
-
export class FieldSelect {
|
|
102
|
-
readonly field = input.required<SelectField>();
|
|
103
|
-
readonly tree = input.required<RootTree>();
|
|
104
|
-
|
|
105
|
-
private readonly ds = inject(DataSourceService);
|
|
106
|
-
|
|
107
|
-
protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
|
|
108
|
-
protected readonly state = computed(() => this.node()());
|
|
109
|
-
|
|
110
|
-
/** Texto escrito en el buscador (sólo se usa si supera el umbral). */
|
|
111
|
-
protected readonly query = signal('');
|
|
112
|
-
|
|
113
|
-
/**
|
|
114
|
-
* Registro del formulario, sólo para resolver `{campo}` en `source.params`.
|
|
115
|
-
* Sin parámetros declarados no se lee: el campo no depende del formulario.
|
|
116
|
-
*/
|
|
117
|
-
private readonly registro = computed<Record<string, unknown> | undefined>(() => {
|
|
118
|
-
const source = this.field().source;
|
|
119
|
-
if (source === undefined || source.params === undefined) return undefined;
|
|
120
|
-
return this.tree()().value();
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
/** `undefined` sin fuente ⇒ `resource` queda idle y jamás llama al loader. */
|
|
124
|
-
private readonly claveFuente = computed(() => {
|
|
125
|
-
const source = this.field().source;
|
|
126
|
-
return source === undefined ? undefined : dataSourceKey(source, this.registro());
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
private readonly fuenteResource = resource({
|
|
130
|
-
params: () => this.claveFuente(),
|
|
131
|
-
loader: async () => {
|
|
132
|
-
const source = this.field().source;
|
|
133
|
-
if (source === undefined) return [];
|
|
134
|
-
return this.ds.rows(source, this.registro());
|
|
135
|
-
},
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Opciones efectivas: las estáticas del schema o las filas de la fuente
|
|
140
|
-
* declarada, mapeadas con la misma regla que una relación (F8 · R6.5).
|
|
141
|
-
*/
|
|
142
|
-
protected readonly options = computed<SelectOption[]>(() => {
|
|
143
|
-
const field = this.field();
|
|
144
|
-
if (field.source === undefined) return field.options ?? [];
|
|
145
|
-
// `value()` del resource TIRA en estado error (contract de Angular):
|
|
146
|
-
// se mira el estado antes de leerlo, o el propio render rompe en vez
|
|
147
|
-
// de pintar el aviso de fallo.
|
|
148
|
-
if (this.fuenteResource.status() === 'error') return [];
|
|
149
|
-
return optionsFromRows(this.fuenteResource.value() ?? [], {
|
|
150
|
-
source: field.source,
|
|
151
|
-
target: getSchema(field.source.resource),
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
/** Sólo aplica con `source`: una lista estática no tiene vuelta que esperar. */
|
|
156
|
-
protected readonly cargando = computed(
|
|
157
|
-
() => this.claveFuente() !== undefined && this.fuenteResource.status() === 'loading',
|
|
158
|
-
);
|
|
159
|
-
|
|
160
|
-
protected readonly fallo = computed(
|
|
161
|
-
() => this.claveFuente() !== undefined && this.fuenteResource.status() === 'error',
|
|
162
|
-
);
|
|
163
|
-
|
|
164
|
-
/** Umbral de la guía §1: 5–7 opciones dropdown simple, más de 7 con búsqueda. */
|
|
165
|
-
protected readonly useSearch = computed(() => this.options().length > 7);
|
|
166
|
-
|
|
167
|
-
/** Guía §1: 2–4 opciones estáticas → radio buttons (ver `radio-mode.ts`). */
|
|
168
|
-
protected readonly useRadio = computed(() => esGrupoRadio(this.field()));
|
|
169
|
-
|
|
170
|
-
protected onQuery(event: Event): void {
|
|
171
|
-
this.query.set((event.target as HTMLInputElement).value);
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/**
|
|
175
|
-
* Opciones que coinciden con el término, SIN conservar la elegida: es la
|
|
176
|
-
* base del mensaje «Sin resultados».
|
|
177
|
-
*/
|
|
178
|
-
protected readonly matches = computed<SelectOption[]>(() => {
|
|
179
|
-
const all = this.options();
|
|
180
|
-
const term = this.query().trim().toLowerCase();
|
|
181
|
-
if (term === '') {
|
|
182
|
-
return all;
|
|
183
|
-
}
|
|
184
|
-
return all.filter((option) => option.label.toLowerCase().includes(term));
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
/** El buscador está activo y no encontró nada nuevo (el elegido no cuenta). */
|
|
188
|
-
protected readonly noMatches = computed(
|
|
189
|
-
() => this.useSearch() && this.query().trim() !== '' && this.matches().length === 0,
|
|
190
|
-
);
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* Opciones que se dibujan. Con búsqueda activa se acotan por etiqueta, pero
|
|
194
|
-
* la opción elegida se conserva siempre aunque no coincida con el término:
|
|
195
|
-
* si desapareciera del DOM, el select dejaría de mostrar el valor actual
|
|
196
|
-
* (guía §1: «valor seleccionado siempre visible»).
|
|
197
|
-
*/
|
|
198
|
-
protected readonly visibleOptions = computed<SelectOption[]>(() => {
|
|
199
|
-
const filtered = this.matches();
|
|
200
|
-
if (this.query().trim() === '') {
|
|
201
|
-
return filtered;
|
|
202
|
-
}
|
|
203
|
-
const selected = String(this.state().value() ?? '');
|
|
204
|
-
if (filtered.some((option) => option.value === selected)) {
|
|
205
|
-
return filtered;
|
|
206
|
-
}
|
|
207
|
-
const kept = this.options().find((option) => option.value === selected);
|
|
208
|
-
return kept === undefined ? filtered : [...filtered, kept];
|
|
209
|
-
});
|
|
210
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
-
|
|
3
|
-
/** Descriptor del campo `slug` (R3.3). */
|
|
4
|
-
export const descriptor: RenderDescriptor = {
|
|
5
|
-
kind: 'field',
|
|
6
|
-
type: 'slug',
|
|
7
|
-
since: '1.0',
|
|
8
|
-
load: () => import('./slug').then((module) => module.FieldSlug),
|
|
9
|
-
// Nace como cadena: los textos, fechas, urls, selects y relacionales.
|
|
10
|
-
seed: 'string',
|
|
11
|
-
surfaces: ['form', 'display'],
|
|
12
|
-
// Se aparta de la forma estándar sólo por `exists`: con el registro ya
|
|
13
|
-
// creado el campo queda bloqueado (`lockAfterCreate`, R3.6).
|
|
14
|
-
inputs: (config, extras) => ({
|
|
15
|
-
field: config,
|
|
16
|
-
tree: extras.ctx.tree,
|
|
17
|
-
exists: extras.ctx.exists,
|
|
18
|
-
}),
|
|
19
|
-
};
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { Component, computed, effect, input, signal } from '@angular/core';
|
|
2
|
-
import { FormField } from '@angular/forms/signals';
|
|
3
|
-
|
|
4
|
-
import type { SlugField } from '../../core/models/schema.model';
|
|
5
|
-
import { slugify } from '../../shared/utils/slugify';
|
|
6
|
-
import { FieldAria } from '../field-aria';
|
|
7
|
-
import { childTree, type RootTree } from '../field-node';
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Slug autogenerado desde otro campo (base.md §5.1). Se regenera mientras el
|
|
11
|
-
* usuario no lo edite a mano; queda bloqueado tras crear el registro si
|
|
12
|
-
* `lockAfterCreate` no es `false`. La rama bloqueada no usa `formField`
|
|
13
|
-
* (atributo `readonly` estático y sin sincronización).
|
|
14
|
-
*/
|
|
15
|
-
@Component({
|
|
16
|
-
selector: 'app-field-slug',
|
|
17
|
-
imports: [FieldAria, FormField],
|
|
18
|
-
template: `
|
|
19
|
-
@if (locked()) {
|
|
20
|
-
<input
|
|
21
|
-
type="text"
|
|
22
|
-
class="field-input"
|
|
23
|
-
[id]="state().name()"
|
|
24
|
-
[value]="state().value()"
|
|
25
|
-
readonly
|
|
26
|
-
/>
|
|
27
|
-
} @else {
|
|
28
|
-
<input
|
|
29
|
-
type="text"
|
|
30
|
-
class="field-input"
|
|
31
|
-
[id]="state().name()"
|
|
32
|
-
[formField]="node()"
|
|
33
|
-
[placeholder]="field().placeholder ?? ''"
|
|
34
|
-
(input)="manual.set(true)"
|
|
35
|
-
/>
|
|
36
|
-
}
|
|
37
|
-
`,
|
|
38
|
-
})
|
|
39
|
-
export class FieldSlug {
|
|
40
|
-
readonly field = input.required<SlugField>();
|
|
41
|
-
readonly tree = input.required<RootTree>();
|
|
42
|
-
/** Si el registro ya existe (lo informa form-view, Fase 10). */
|
|
43
|
-
readonly exists = input(false);
|
|
44
|
-
|
|
45
|
-
/** Edición manual del usuario: corta la autogeneración. */
|
|
46
|
-
protected readonly manual = signal(false);
|
|
47
|
-
|
|
48
|
-
protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
|
|
49
|
-
protected readonly state = computed(() => this.node()());
|
|
50
|
-
|
|
51
|
-
protected readonly locked = computed(
|
|
52
|
-
() => this.field().lockAfterCreate !== false && this.exists(),
|
|
53
|
-
);
|
|
54
|
-
|
|
55
|
-
private readonly sourceValue = computed(() =>
|
|
56
|
-
childTree<unknown>(this.tree(), this.field().from)().value(),
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
constructor() {
|
|
60
|
-
effect(() => {
|
|
61
|
-
if (this.locked() || this.manual()) {
|
|
62
|
-
return;
|
|
63
|
-
}
|
|
64
|
-
this.state().value.set(slugify(String(this.sourceValue() ?? '')));
|
|
65
|
-
});
|
|
66
|
-
}
|
|
67
|
-
}
|
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
-
|
|
3
|
-
/** Descriptor del campo `string-list` (R3.3). */
|
|
4
|
-
export const descriptor: RenderDescriptor = {
|
|
5
|
-
kind: 'field',
|
|
6
|
-
type: 'string-list',
|
|
7
|
-
since: '1.0',
|
|
8
|
-
load: () => import('./string-list').then((module) => module.FieldStringList),
|
|
9
|
-
seed: 'array',
|
|
10
|
-
surfaces: ['form', 'display'],
|
|
11
|
-
};
|