@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,90 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Mensaje completo a copiar o compartir.
|
|
3
|
-
*
|
|
4
|
-
* `shareTextTemplate` es el MENSAJE FORMATEADO por el schema (el que trae
|
|
5
|
-
* saludo, ref, PIN o el enlace ya escrito dentro), mientras que `urlTemplate`
|
|
6
|
-
* es sólo el enlace. Los tres caminos —copiar, compartir nativo y WhatsApp—
|
|
7
|
-
* deben ofrecer el mismo texto: antes `copy()` y la acción del listado sólo
|
|
8
|
-
* escribían `url`, así que el operador se quedaba sin el mensaje.
|
|
9
|
-
*
|
|
10
|
-
* Tres casos, sin perder información y sin duplicar el enlace:
|
|
11
|
-
* - sin `shareText` → sólo la URL
|
|
12
|
-
* - `shareText` con el sitio ya mencionado → el mensaje tal cual
|
|
13
|
-
* - `shareText` sin el sitio → mensaje + espacio + URL
|
|
14
|
-
*
|
|
15
|
-
* "Sitio" se decide por ORIGIN, no por la URL exacta: la plantilla puede
|
|
16
|
-
* referenciar sólo la base (`{public_base_url}` → `https://ejemplo.com`)
|
|
17
|
-
* o la URL completa con el path (`…/p/{ref}`); en ambos casos el mensaje
|
|
18
|
-
* YA apunta al sitio y no se le pega nada al final. Antes se comparaba
|
|
19
|
-
* contra la URL completa, así que un mensaje con la base recibía la URL
|
|
20
|
-
* con código duplicada al final.
|
|
21
|
-
*
|
|
22
|
-
* @param url Enlace resuelto (`urlTemplate`).
|
|
23
|
-
* @param shareText Mensaje formateado opcional (`shareTextTemplate`).
|
|
24
|
-
*/
|
|
25
|
-
export function buildShareMessage(url: string, shareText?: string): string {
|
|
26
|
-
const text = (shareText ?? '').trim();
|
|
27
|
-
if (!text) return url;
|
|
28
|
-
if (url && mentionsSite(text, url)) return text;
|
|
29
|
-
return url ? `${text} ${url}` : text;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** ¿El mensaje ya apunta al sitio de `url` (origin o URL exacta)? */
|
|
33
|
-
function mentionsSite(text: string, url: string): boolean {
|
|
34
|
-
if (text.includes(url)) return true;
|
|
35
|
-
try {
|
|
36
|
-
const { origin } = new URL(url);
|
|
37
|
-
return origin !== 'null' && text.includes(origin);
|
|
38
|
-
} catch {
|
|
39
|
-
// `url` relativa (`/p/123`): sin origin que comparar, sólo la URL exacta.
|
|
40
|
-
return false;
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
* Escribe en el portapapeles sin reventar: primero la API moderna, después
|
|
46
|
-
* el `textarea` + `execCommand` (obsoleto, pero sigue siendo el fallback
|
|
47
|
-
* que existe en contextos no seguros).
|
|
48
|
-
*
|
|
49
|
-
* Único punto del admin que sabe cómo copiar: `copy-share`, las acciones de
|
|
50
|
-
* fila del listado y cualquier widget futuro comparten este camino, en vez
|
|
51
|
-
* de reimplementar cada uno sus propios fallbacks.
|
|
52
|
-
*
|
|
53
|
-
* @returns `true` si el texto quedó copiado por alguna de las vías.
|
|
54
|
-
*/
|
|
55
|
-
export async function writeClipboard(text: string): Promise<boolean> {
|
|
56
|
-
const clipboard = typeof navigator !== 'undefined' ? navigator.clipboard : undefined;
|
|
57
|
-
if (clipboard && typeof clipboard.writeText === 'function') {
|
|
58
|
-
try {
|
|
59
|
-
await clipboard.writeText(text);
|
|
60
|
-
return true;
|
|
61
|
-
} catch {
|
|
62
|
-
// Permiso denegado o contexto no seguro: sigue al fallback legado.
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return legacyCopy(text);
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function legacyCopy(text: string): boolean {
|
|
69
|
-
if (typeof document === 'undefined' || typeof document.execCommand !== 'function') {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
const area = document.createElement('textarea');
|
|
73
|
-
area.value = text;
|
|
74
|
-
area.setAttribute('readonly', '');
|
|
75
|
-
area.style.position = 'fixed';
|
|
76
|
-
area.style.top = '0';
|
|
77
|
-
area.style.opacity = '0';
|
|
78
|
-
document.body.appendChild(area);
|
|
79
|
-
area.select();
|
|
80
|
-
|
|
81
|
-
let copied = false;
|
|
82
|
-
try {
|
|
83
|
-
copied = document.execCommand('copy');
|
|
84
|
-
} catch {
|
|
85
|
-
copied = false;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
document.body.removeChild(area);
|
|
89
|
-
return copied;
|
|
90
|
-
}
|
|
@@ -1,95 +0,0 @@
|
|
|
1
|
-
import { resolveEndpointPath } from './context-resolver';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* Plan `doc/context-sources-plan.md` · Fase 2.
|
|
5
|
-
*
|
|
6
|
-
* TDD: éstos son los tests RED del resolver puro. Congelan las reglas del
|
|
7
|
-
* contrato: resolución anidada, escalares, codificación, pereza (sin
|
|
8
|
-
* marcadores no se llama a ninguna fuente) y TODOS los casos fail-loud.
|
|
9
|
-
*/
|
|
10
|
-
describe('context-resolver — marcadores en paths de endpoints', () => {
|
|
11
|
-
const fuentes = {
|
|
12
|
-
auth: () => ({ user: { id: 'u_9', name: 'Ana' }, token: 'tok' }),
|
|
13
|
-
config: () => ({ pais: 'AR' }),
|
|
14
|
-
tenant: () => 'acme',
|
|
15
|
-
numero: () => 7,
|
|
16
|
-
ruta: () => 'a b/c',
|
|
17
|
-
vacio: () => '',
|
|
18
|
-
nulo: () => null,
|
|
19
|
-
objeto: () => ({ a: 1 }),
|
|
20
|
-
rota: () => {
|
|
21
|
-
throw new Error('boom fuente');
|
|
22
|
-
},
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
// ── Casos felices ────────────────────────────────────────────────────────
|
|
26
|
-
it('resuelve una ruta anidada {auth.user.id}', () => {
|
|
27
|
-
expect(resolveEndpointPath('/businesses/{auth.user.id}/orders', fuentes)).toBe(
|
|
28
|
-
'/businesses/u_9/orders',
|
|
29
|
-
);
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
it('resuelve la fuente entera {tenant}', () => {
|
|
33
|
-
expect(resolveEndpointPath('/tenants/{tenant}/orders', fuentes)).toBe('/tenants/acme/orders');
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
it('convierte escalares a texto', () => {
|
|
37
|
-
expect(resolveEndpointPath('/page/{numero}', fuentes)).toBe('/page/7');
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('codifica cada valor como segmento de URL', () => {
|
|
41
|
-
expect(resolveEndpointPath('/x/{ruta}/y', fuentes)).toBe('/x/a%20b%2Fc/y');
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
it('resuelve varios marcadores en un mismo path', () => {
|
|
45
|
-
expect(
|
|
46
|
-
resolveEndpointPath('/tenants/{tenant}/businesses/{auth.user.id}/paises/{config.pais}', fuentes),
|
|
47
|
-
).toBe('/tenants/acme/businesses/u_9/paises/AR');
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
it('sin marcadores devuelve el path tal cual y NO llama a ninguna fuente', () => {
|
|
51
|
-
const perezosa = {
|
|
52
|
-
auth: () => {
|
|
53
|
-
throw new Error('no debe evaluarse sin marcadores');
|
|
54
|
-
},
|
|
55
|
-
};
|
|
56
|
-
expect(resolveEndpointPath('/admin/products', perezosa)).toBe('/admin/products');
|
|
57
|
-
});
|
|
58
|
-
|
|
59
|
-
it('resuelve un marcador dentro de un path que ya lleva query string', () => {
|
|
60
|
-
expect(resolveEndpointPath('/orders?owner={auth.user.id}&limit=10', fuentes)).toBe(
|
|
61
|
-
'/orders?owner=u_9&limit=10',
|
|
62
|
-
);
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
// ── Fail loud ────────────────────────────────────────────────────────────
|
|
66
|
-
it('valor ausente ⇒ error que nombra el path y el marcador', () => {
|
|
67
|
-
const path = '/x/{auth.user.nope}/y';
|
|
68
|
-
expect(() => resolveEndpointPath(path, fuentes)).toThrow(path);
|
|
69
|
-
expect(() => resolveEndpointPath(path, fuentes)).toThrow('auth.user.nope');
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
it('valor vacío ⇒ error, nunca un segmento vacío en la URL', () => {
|
|
73
|
-
expect(() => resolveEndpointPath('/x/{vacio}/y', fuentes)).toThrow('/x/{vacio}/y');
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
it('valor nulo ⇒ error', () => {
|
|
77
|
-
expect(() => resolveEndpointPath('/x/{nulo}', fuentes)).toThrow('nulo');
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
it('fuente no registrada ⇒ error con el nombre de la fuente', () => {
|
|
81
|
-
expect(() => resolveEndpointPath('/x/{desconocida.id}', fuentes)).toThrow('desconocida');
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it('getter que revienta ⇒ su error se propaga tal cual', () => {
|
|
85
|
-
expect(() => resolveEndpointPath('/x/{rota}', fuentes)).toThrow('boom fuente');
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('{key} sin resolver ⇒ error que aclara que sólo aplica a operaciones con clave', () => {
|
|
89
|
-
expect(() => resolveEndpointPath('/x/{key}', fuentes)).toThrow('key');
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
it('valor no escalar ⇒ error, nunca [object Object] en la URL', () => {
|
|
93
|
-
expect(() => resolveEndpointPath('/x/{objeto}', fuentes)).toThrow('objeto');
|
|
94
|
-
});
|
|
95
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Resolución de marcadores de contexto en paths de endpoints.
|
|
3
|
-
*
|
|
4
|
-
* Plan `doc/context-sources-plan.md` · Fase 2. Lógica PURA: ni HTTP ni DI —
|
|
5
|
-
* recibe un path y un mapa de getters de fuentes y devuelve el path ya
|
|
6
|
-
* resuelto, o revienta.
|
|
7
|
-
*
|
|
8
|
-
* Reglas del contrato (R1–R4 del plan):
|
|
9
|
-
*
|
|
10
|
-
* - `{<fuente>}` / `{<fuente>.<ruta.anidada>}` → valor de la fuente en el
|
|
11
|
-
* momento de la llamada (los getters se evalúan SOLO si hay marcadores).
|
|
12
|
-
* - Cada valor se codifica como segmento de URL (`encodeURIComponent`).
|
|
13
|
-
* - **Fail loud**: fuente no registrada, valor ausente/vacío, valor no
|
|
14
|
-
* escalar o `{key}` sin resolver ⇒ error que nombra el path y el marcador.
|
|
15
|
-
* Jamás se devuelve un segmento vacío ni el texto literal del marcador.
|
|
16
|
-
* - Sin marcadores ⇒ el path vuelve intacto y ninguna fuente se evalúa.
|
|
17
|
-
*
|
|
18
|
-
* OJO con `interpolateTemplate` (R3): su semántica «ausente ⇒ cadena vacía»
|
|
19
|
-
* es correcta para acciones y fuentes de datos, pero acá sería un 404 mudo.
|
|
20
|
-
* Por eso la lectura y la validación viven en este fichero y ese helper no
|
|
21
|
-
* se toca.
|
|
22
|
-
*/
|
|
23
|
-
|
|
24
|
-
/** `{nombre}` o `{nombre.ruta.anidada}` — misma forma que usa el interpolador. */
|
|
25
|
-
const MARKER = /\{([a-zA-Z0-9_.]+)\}/g;
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Resuelve los marcadores de `path` contra `sources`.
|
|
29
|
-
*
|
|
30
|
-
* @param path Path declarado en `ResourceEndpoints`, tal cual vino del schema.
|
|
31
|
-
* @param sources Getters por nombre de fuente (`auth`, `config`, `valueSources`…).
|
|
32
|
-
* @returns El path con cada marcador sustituido y codificado.
|
|
33
|
-
* @throws Error nombrando path y marcador ante cualquier valor irresoluble.
|
|
34
|
-
*/
|
|
35
|
-
export function resolveEndpointPath(
|
|
36
|
-
path: string,
|
|
37
|
-
sources: Readonly<Record<string, () => unknown>>,
|
|
38
|
-
): string {
|
|
39
|
-
if (!path.includes('{')) return path;
|
|
40
|
-
return path.replace(MARKER, (marker, nombre: string) =>
|
|
41
|
-
sustituir(path, marker, nombre, sources),
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/** Sustituye UN marcador. `path` completo sólo viaja para el mensaje de error. */
|
|
46
|
-
function sustituir(
|
|
47
|
-
path: string,
|
|
48
|
-
marker: string,
|
|
49
|
-
nombre: string,
|
|
50
|
-
sources: Readonly<Record<string, () => unknown>>,
|
|
51
|
-
): string {
|
|
52
|
-
if (nombre === 'key') {
|
|
53
|
-
throw new Error(
|
|
54
|
-
`[gas-admin-ui] path '${path}': ${marker} llegó sin resolver — ` +
|
|
55
|
-
`la clave sólo la sustituyen get/update/remove antes de resolver el contexto.`,
|
|
56
|
-
);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const punto = nombre.indexOf('.');
|
|
60
|
-
const fuente = punto === -1 ? nombre : nombre.slice(0, punto);
|
|
61
|
-
const getter = sources[fuente];
|
|
62
|
-
if (typeof getter !== 'function') {
|
|
63
|
-
throw new Error(
|
|
64
|
-
`[gas-admin-ui] path '${path}': no se pudo resolver ${marker} — ` +
|
|
65
|
-
`la fuente '${fuente}' no está registrada.`,
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
// El getter se evalúa acá y sólo acá: si revienta, su error se propaga.
|
|
70
|
-
const valor = punto === -1 ? getter() : leerRuta(getter(), nombre.slice(punto + 1));
|
|
71
|
-
|
|
72
|
-
if (valor === undefined || valor === null) {
|
|
73
|
-
throw new Error(
|
|
74
|
-
`[gas-admin-ui] path '${path}': no se pudo resolver ${marker} — sin valor para '${nombre}'.`,
|
|
75
|
-
);
|
|
76
|
-
}
|
|
77
|
-
if (typeof valor === 'object') {
|
|
78
|
-
throw new Error(
|
|
79
|
-
`[gas-admin-ui] path '${path}': no se pudo resolver ${marker} — ` +
|
|
80
|
-
`el valor de '${fuente}' no es un segmento de URL (no es escalar).`,
|
|
81
|
-
);
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
const texto = String(valor);
|
|
85
|
-
if (texto === '') {
|
|
86
|
-
throw new Error(
|
|
87
|
-
`[gas-admin-ui] path '${path}': no se pudo resolver ${marker} — valor vacío en '${fuente}'.`,
|
|
88
|
-
);
|
|
89
|
-
}
|
|
90
|
-
return encodeURIComponent(texto);
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
/** Lectura profunda `a.b.c` sobre el valor de la fuente; inexistente ⇒ `undefined`. */
|
|
94
|
-
function leerRuta(valor: unknown, ruta: string): unknown {
|
|
95
|
-
let actual: unknown = valor;
|
|
96
|
-
for (const paso of ruta.split('.')) {
|
|
97
|
-
if (actual === null || typeof actual !== 'object') return undefined;
|
|
98
|
-
actual = (actual as Record<string, unknown>)[paso];
|
|
99
|
-
}
|
|
100
|
-
return actual;
|
|
101
|
-
}
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { extractPagination } from './response-unwrapper';
|
|
2
|
-
|
|
3
|
-
// Extracción de metadata de paginación (plan doc/engine-refactor-plan.md · Fase 2.2)
|
|
4
|
-
// Lo consume `listPage()` (Fase 3); acá se prueba la utilidad pura.
|
|
5
|
-
describe('extractPagination', () => {
|
|
6
|
-
it('lee cursor/total/hasMore de meta cuando metaKey está declarado', () => {
|
|
7
|
-
const raw = { data: [{ slug: 'a' }], meta: { cursor: 'c1', count: 10, more: true } };
|
|
8
|
-
const result = extractPagination(raw, {
|
|
9
|
-
dataKey: 'data',
|
|
10
|
-
metaKey: 'meta',
|
|
11
|
-
pagination: { cursorKey: 'cursor', totalKey: 'count', hasMoreKey: 'more' },
|
|
12
|
-
});
|
|
13
|
-
expect(result).toEqual({ nextCursor: 'c1', total: 10, hasMore: true });
|
|
14
|
-
});
|
|
15
|
-
|
|
16
|
-
it('auto-detecta claves comunes (nextCursor, total, hasMore) en el body', () => {
|
|
17
|
-
const raw = { items: [{ slug: 'a' }], nextCursor: 'tok', total: 42, hasMore: true };
|
|
18
|
-
expect(extractPagination(raw, { dataKey: 'items' })).toEqual({
|
|
19
|
-
nextCursor: 'tok',
|
|
20
|
-
total: 42,
|
|
21
|
-
hasMore: true,
|
|
22
|
-
});
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it('sin cursor ni claves de paginación ⇒ nextCursor null, total 0, hasMore false', () => {
|
|
26
|
-
const raw = { items: [{ slug: 'a' }] };
|
|
27
|
-
expect(extractPagination(raw, undefined)).toEqual({
|
|
28
|
-
nextCursor: null,
|
|
29
|
-
total: 0,
|
|
30
|
-
hasMore: false,
|
|
31
|
-
});
|
|
32
|
-
});
|
|
33
|
-
|
|
34
|
-
it('con cursor presente y sin flag hasMore explícito ⇒ hasMore true (derivado)', () => {
|
|
35
|
-
const raw = { items: [], nextCursor: 'c2' };
|
|
36
|
-
expect(extractPagination(raw, undefined)).toEqual({
|
|
37
|
-
nextCursor: 'c2',
|
|
38
|
-
total: 0,
|
|
39
|
-
hasMore: true,
|
|
40
|
-
});
|
|
41
|
-
});
|
|
42
|
-
|
|
43
|
-
it('metaKey declarado pero ausente en el body ⇒ cae al body', () => {
|
|
44
|
-
const raw = { items: [], nextCursor: 'c3', total: 7 };
|
|
45
|
-
const result = extractPagination(raw, { dataKey: 'items', metaKey: 'meta' });
|
|
46
|
-
expect(result).toEqual({ nextCursor: 'c3', total: 7, hasMore: true });
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
it('cursor vacío string se trata como ausente (nextCursor null, hasMore false)', () => {
|
|
50
|
-
const raw = { items: [], nextCursor: '' };
|
|
51
|
-
expect(extractPagination(raw, undefined)).toEqual({
|
|
52
|
-
nextCursor: null,
|
|
53
|
-
total: 0,
|
|
54
|
-
hasMore: false,
|
|
55
|
-
});
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it('cuerpo no-objeto ⇒ sin paginación (no lanza)', () => {
|
|
59
|
-
expect(extractPagination(null, undefined)).toEqual({
|
|
60
|
-
nextCursor: null,
|
|
61
|
-
total: 0,
|
|
62
|
-
hasMore: false,
|
|
63
|
-
});
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('hasMoreKey explícito tiene prioridad sobre el derivado del cursor', () => {
|
|
67
|
-
const raw = { items: [], nextCursor: 'c9', hasMore: false };
|
|
68
|
-
expect(extractPagination(raw, undefined).hasMore).toBe(false);
|
|
69
|
-
});
|
|
70
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Desempaquetado opt-in de respuestas HTTP anidadas (`{ data, meta }`,
|
|
3
|
-
* `{ items, total }`, o claves arbitrarias).
|
|
4
|
-
*
|
|
5
|
-
* El motor NO asume un sobre: sólo desempaqueta cuando la declaración
|
|
6
|
-
* `ResponseUnwrap` está presente. Sin ella, el comportamiento actual se
|
|
7
|
-
* conserva (arreglo plano, `{ items }`, u objeto crudo).
|
|
8
|
-
*
|
|
9
|
-
* Mantener esta lógica aquí (y no en `ApiService`) deja el servicio enfocado
|
|
10
|
-
* en el despacho y facilita la prueba de las reglas de unwrap.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import type { ResponseUnwrap } from '../models/schema.model';
|
|
14
|
-
|
|
15
|
-
export type UnwrapOperation = 'list' | 'get' | 'create' | 'update' | 'remove';
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Devuelve `true` si la declaración `unwrap` aplica a la operación dada.
|
|
19
|
-
* Si `apply` falta, se asume `'all'` (decisión fija del plan).
|
|
20
|
-
*/
|
|
21
|
-
export function appliesTo(unwrap: ResponseUnwrap, operation: UnwrapOperation): boolean {
|
|
22
|
-
const apply = unwrap.apply ?? 'all';
|
|
23
|
-
return apply === 'all' || apply === operation;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Extrae el payload declarado por `unwrap.dataKey` del cuerpo `raw`.
|
|
28
|
-
*
|
|
29
|
-
* - Si `unwrap` no aplica a `operation`, devuelve `raw` tal cual.
|
|
30
|
-
* - Si aplica y `raw` tiene la clave, devuelve su valor.
|
|
31
|
-
* - Si aplica y la clave falta, lanza un `Error` descriptivo (no silencia).
|
|
32
|
-
*/
|
|
33
|
-
export function unwrapResponse(raw: unknown, unwrap: ResponseUnwrap | undefined, operation: UnwrapOperation): unknown {
|
|
34
|
-
if (!unwrap || !appliesTo(unwrap, operation)) {
|
|
35
|
-
return raw;
|
|
36
|
-
}
|
|
37
|
-
if (raw !== null && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
38
|
-
const record = raw as Record<string, unknown>;
|
|
39
|
-
if (Object.prototype.hasOwnProperty.call(record, unwrap.dataKey)) {
|
|
40
|
-
return record[unwrap.dataKey];
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
throw new Error(
|
|
44
|
-
`Response unwrap declared dataKey '${unwrap.dataKey}' but it is missing in the response for operation '${operation}'`,
|
|
45
|
-
);
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Metadata de paginación extraída del sobre; la consume `ApiService.listPage()`. */
|
|
49
|
-
export interface PaginationMeta {
|
|
50
|
-
nextCursor: string | null;
|
|
51
|
-
total: number;
|
|
52
|
-
hasMore: boolean;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const NO_PAGINATION: PaginationMeta = { nextCursor: null, total: 0, hasMore: false };
|
|
56
|
-
|
|
57
|
-
/**
|
|
58
|
-
* Extrae la metadata de paginación del cuerpo `raw` según la declaración
|
|
59
|
-
* `unwrap.pagination` (cursorKey/totalKey/hasMoreKey).
|
|
60
|
-
*
|
|
61
|
-
* - Con `metaKey` declarado Y presente en el body, las claves se leen de ese
|
|
62
|
-
* sub-objeto; si no, del body raíz.
|
|
63
|
-
* - Sin claves declaradas se auto-detectan las comunes: `nextCursor`,
|
|
64
|
-
* `total`, `hasMore`.
|
|
65
|
-
* - `hasMore` explícito (boolean) tiene prioridad; sin él se deriva del
|
|
66
|
-
* cursor: presente ⇒ `true`, ausente/vacío ⇒ `false`.
|
|
67
|
-
* - Cuerpo no-objeto ⇒ sin paginación. Nunca lanza: la paginación es
|
|
68
|
-
* opt-in y su ausencia sólo significa `hasMore = false`.
|
|
69
|
-
*/
|
|
70
|
-
export function extractPagination(
|
|
71
|
-
raw: unknown,
|
|
72
|
-
unwrap: ResponseUnwrap | undefined,
|
|
73
|
-
): PaginationMeta {
|
|
74
|
-
if (raw === null || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
75
|
-
return { ...NO_PAGINATION };
|
|
76
|
-
}
|
|
77
|
-
const body = raw as Record<string, unknown>;
|
|
78
|
-
|
|
79
|
-
let source = body;
|
|
80
|
-
const metaKey = unwrap?.metaKey;
|
|
81
|
-
if (metaKey) {
|
|
82
|
-
const meta = body[metaKey];
|
|
83
|
-
if (meta !== null && typeof meta === 'object' && !Array.isArray(meta)) {
|
|
84
|
-
source = meta as Record<string, unknown>;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
const cursorKey = unwrap?.pagination?.cursorKey ?? 'nextCursor';
|
|
89
|
-
const totalKey = unwrap?.pagination?.totalKey ?? 'total';
|
|
90
|
-
const hasMoreKey = unwrap?.pagination?.hasMoreKey ?? 'hasMore';
|
|
91
|
-
|
|
92
|
-
const cursorValue = source[cursorKey] ?? body[cursorKey];
|
|
93
|
-
const nextCursor = typeof cursorValue === 'string' && cursorValue !== '' ? cursorValue : null;
|
|
94
|
-
|
|
95
|
-
const totalValue = source[totalKey] ?? body[totalKey];
|
|
96
|
-
const total = typeof totalValue === 'number' ? totalValue : 0;
|
|
97
|
-
|
|
98
|
-
const hasMoreValue = source[hasMoreKey] ?? body[hasMoreKey];
|
|
99
|
-
const hasMore =
|
|
100
|
-
typeof hasMoreValue === 'boolean' ? hasMoreValue : nextCursor !== null;
|
|
101
|
-
|
|
102
|
-
return { nextCursor, total, hasMore };
|
|
103
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
|
|
3
|
-
import type { DataSource, ResourceSchema } from '../models/schema.model';
|
|
4
|
-
import { optionsFromRows } from './source-options';
|
|
5
|
-
|
|
6
|
-
const TARGET: ResourceSchema = {
|
|
7
|
-
id: 'categories',
|
|
8
|
-
label: 'Categoría',
|
|
9
|
-
labelPlural: 'Categorías',
|
|
10
|
-
kind: 'collection',
|
|
11
|
-
endpoint: { list: '/admin/categories' },
|
|
12
|
-
keyField: 'slug',
|
|
13
|
-
titleField: 'name',
|
|
14
|
-
listColumns: [],
|
|
15
|
-
fields: [],
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const FILAS = [
|
|
19
|
-
{ slug: 'bebidas', name: 'Bebidas', description: 'Frías', active: true },
|
|
20
|
-
{ slug: 'pan', name: '', description: '', active: false },
|
|
21
|
-
];
|
|
22
|
-
|
|
23
|
-
const source = (extra: Partial<DataSource> = {}): DataSource => ({
|
|
24
|
-
resource: 'categories',
|
|
25
|
-
...extra,
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
describe('optionsFromRows', () => {
|
|
29
|
-
it('usa valueField/labelField declarados en la fuente', () => {
|
|
30
|
-
const opciones = optionsFromRows(FILAS, {
|
|
31
|
-
source: source({ valueField: 'slug', labelField: 'name' }),
|
|
32
|
-
target: TARGET,
|
|
33
|
-
});
|
|
34
|
-
expect(opciones).toEqual([
|
|
35
|
-
{ value: 'bebidas', label: 'Bebidas' },
|
|
36
|
-
{ value: 'pan', label: 'pan' }, // label vacío ⇒ cae al valor
|
|
37
|
-
]);
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
it('sin los campos declarados cae a keyField/titleField del recurso destino', () => {
|
|
41
|
-
const opciones = optionsFromRows(FILAS, { source: source(), target: TARGET });
|
|
42
|
-
expect(opciones[0]).toEqual({ value: 'bebidas', label: 'Bebidas' });
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
it('sin nada declarado el valor es `id` (último recurso conocido)', () => {
|
|
46
|
-
const opciones = optionsFromRows([{ id: 'x' }], {
|
|
47
|
-
source: source(),
|
|
48
|
-
target: undefined,
|
|
49
|
-
});
|
|
50
|
-
expect(opciones).toEqual([{ value: 'x', label: 'x' }]);
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('una fila sin valor no es opción: no hay qué seleccionar', () => {
|
|
54
|
-
const opciones = optionsFromRows(
|
|
55
|
-
[{ slug: '', name: 'Vacía' }, { name: 'Sin clave' }, { slug: null, name: 'Nula' }],
|
|
56
|
-
{ source: source({ valueField: 'slug', labelField: 'name' }), target: TARGET },
|
|
57
|
-
);
|
|
58
|
-
expect(opciones).toEqual([]);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it('`onlyActive` descarta las inactivas en el cliente', () => {
|
|
62
|
-
const opciones = optionsFromRows(FILAS, {
|
|
63
|
-
source: source({ valueField: 'slug', labelField: 'name' }),
|
|
64
|
-
target: TARGET,
|
|
65
|
-
onlyActive: true,
|
|
66
|
-
});
|
|
67
|
-
expect(opciones.map((o) => o.value)).toEqual(['bebidas']);
|
|
68
|
-
});
|
|
69
|
-
|
|
70
|
-
it('secondaryField añade el dato con « · », y se cae si no hay dato', () => {
|
|
71
|
-
const opciones = optionsFromRows(
|
|
72
|
-
[
|
|
73
|
-
{ slug: 'a', name: 'A', description: 'Frías' },
|
|
74
|
-
{ slug: 'b', name: 'B', description: '' },
|
|
75
|
-
{ slug: 'c', name: 'C' },
|
|
76
|
-
],
|
|
77
|
-
{
|
|
78
|
-
source: source({ valueField: 'slug', labelField: 'name' }),
|
|
79
|
-
target: TARGET,
|
|
80
|
-
secondaryField: 'description',
|
|
81
|
-
},
|
|
82
|
-
);
|
|
83
|
-
expect(opciones.map((o) => o.label)).toEqual(['A · Frías', 'B', 'C']);
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
it('no muta las filas de entrada', () => {
|
|
87
|
-
const filas = [{ slug: 'a', name: 'A', active: false }];
|
|
88
|
-
const copia = JSON.parse(JSON.stringify(filas));
|
|
89
|
-
optionsFromRows(filas, {
|
|
90
|
-
source: source({ valueField: 'slug', labelField: 'name' }),
|
|
91
|
-
target: TARGET,
|
|
92
|
-
onlyActive: true,
|
|
93
|
-
});
|
|
94
|
-
expect(filas).toEqual(copia);
|
|
95
|
-
});
|
|
96
|
-
});
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import type { DataSource, ResourceSchema, SelectOption } from '../models/schema.model';
|
|
2
|
-
|
|
3
|
-
/** Contexto de mapeo: la fuente declarada más las particularidades del campo. */
|
|
4
|
-
export interface RowsToOptions {
|
|
5
|
-
/** Fuente de la que salieron las filas (F8 · R6.5). */
|
|
6
|
-
source: DataSource;
|
|
7
|
-
/** Schema del recurso destino: aporta `keyField`/`titleField` por defecto. */
|
|
8
|
-
target: ResourceSchema | undefined;
|
|
9
|
-
/** Descarta filas con `active: false` (`relation.onlyActive`). */
|
|
10
|
-
onlyActive?: boolean;
|
|
11
|
-
/** Dato secundario junto a la etiqueta («Juan Pérez · DNI 123»). */
|
|
12
|
-
secondaryField?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Filas de una fuente declarada → opciones de desplegable (F8 · R6.5).
|
|
17
|
-
*
|
|
18
|
-
* Es el ÚNICO punto que traduce filas a `SelectOption`: antes esa regla
|
|
19
|
-
* estaba escrita dos veces (en `relation` y cada consumidor montaba su
|
|
20
|
-
* propia petición). Con `source` compartido, relaciones y desplegables con
|
|
21
|
-
* datos remotos pasan por acá.
|
|
22
|
-
*
|
|
23
|
-
* Reglas, en orden:
|
|
24
|
-
* - valor: `source.valueField` → `target.keyField` → `id`;
|
|
25
|
-
* - etiqueta: `source.labelField` → `target.titleField` → el valor;
|
|
26
|
-
* - fila sin valor (vacío o inexistente) no es opción: no hay qué seleccionar;
|
|
27
|
-
* - `onlyActive` descarta las inactivas en el cliente — el back ya las excluye
|
|
28
|
-
* de su FK, pero una lista cacheada puede traer una fila vieja.
|
|
29
|
-
*/
|
|
30
|
-
export function optionsFromRows(
|
|
31
|
-
rows: readonly Record<string, unknown>[],
|
|
32
|
-
{ source, target, onlyActive, secondaryField }: RowsToOptions,
|
|
33
|
-
): SelectOption[] {
|
|
34
|
-
const valueKey = source.valueField ?? target?.keyField ?? 'id';
|
|
35
|
-
const labelKey = source.labelField ?? target?.titleField ?? valueKey;
|
|
36
|
-
const options: SelectOption[] = [];
|
|
37
|
-
for (const row of rows) {
|
|
38
|
-
if (onlyActive === true && row['active'] === false) continue;
|
|
39
|
-
const value = row[valueKey];
|
|
40
|
-
if (value === undefined || value === null || value === '') continue;
|
|
41
|
-
const label = row[labelKey];
|
|
42
|
-
const primary = typeof label === 'string' && label !== '' ? label : String(value);
|
|
43
|
-
options.push({ value: String(value), label: withSecondary(primary, row, secondaryField) });
|
|
44
|
-
}
|
|
45
|
-
return options;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/** Etiqueta con su dato secundario, o la etiqueta tal cual si no hay. */
|
|
49
|
-
function withSecondary(primary: string, row: Record<string, unknown>, key?: string): string {
|
|
50
|
-
if (key === undefined) return primary;
|
|
51
|
-
const secondary = row[key];
|
|
52
|
-
if (secondary === undefined || secondary === null || secondary === '') return primary;
|
|
53
|
-
return `${primary} · ${String(secondary)}`;
|
|
54
|
-
}
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest';
|
|
2
|
-
import { interpolateTemplate } from './template-interpolator';
|
|
3
|
-
|
|
4
|
-
describe('interpolateTemplate', () => {
|
|
5
|
-
it('1. interpola variables simples en la plantilla', () => {
|
|
6
|
-
const template = 'https://ejemplo.com/p/{ref}?cliente={customer_name}';
|
|
7
|
-
const data = { ref: 'ORD-123', customer_name: 'Juan Perez' };
|
|
8
|
-
|
|
9
|
-
const result = interpolateTemplate(template, data);
|
|
10
|
-
|
|
11
|
-
expect(result).toBe('https://ejemplo.com/p/ORD-123?cliente=Juan Perez');
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
it('2. reemplaza variables nulas o indefinidas por cadena vacía', () => {
|
|
15
|
-
const template = 'Pedido {ref} - Estado: {status}';
|
|
16
|
-
const data = { ref: 'ORD-999' };
|
|
17
|
-
|
|
18
|
-
const result = interpolateTemplate(template, data);
|
|
19
|
-
|
|
20
|
-
expect(result).toBe('Pedido ORD-999 - Estado: ');
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
it('3. resuelve claves anidadas usando notación de punto', () => {
|
|
24
|
-
const template = 'ID: {order.meta.id} - Nombre: {order.customer.name}';
|
|
25
|
-
const data = {
|
|
26
|
-
order: {
|
|
27
|
-
meta: { id: 'M-55' },
|
|
28
|
-
customer: { name: 'Maria' },
|
|
29
|
-
},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
|
-
const result = interpolateTemplate(template, data);
|
|
33
|
-
|
|
34
|
-
expect(result).toBe('ID: M-55 - Nombre: Maria');
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
it('4. recurre al contexto adicional si la clave no está en data', () => {
|
|
38
|
-
const template = '{public_base_url}/p/{ref}';
|
|
39
|
-
const data = { ref: 'PED-404' };
|
|
40
|
-
const extraContext = { public_base_url: 'https://retiro.tienda.com' };
|
|
41
|
-
|
|
42
|
-
const result = interpolateTemplate(template, data, extraContext);
|
|
43
|
-
|
|
44
|
-
expect(result).toBe('https://retiro.tienda.com/p/PED-404');
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
it('5. devuelve la misma plantilla si no contiene marcadores', () => {
|
|
48
|
-
const template = 'Texto plano sin variables';
|
|
49
|
-
const result = interpolateTemplate(template, { a: 1 });
|
|
50
|
-
|
|
51
|
-
expect(result).toBe('Texto plano sin variables');
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
it('6. maneja plantillas vacías o nulas sin romper', () => {
|
|
55
|
-
expect(interpolateTemplate('', { ref: '123' })).toBe('');
|
|
56
|
-
// @ts-expect-error probando valor nulo
|
|
57
|
-
expect(interpolateTemplate(null, { ref: '123' })).toBe('');
|
|
58
|
-
});
|
|
59
|
-
});
|