@tinya-code/gas-admin-ui 0.2.0-alpha.12 → 0.2.0-alpha.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -0
- package/ng-package.json +7 -0
- package/package.json +3 -15
- package/src/lib/actions/action-registry.spec.ts +52 -0
- package/src/lib/actions/action-registry.ts +126 -0
- package/src/lib/actions/action-service.spec.ts +215 -0
- package/src/lib/actions/action-service.ts +87 -0
- package/src/lib/actions/descriptors.spec.ts +155 -0
- package/src/lib/actions/descriptors.ts +154 -0
- package/src/lib/columns/column-cell.ts +33 -0
- package/src/lib/columns/column-defaults.spec.ts +99 -0
- package/src/lib/columns/column-defaults.ts +84 -0
- package/src/lib/columns/column-format.spec.ts +274 -0
- package/src/lib/columns/descriptors.ts +113 -0
- package/src/lib/columns/format-badge.ts +51 -0
- package/src/lib/columns/format-boolean.ts +58 -0
- package/src/lib/columns/format-currency.ts +30 -0
- package/src/lib/columns/format-date.ts +35 -0
- package/src/lib/columns/format-number.ts +32 -0
- package/src/lib/columns/format-text.ts +23 -0
- package/src/lib/columns/format-thumbnail.ts +63 -0
- package/src/lib/core/adapters/firebase-auth.adapter.ts +6 -0
- package/src/lib/core/guards/auth.guard.spec.ts +88 -0
- package/src/lib/core/guards/auth.guard.ts +48 -0
- package/src/lib/core/guards/unsaved-changes.guard.ts +16 -0
- package/src/lib/core/interceptors/api-error.interceptor.spec.ts +182 -0
- package/src/lib/core/interceptors/api-error.interceptor.ts +87 -0
- package/src/lib/core/models/api.model.ts +104 -0
- package/src/lib/core/models/schema-descriptions.ts +118 -0
- package/src/lib/core/models/schema.model.ts +1097 -0
- package/src/lib/core/registry/composition-validator.spec.ts +358 -0
- package/src/lib/core/registry/composition-validator.ts +270 -0
- package/src/lib/core/registry/field-validators.spec.ts +199 -0
- package/src/lib/core/registry/field-validators.ts +440 -0
- package/src/lib/core/registry/render-host.spec.ts +208 -0
- package/src/lib/core/registry/render-host.ts +646 -0
- package/src/lib/core/registry/render-registry.spec.ts +96 -0
- package/src/lib/core/registry/render-registry.ts +312 -0
- package/src/lib/core/registry/schema-catalog.ts +156 -0
- package/src/lib/core/services/api.service.spec.ts +1142 -0
- package/src/lib/core/services/api.service.ts +371 -0
- package/src/lib/core/services/auth.service.spec.ts +653 -0
- package/src/lib/core/services/auth.service.ts +503 -0
- package/src/lib/core/services/config.service.spec.ts +121 -0
- package/src/lib/core/services/config.service.ts +119 -0
- package/src/lib/core/services/data-source.service.spec.ts +179 -0
- package/src/lib/core/services/data-source.service.ts +142 -0
- package/src/lib/core/services/error-mapper.service.spec.ts +50 -0
- package/src/lib/core/services/error-mapper.service.ts +49 -0
- package/src/lib/core/services/notification.service.ts +73 -0
- package/src/lib/core/services/relation-drawer.service.ts +41 -0
- package/src/lib/core/services/theme.service.spec.ts +106 -0
- package/src/lib/core/services/theme.service.ts +86 -0
- package/src/lib/core/services/transport.spec.ts +198 -0
- package/src/lib/core/services/transport.ts +164 -0
- package/src/lib/core/services/upload.service.ts +81 -0
- package/src/lib/core/testing/categories.schema.ts +125 -0
- package/src/lib/core/testing/orders.schema.ts +372 -0
- package/src/lib/core/testing/products.schema.ts +258 -0
- package/src/lib/core/testing/site.schema.ts +195 -0
- package/src/lib/core/testing/test-catalog.ts +21 -0
- package/src/lib/core/tokens.ts +112 -0
- package/src/lib/core/utils/clipboard.spec.ts +55 -0
- package/src/lib/core/utils/clipboard.ts +90 -0
- package/src/lib/core/utils/context-resolver.spec.ts +95 -0
- package/src/lib/core/utils/context-resolver.ts +101 -0
- package/src/lib/core/utils/response-unwrapper.spec.ts +70 -0
- package/src/lib/core/utils/response-unwrapper.ts +103 -0
- package/src/lib/core/utils/source-options.spec.ts +96 -0
- package/src/lib/core/utils/source-options.ts +54 -0
- package/src/lib/core/utils/template-interpolator.spec.ts +59 -0
- package/src/lib/core/utils/template-interpolator.ts +56 -0
- package/src/lib/core/view-navigation/view-navigation.service.spec.ts +207 -0
- package/src/lib/core/view-navigation/view-navigation.service.ts +132 -0
- package/src/lib/fields/boolean/boolean.descriptor.ts +11 -0
- package/src/lib/fields/boolean/boolean.ts +40 -0
- package/src/lib/fields/color/color.descriptor.ts +12 -0
- package/src/lib/fields/color/color.ts +34 -0
- package/src/lib/fields/currency/currency.descriptor.ts +12 -0
- package/src/lib/fields/currency/currency.ts +67 -0
- package/src/lib/fields/date/date.descriptor.ts +12 -0
- package/src/lib/fields/date/date.ts +36 -0
- package/src/lib/fields/descriptors.ts +122 -0
- package/src/lib/fields/display/display-badge.ts +65 -0
- package/src/lib/fields/display/display-date.ts +39 -0
- package/src/lib/fields/display/display-defaults.ts +84 -0
- package/src/lib/fields/display/display-file.ts +41 -0
- package/src/lib/fields/display/display-image.ts +54 -0
- package/src/lib/fields/display/display-list.ts +107 -0
- package/src/lib/fields/display/display-number.ts +41 -0
- package/src/lib/fields/display/display-text.ts +42 -0
- package/src/lib/fields/display/display.spec.ts +242 -0
- package/src/lib/fields/dropdown/dropdown.descriptor.ts +12 -0
- package/src/lib/fields/dropdown/dropdown.ts +45 -0
- package/src/lib/fields/email/email.descriptor.ts +12 -0
- package/src/lib/fields/email/email.ts +28 -0
- package/src/lib/fields/field-aria.ts +60 -0
- package/src/lib/fields/field-host/field-host.spec.ts +118 -0
- package/src/lib/fields/field-host/field-host.ts +47 -0
- package/src/lib/fields/field-node.ts +88 -0
- package/src/lib/fields/file/file.descriptor.ts +14 -0
- package/src/lib/fields/file/file.spec.ts +134 -0
- package/src/lib/fields/file/file.ts +281 -0
- package/src/lib/fields/group/group.descriptor.ts +21 -0
- package/src/lib/fields/group/group.ts +110 -0
- package/src/lib/fields/image/image.descriptor.ts +12 -0
- package/src/lib/fields/image/image.spec.ts +117 -0
- package/src/lib/fields/image/image.ts +281 -0
- package/src/lib/fields/key-value/key-value.descriptor.ts +11 -0
- package/src/lib/fields/key-value/key-value.ts +283 -0
- package/src/lib/fields/list/list.descriptor.ts +21 -0
- package/src/lib/fields/list/list.ts +376 -0
- package/src/lib/fields/multiselect/multiselect.descriptor.ts +11 -0
- package/src/lib/fields/multiselect/multiselect.spec.ts +142 -0
- package/src/lib/fields/multiselect/multiselect.ts +152 -0
- package/src/lib/fields/number/number.descriptor.ts +12 -0
- package/src/lib/fields/number/number.ts +40 -0
- package/src/lib/fields/phone/phone.descriptor.ts +12 -0
- package/src/lib/fields/phone/phone.ts +46 -0
- package/src/lib/fields/readonly-text/readonly-text.descriptor.ts +12 -0
- package/src/lib/fields/readonly-text/readonly-text.ts +58 -0
- package/src/lib/fields/relation/relation.descriptor.ts +12 -0
- package/src/lib/fields/relation/relation.spec.ts +193 -0
- package/src/lib/fields/relation/relation.ts +224 -0
- package/src/lib/fields/rich-select/rich-select.descriptor.ts +12 -0
- package/src/lib/fields/rich-select/rich-select.ts +175 -0
- package/src/lib/fields/select/radio-mode.ts +25 -0
- package/src/lib/fields/select/select.descriptor.ts +12 -0
- package/src/lib/fields/select/select.spec.ts +162 -0
- package/src/lib/fields/select/select.ts +210 -0
- package/src/lib/fields/slug/slug.descriptor.ts +19 -0
- package/src/lib/fields/slug/slug.ts +67 -0
- package/src/lib/fields/string-list/string-list.descriptor.ts +11 -0
- package/src/lib/fields/string-list/string-list.ts +222 -0
- package/src/lib/fields/text/text.descriptor.ts +12 -0
- package/src/lib/fields/text/text.ts +35 -0
- package/src/lib/fields/textarea/textarea.descriptor.ts +12 -0
- package/src/lib/fields/textarea/textarea.spec.ts +117 -0
- package/src/lib/fields/textarea/textarea.ts +126 -0
- package/src/lib/fields/time/time.descriptor.ts +12 -0
- package/src/lib/fields/time/time.ts +28 -0
- package/src/lib/fields/unknown-render/unknown-render.ts +49 -0
- package/src/lib/fields/url/url.descriptor.ts +12 -0
- package/src/lib/fields/url/url.ts +53 -0
- package/src/lib/gas-admin-ui.spec.ts +21 -0
- package/src/lib/gas-admin-ui.ts +9 -0
- package/src/lib/layouts/descriptors.ts +9 -0
- package/src/lib/pages/dashboard/dashboard.ts +167 -0
- package/src/lib/pages/detail-view/detail-view.spec.ts +333 -0
- package/src/lib/pages/detail-view/detail-view.ts +570 -0
- package/src/lib/pages/drawer-form/drawer-form.ts +228 -0
- package/src/lib/pages/form-view/form-model.ts +88 -0
- package/src/lib/pages/form-view/form-schema.ts +255 -0
- package/src/lib/pages/form-view/form-view.ts +1622 -0
- package/src/lib/pages/list-view/list-view.formats.spec.ts +214 -0
- package/src/lib/pages/list-view/list-view.ts +882 -0
- package/src/lib/pages/login/login.spec.ts +109 -0
- package/src/lib/pages/login/login.ts +227 -0
- package/src/lib/pages/resource-page/resource-page.ts +234 -0
- package/src/lib/shared/components/badge/badge.ts +35 -0
- package/src/lib/shared/components/button/button.ts +65 -0
- package/src/lib/shared/components/confirm-dialog/confirm-dialog.ts +48 -0
- package/src/lib/shared/components/copy-button/copy-button.ts +56 -0
- package/src/lib/shared/components/copy-share/copy-share.spec.ts +122 -0
- package/src/lib/shared/components/copy-share/copy-share.ts +107 -0
- package/src/lib/shared/components/drawer/drawer.spec.ts +127 -0
- package/src/lib/shared/components/drawer/drawer.ts +172 -0
- package/src/lib/shared/components/empty-state/empty-state.ts +30 -0
- package/src/lib/shared/components/modal/modal.spec.ts +149 -0
- package/src/lib/shared/components/modal/modal.ts +145 -0
- package/src/lib/shared/components/post-create-modal/post-create-modal.spec.ts +165 -0
- package/src/lib/shared/components/post-create-modal/post-create-modal.ts +159 -0
- package/src/lib/shared/components/skeleton/skeleton.ts +31 -0
- package/src/lib/shared/components/toast/toast.ts +58 -0
- package/src/lib/shared/utils/col-span.ts +25 -0
- package/src/lib/shared/utils/condition-evaluator.ts +53 -0
- package/src/lib/shared/utils/depends-on.ts +48 -0
- package/src/lib/shared/utils/dialog-focus.ts +28 -0
- package/src/lib/shared/utils/field-path.ts +49 -0
- package/src/lib/shared/utils/gap-sorting.ts +144 -0
- package/src/lib/shared/utils/seed.spec.ts +64 -0
- package/src/lib/shared/utils/seed.ts +149 -0
- package/src/lib/shared/utils/slugify.ts +13 -0
- package/src/lib/shared/utils/validators.ts +41 -0
- package/src/lib/shell/layout/layout.ts +63 -0
- package/src/lib/shell/shell.routes.spec.ts +59 -0
- package/src/lib/shell/shell.routes.ts +122 -0
- package/src/lib/shell/sidebar/sidebar.ts +139 -0
- package/src/lib/shell/theme-toggle/theme-toggle.ts +74 -0
- package/src/lib/views/calendar-view/calendar-model.ts +426 -0
- package/src/lib/views/calendar-view/calendar-view.spec.ts +557 -0
- package/src/lib/views/calendar-view/calendar-view.ts +568 -0
- package/src/lib/views/composer/composer-host.spec.ts +301 -0
- package/src/lib/views/composer/composer-host.ts +248 -0
- package/src/lib/views/composer/composer-imports.spec.ts +54 -0
- package/src/lib/views/composer/view-context.spec.ts +117 -0
- package/src/lib/views/composer/view-context.ts +109 -0
- package/src/lib/views/composer/view-slot.spec.ts +130 -0
- package/src/lib/views/composer/view-slot.ts +178 -0
- package/src/lib/views/descriptors.ts +46 -0
- package/src/lib/views/kanban-view/kanban-model.ts +173 -0
- package/src/lib/views/kanban-view/kanban-view.spec.ts +540 -0
- package/src/lib/views/kanban-view/kanban-view.ts +327 -0
- package/src/lib/widgets/bar-chart/bar-chart.descriptor.ts +17 -0
- package/src/lib/widgets/bar-chart/bar-chart.spec.ts +66 -0
- package/src/lib/widgets/bar-chart/bar-chart.ts +76 -0
- package/src/lib/widgets/chart-line/chart-line.descriptor.ts +17 -0
- package/src/lib/widgets/chart-line/chart-line.spec.ts +113 -0
- package/src/lib/widgets/chart-line/chart-line.ts +219 -0
- package/src/lib/widgets/descriptors.ts +29 -0
- package/src/lib/widgets/metric-card/metric-card.descriptor.ts +41 -0
- package/src/lib/widgets/metric-card/metric-card.spec.ts +52 -0
- package/src/lib/widgets/metric-card/metric-card.ts +48 -0
- package/src/lib/widgets/quick-actions/quick-actions.descriptor.ts +13 -0
- package/src/lib/widgets/quick-actions/quick-actions.spec.ts +61 -0
- package/src/lib/widgets/quick-actions/quick-actions.ts +100 -0
- package/src/lib/widgets/record-list/record-list.descriptor.ts +16 -0
- package/src/lib/widgets/record-list/record-list.spec.ts +79 -0
- package/src/lib/widgets/record-list/record-list.ts +115 -0
- package/src/lib/widgets/series.spec.ts +42 -0
- package/src/lib/widgets/series.ts +56 -0
- package/src/lib/widgets/status-progress/status-progress.descriptor.ts +19 -0
- package/src/lib/widgets/status-progress/status-progress.spec.ts +63 -0
- package/src/lib/widgets/status-progress/status-progress.ts +104 -0
- package/src/lib/widgets/widget-host/widget-host.spec.ts +179 -0
- package/src/lib/widgets/widget-host/widget-host.ts +99 -0
- package/src/public-api.ts +104 -0
- package/tinya-code-gas-admin-ui-0.2.0-alpha.15.tgz +0 -0
- package/tsconfig.lib.json +13 -0
- package/tsconfig.lib.prod.json +11 -0
- package/tsconfig.spec.json +10 -0
- package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs +0 -84
- package/fesm2022/tinya-code-gas-admin-ui-badge-EeCA_l-o.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs +0 -140
- package/fesm2022/tinya-code-gas-admin-ui-bar-chart-G7sEMSBY.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs +0 -113
- package/fesm2022/tinya-code-gas-admin-ui-boolean-B2ASKL0E.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs +0 -163
- package/fesm2022/tinya-code-gas-admin-ui-button-BQjNW8KB.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs +0 -1027
- package/fesm2022/tinya-code-gas-admin-ui-calendar-view-DCY8bj2V.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs +0 -278
- package/fesm2022/tinya-code-gas-admin-ui-chart-line-BjdlKHoH.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-color-Dljh_By3.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs +0 -101
- package/fesm2022/tinya-code-gas-admin-ui-condition-evaluator-DY2k1M3C.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs +0 -141
- package/fesm2022/tinya-code-gas-admin-ui-currency-BvuF3Z63.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs +0 -88
- package/fesm2022/tinya-code-gas-admin-ui-data-source.service-96FSbN72.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-date-1SJGXZrd.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs +0 -140
- package/fesm2022/tinya-code-gas-admin-ui-display-badge-fwk6sDU7.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs +0 -119
- package/fesm2022/tinya-code-gas-admin-ui-display-date-D7zLiDiG.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs +0 -125
- package/fesm2022/tinya-code-gas-admin-ui-display-file-BiIhqvQC.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs +0 -152
- package/fesm2022/tinya-code-gas-admin-ui-display-image-C1dhnZi1.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs +0 -211
- package/fesm2022/tinya-code-gas-admin-ui-display-list-DOEuUbGe.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs +0 -116
- package/fesm2022/tinya-code-gas-admin-ui-display-number-DDMzr4Hn.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs +0 -111
- package/fesm2022/tinya-code-gas-admin-ui-display-text-B8CdVNpv.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs +0 -122
- package/fesm2022/tinya-code-gas-admin-ui-dropdown-C3blvmE1.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-email-CT_O5ihA.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs +0 -109
- package/fesm2022/tinya-code-gas-admin-ui-empty-state-BMc0QtoT.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs +0 -45
- package/fesm2022/tinya-code-gas-admin-ui-error-mapper.service-DY5y1FjE.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs +0 -50
- package/fesm2022/tinya-code-gas-admin-ui-field-aria-BD1Wiggc.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs +0 -24
- package/fesm2022/tinya-code-gas-admin-ui-field-node-CIK6KNN_.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs +0 -27
- package/fesm2022/tinya-code-gas-admin-ui-field-path-B4-FTBGZ.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs +0 -408
- package/fesm2022/tinya-code-gas-admin-ui-file-Dmxioog7.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs +0 -4516
- package/fesm2022/tinya-code-gas-admin-ui-form-view-C709rc57.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs +0 -82
- package/fesm2022/tinya-code-gas-admin-ui-format-badge-CK8ct5Wk.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs +0 -105
- package/fesm2022/tinya-code-gas-admin-ui-format-boolean-ClzezZz-.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs +0 -65
- package/fesm2022/tinya-code-gas-admin-ui-format-currency-CVmniKml.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs +0 -65
- package/fesm2022/tinya-code-gas-admin-ui-format-date-Dr1YsDd_.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs +0 -65
- package/fesm2022/tinya-code-gas-admin-ui-format-number-CWCjpI4n.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs +0 -59
- package/fesm2022/tinya-code-gas-admin-ui-format-text-DBOgJkH0.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs +0 -94
- package/fesm2022/tinya-code-gas-admin-ui-format-thumbnail-PCRvxWKt.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs +0 -72
- package/fesm2022/tinya-code-gas-admin-ui-gap-sorting-iTdy_DYG.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs +0 -238
- package/fesm2022/tinya-code-gas-admin-ui-group-h7-O8tt4.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs +0 -416
- package/fesm2022/tinya-code-gas-admin-ui-image-7UhJ7nqI.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs +0 -439
- package/fesm2022/tinya-code-gas-admin-ui-kanban-view-Dl0Ol5Kb.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs +0 -513
- package/fesm2022/tinya-code-gas-admin-ui-key-value-nVdgg5at.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs +0 -637
- package/fesm2022/tinya-code-gas-admin-ui-list-C8ijJHAx.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs +0 -127
- package/fesm2022/tinya-code-gas-admin-ui-metric-card-DnGMQL8l.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs +0 -251
- package/fesm2022/tinya-code-gas-admin-ui-multiselect-PB28e-jl.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs +0 -66
- package/fesm2022/tinya-code-gas-admin-ui-notification.service-LjSULER6.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs +0 -114
- package/fesm2022/tinya-code-gas-admin-ui-number-JNr7glc8.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs +0 -116
- package/fesm2022/tinya-code-gas-admin-ui-phone-jR417hPf.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs +0 -235
- package/fesm2022/tinya-code-gas-admin-ui-quick-actions-DCQ6emmF.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs +0 -8
- package/fesm2022/tinya-code-gas-admin-ui-radio-mode-DxvN07DY.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs +0 -124
- package/fesm2022/tinya-code-gas-admin-ui-readonly-text-BL7-4qie.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs +0 -249
- package/fesm2022/tinya-code-gas-admin-ui-record-list-BiZfnd_7.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs +0 -39
- package/fesm2022/tinya-code-gas-admin-ui-relation-drawer.service-B0kT8EiW.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs +0 -309
- package/fesm2022/tinya-code-gas-admin-ui-relation-ykpJ3PYl.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs +0 -576
- package/fesm2022/tinya-code-gas-admin-ui-render-registry-CULkxrSt.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs +0 -161
- package/fesm2022/tinya-code-gas-admin-ui-rich-select-CIHaOrbU.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-rolldown-runtime-DpJrqANO.mjs +0 -11
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs +0 -685
- package/fesm2022/tinya-code-gas-admin-ui-schema-catalog-D0Pk0wAN.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs +0 -80
- package/fesm2022/tinya-code-gas-admin-ui-seed-CcCA3FdQ.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs +0 -296
- package/fesm2022/tinya-code-gas-admin-ui-select-CDwX9yNO.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs +0 -119
- package/fesm2022/tinya-code-gas-admin-ui-skeleton-DnWK0tcZ.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs +0 -158
- package/fesm2022/tinya-code-gas-admin-ui-slug-DU58cbiY.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs +0 -26
- package/fesm2022/tinya-code-gas-admin-ui-source-options-B9R4mw8-.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs +0 -205
- package/fesm2022/tinya-code-gas-admin-ui-status-progress-DmeG8htE.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs +0 -432
- package/fesm2022/tinya-code-gas-admin-ui-string-list-gleasqzD.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs +0 -36
- package/fesm2022/tinya-code-gas-admin-ui-template-interpolator-ewv7CkNX.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-text-CUPmL7W_.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs +0 -223
- package/fesm2022/tinya-code-gas-admin-ui-textarea-CGLSIW13.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs +0 -107
- package/fesm2022/tinya-code-gas-admin-ui-time-BcHzsTWf.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs +0 -60
- package/fesm2022/tinya-code-gas-admin-ui-upload.service-CZPEPCeN.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs +0 -141
- package/fesm2022/tinya-code-gas-admin-ui-url-GO4LZi__.mjs.map +0 -1
- package/fesm2022/tinya-code-gas-admin-ui.mjs +0 -4599
- package/fesm2022/tinya-code-gas-admin-ui.mjs.map +0 -1
- package/types/tinya-code-gas-admin-ui.d.ts +0 -3632
package/README.md
CHANGED
|
@@ -310,6 +310,228 @@ The shell starts at `/login` and, once authenticated, shows the sidebar menu and
|
|
|
310
310
|
|
|
311
311
|
---
|
|
312
312
|
|
|
313
|
+
## Todos los tipos de campo del formulario
|
|
314
|
+
|
|
315
|
+
Esta es la referencia completa de todos los tipos de campo que puedes declarar en `ResourceSchema.fields`. Cada tipo tiene su propio comportamiento de renderizado, validación y propiedades específicas.
|
|
316
|
+
|
|
317
|
+
| Tipo | Descripción | Uso típico |
|
|
318
|
+
|------|-------------|------------|
|
|
319
|
+
| `text` | Input de una línea para texto corto | Nombres, slugs, URLs cortas |
|
|
320
|
+
| `textarea` | Área de texto multilínea | Descripciones largas, notas |
|
|
321
|
+
| `slug` | Slug generado automáticamente de otro campo | Identificadores amigables para SEO |
|
|
322
|
+
| `number` | Campo numérico con controles de incremento | Cantidades, precios, edades |
|
|
323
|
+
| `currency` | Campo monetario con formato de moneda | Precios, montos, saldos |
|
|
324
|
+
| `boolean` | Interruptor (toggle) o casilla de verificación | Estados activos/inactivos |
|
|
325
|
+
| `select` | Desplegable con radios para 2-4 opciones o dropdown para más | Estados, categorías pequeñas |
|
|
326
|
+
| `dropdown` | Desplegable nativo (`<select>`) SIEMPRE, sin mutar a radios | Estados con muchas opciones |
|
|
327
|
+
| `rich-select` | Selector visual con tarjetas y preview HTML personalizado | Opciones con iconos, colores, estados visuales |
|
|
328
|
+
| `multiselect` | Selección múltiple de opciones | Tags, etiquetas múltiples |
|
|
329
|
+
| `url` | Campo para URLs válidas | Enlaces externos, sitios web |
|
|
330
|
+
| `email` | Campo de email con validación de formato | Correos electrónicos |
|
|
331
|
+
| `phone` | Campo de teléfono con formato telefónico | Números de teléfono |
|
|
332
|
+
| `date` | Selector de fecha (ISO `YYYY-MM-DD`) | Fechas de nacimiento, citas |
|
|
333
|
+
| `time` | Selector de hora | Horas de apertura, citas |
|
|
334
|
+
| `readonly-text` | Texto de solo lectura que no se envía al back | IDs generados, timestamps |
|
|
335
|
+
| `relation` | Relación con otro recurso (fuente de datos) | Clientes, productos, usuarios relacionados |
|
|
336
|
+
| `image` | Subida y vista previa de imágenes | Logos, fotos de perfil |
|
|
337
|
+
| `file` | Subida de archivos genéricos (PDF, DOCX, etc.) | Documentos, adjuntos |
|
|
338
|
+
| `group` | Grupo de campos con tarjeta, colapsable o inline | Secciones de formulario relacionadas |
|
|
339
|
+
| `list` | Lista repetible de campos (items) | Múltiples direcciones, teléfonos |
|
|
340
|
+
| `string-list` | Lista simple de strings | Tags simples, palabras clave |
|
|
341
|
+
| `key-value` | Tabla de pares clave-valor | Metadatos, configuraciones personalizadas |
|
|
342
|
+
| `color` | Selector de color nativo (`<input type="color">`) | Colores primarios, secundarios, temas |
|
|
343
|
+
|
|
344
|
+
### Ejemplos por tipo
|
|
345
|
+
|
|
346
|
+
A continuación verás cómo declarar cada tipo en tu schema, con sus propiedades específicas:
|
|
347
|
+
|
|
348
|
+
#### `text` — Input de una línea
|
|
349
|
+
|
|
350
|
+
```ts
|
|
351
|
+
{ key: 'name', type: 'text', label: 'Nombre del Negocio', required: true, validators: { minLength: 2, maxLength: 120 }, section: 'general', width: 6 }
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
#### `textarea` — Área de texto multilínea
|
|
355
|
+
|
|
356
|
+
```ts
|
|
357
|
+
{ key: 'description', type: 'textarea', label: 'Descripción', validators: { maxLength: 500 }, section: 'general', width: 12 }
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
#### `slug` — Identificador amigable
|
|
361
|
+
|
|
362
|
+
```ts
|
|
363
|
+
{ key: 'slug', type: 'slug', from: 'name', label: 'Slug', required: true, lockAfterCreate: true, section: 'general', width: 6 }
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
#### `number` — Campo numérico
|
|
367
|
+
|
|
368
|
+
```ts
|
|
369
|
+
{ key: 'minVisits', type: 'number', label: 'Visitas Mínimas', required: true, min: 0, section: 'general', width: 6 }
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
#### `currency` — Campo monetario
|
|
373
|
+
|
|
374
|
+
```ts
|
|
375
|
+
{ key: 'price', type: 'currency', label: 'Precio', validators: { min: 0 }, section: 'pricing', width: 6 }
|
|
376
|
+
```
|
|
377
|
+
|
|
378
|
+
#### `boolean` — Interruptor / Casilla
|
|
379
|
+
|
|
380
|
+
```ts
|
|
381
|
+
{ key: 'active', type: 'boolean', label: 'Activo', section: 'general', width: 3 }
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### `select` — Desplegable (radios si 2-4 opciones, dropdown si más)
|
|
385
|
+
|
|
386
|
+
```ts
|
|
387
|
+
{ key: 'status', type: 'select', label: 'Estado', required: true, options: [
|
|
388
|
+
{ value: 'active', label: 'Activo' },
|
|
389
|
+
{ value: 'inactive', label: 'Inactivo' },
|
|
390
|
+
{ value: 'pending', label: 'Pendiente' },
|
|
391
|
+
], section: 'general', width: 6 }
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
#### `dropdown` — Desplegable nativo SIEMPRE
|
|
395
|
+
|
|
396
|
+
```ts
|
|
397
|
+
{ key: 'timezone', type: 'dropdown', label: 'Zona Horaria', required: true, section: 'general', width: 6, default: 'America/Lima', options: [
|
|
398
|
+
{ value: 'America/Lima', label: 'Lima (Perú)' },
|
|
399
|
+
{ value: 'America/Bogota', label: 'Bogotá (Colombia)' },
|
|
400
|
+
{ value: 'America/Mexico_City', label: 'Ciudad de México' },
|
|
401
|
+
], }
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### `rich-select` — Selector visual con tarjetas
|
|
405
|
+
|
|
406
|
+
```ts
|
|
407
|
+
{ key: 'cardStyle', type: 'rich-select', label: 'Estilo de Tarjeta', cardWidth: 3, section: 'branding', width: 12,
|
|
408
|
+
options: [
|
|
409
|
+
{ value: 'none', label: 'Ninguno', preview: '<span class="w-8 h-8 bg-amber-300 rounded-sm flex items-center justify-center text-xs font-medium text-amber-900">Ninguna</span>' },
|
|
410
|
+
{ value: 'monthly', label: 'Mensual', preview: '<span class="w-8 h-8 bg-blue-100 rounded-sm flex items-center justify-center text-xs font-medium text-blue-900">Mensual</span>' },
|
|
411
|
+
{ value: 'yearly', label: 'Anual', preview: '<span class="w-8 h-8 bg-green-100 rounded-sm flex items-center justify-center text-xs font-medium text-green-900">Anual</span>' },
|
|
412
|
+
], }
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
#### `multiselect` — Selección múltiple
|
|
416
|
+
|
|
417
|
+
```ts
|
|
418
|
+
{ key: 'tags', type: 'multiselect', label: 'Etiquetas', section: 'tags', width: 12, options: [
|
|
419
|
+
{ value: 'vip', label: 'Cliente VIP' },
|
|
420
|
+
{ value: 'new', label: 'Cliente Nuevo' },
|
|
421
|
+
{ value: 'inactive', label: 'Inactivo' },
|
|
422
|
+
], }
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
#### `url` — Campo de URL
|
|
426
|
+
|
|
427
|
+
```ts
|
|
428
|
+
{ key: 'website', type: 'url', label: 'Sitio Web', placeholder: 'https://ejemplo.com', section: 'contact', width: 12 }
|
|
429
|
+
```
|
|
430
|
+
|
|
431
|
+
#### `email` — Campo de correo electrónico
|
|
432
|
+
|
|
433
|
+
```ts
|
|
434
|
+
{ key: 'email', type: 'email', label: 'Correo Electrónico', required: true, section: 'contact', width: 12 }
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
#### `phone` — Campo de teléfono
|
|
438
|
+
|
|
439
|
+
```ts
|
|
440
|
+
{ key: 'phone', type: 'phone', label: 'Teléfono', section: 'contact', width: 6 }
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
#### `date` — Selector de fecha
|
|
444
|
+
|
|
445
|
+
```ts
|
|
446
|
+
{ key: 'birthDate', type: 'date', label: 'Fecha de Nacimiento', section: 'personal', width: 6 }
|
|
447
|
+
```
|
|
448
|
+
|
|
449
|
+
#### `time` — Selector de hora
|
|
450
|
+
|
|
451
|
+
```ts
|
|
452
|
+
{ key: 'openingTime', type: 'time', label: 'Hora de Apertura', section: 'hours', width: 6 }
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### `readonly-text` — Texto de solo lectura
|
|
456
|
+
|
|
457
|
+
```ts
|
|
458
|
+
{ key: 'memberId', type: 'readonly-text', label: 'ID Membresía', readonly: true, section: 'general', width: 6 }
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
#### `relation` — Relación con otro recurso
|
|
462
|
+
|
|
463
|
+
```ts
|
|
464
|
+
{ key: 'clientId', type: 'relation', label: 'Cliente', source: { resource: 'clients', labelField: 'name', valueField: 'membershipId' }, section: 'association', width: 12 }
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
#### `image` — Subida de imagen
|
|
468
|
+
|
|
469
|
+
```ts
|
|
470
|
+
{ key: 'logoUrl', type: 'image', label: 'Logo', section: 'branding', width: 12, accept: 'image/*', maxSizeMB: 2 }
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### `file` — Subida de archivo genérico
|
|
474
|
+
|
|
475
|
+
```ts
|
|
476
|
+
{ key: 'document', type: 'file', label: 'Documento', section: 'files', width: 12, accept: '.pdf,.doc,.docx', maxSizeMB: 5 }
|
|
477
|
+
```
|
|
478
|
+
|
|
479
|
+
#### `group` — Grupo de campos
|
|
480
|
+
|
|
481
|
+
```ts
|
|
482
|
+
{ key: 'address', type: 'group', label: 'Dirección', display: 'card', section: 'location', width: 12, fields: [
|
|
483
|
+
{ key: 'street', type: 'text', label: 'Calle', width: 6 },
|
|
484
|
+
{ key: 'city', type: 'text', label: 'Ciudad', width: 6 },
|
|
485
|
+
]},
|
|
486
|
+
```
|
|
487
|
+
|
|
488
|
+
#### `list` — Lista repetible de items
|
|
489
|
+
|
|
490
|
+
```ts
|
|
491
|
+
{ key: 'phones', type: 'list', label: 'Teléfonos', itemFields: [
|
|
492
|
+
{ key: 'number', type: 'phone', label: 'Número', width: 6 },
|
|
493
|
+
{ key: 'type', type: 'select', label: 'Tipo', options: [{ value: 'mobile', label: 'Móvil' }, { value: 'home', label: 'Hogar' }], width: 6 },
|
|
494
|
+
], section: 'contact', width: 12 }
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
#### `string-list` — Lista simple de strings
|
|
498
|
+
|
|
499
|
+
```ts
|
|
500
|
+
{ key: 'skills', type: 'string-list', label: 'Habilidades', itemType: 'text', section: 'skills', width: 12 }
|
|
501
|
+
```
|
|
502
|
+
|
|
503
|
+
#### `key-value` — Pares clave-valor
|
|
504
|
+
|
|
505
|
+
```ts
|
|
506
|
+
{ key: 'metadata', type: 'key-value', label: 'Metadatos', section: 'advanced', width: 12 }
|
|
507
|
+
```
|
|
508
|
+
|
|
509
|
+
#### `color` — Selector de color
|
|
510
|
+
|
|
511
|
+
```ts
|
|
512
|
+
{ key: 'colorPrimary', type: 'color', label: 'Color Primario', placeholder: '#4A2C11', section: 'branding', width: 6, default: '#4A2C11' }
|
|
513
|
+
```
|
|
514
|
+
|
|
515
|
+
### Propiedades comunes a todos los campos
|
|
516
|
+
|
|
517
|
+
| Propiedad | Tipo | Descripción |
|
|
518
|
+
|-----------|------|-------------|
|
|
519
|
+
| `key` | `string` | Nombre de la propiedad en el JSON que envía/recibe el back. |
|
|
520
|
+
| `label` | `string` | Rótulo visible para el usuario en el formulario y la ficha. |
|
|
521
|
+
| `help` | `string` | Texto corto de ayuda que se muestra debajo del campo. |
|
|
522
|
+
| `placeholder` | `string` | Texto sugerente que se muestra dentro del input cuando está vacío. |
|
|
523
|
+
| `required` | `boolean` | Si es obligatorio. El back también lo valida (R7.4). |
|
|
524
|
+
| `default` | `any` | Valor por defecto si el registro no trae este campo (ej. `default: 'none'` en un `select`/`dropdown`). |
|
|
525
|
+
| `readonly` | `boolean` | Si el campo es solo lectura (no editable en el formulario). |
|
|
526
|
+
| `width` | `1-12` | Ancho en la grilla del formulario (1–12), igual que un widget del dashboard. |
|
|
527
|
+
| `section` | `string` | Id de la sección/pestaña del layout a la que pertenece (base.md §4). |
|
|
528
|
+
| `description` | `string` | Descripción corta de para qué sirve este campo. Se muestra como tooltip al pasar el cursor sobre el rótulo. |
|
|
529
|
+
| `docLink` | `string` | URL de documentación externa para este campo (se abre al hacer clic en el tooltip). |
|
|
530
|
+
| `validators` | `object` | Reglas de validación (`minLength`, `maxLength`, `pattern`, `min`, `max`, `unique`). |
|
|
531
|
+
| `access` | `object` | Nivel de acceso requerido (`read`, `write`) — solo UX, el back vuelve a verificar en cada petición. |
|
|
532
|
+
|
|
533
|
+
---
|
|
534
|
+
|
|
313
535
|
## Schema examples (real implementations)
|
|
314
536
|
|
|
315
537
|
The examples below are taken from a production consumer of this library.
|
package/ng-package.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tinya-code/gas-admin-ui",
|
|
3
|
-
"version": "0.2.0-alpha.
|
|
3
|
+
"version": "0.2.0-alpha.15",
|
|
4
4
|
"description": "Motor de render Angular para admin CRUD: declará ResourceSchema y el motor dibuja vistas, campos y widgets.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -28,17 +28,5 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"tslib": "^2.3.0"
|
|
30
30
|
},
|
|
31
|
-
"sideEffects": false
|
|
32
|
-
|
|
33
|
-
"typings": "types/tinya-code-gas-admin-ui.d.ts",
|
|
34
|
-
"exports": {
|
|
35
|
-
"./package.json": {
|
|
36
|
-
"default": "./package.json"
|
|
37
|
-
},
|
|
38
|
-
".": {
|
|
39
|
-
"types": "./types/tinya-code-gas-admin-ui.d.ts",
|
|
40
|
-
"default": "./fesm2022/tinya-code-gas-admin-ui.mjs"
|
|
41
|
-
}
|
|
42
|
-
},
|
|
43
|
-
"type": "module"
|
|
44
|
-
}
|
|
31
|
+
"sideEffects": false
|
|
32
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
actionType,
|
|
5
|
+
actionsFor,
|
|
6
|
+
getAction,
|
|
7
|
+
registerAction,
|
|
8
|
+
type ActionDescriptor,
|
|
9
|
+
} from './action-registry';
|
|
10
|
+
|
|
11
|
+
function descriptor(
|
|
12
|
+
type: string,
|
|
13
|
+
surfaces: ActionDescriptor['surfaces'] = ['row'],
|
|
14
|
+
): ActionDescriptor {
|
|
15
|
+
return { type, surfaces, run: async () => ({ ok: true }) };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
describe('action-registry', () => {
|
|
19
|
+
it('registra un descriptor y lo devuelve por su tipo', () => {
|
|
20
|
+
registerAction(descriptor('compartir'));
|
|
21
|
+
expect(getAction('compartir')?.type).toBe('compartir');
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
it('devuelve undefined para un tipo sin registrar', () => {
|
|
25
|
+
expect(getAction('sin-registrar')).toBeUndefined();
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('al re-registrar un tipo reemplaza el descriptor anterior', () => {
|
|
29
|
+
registerAction(descriptor('navegar', ['detail']));
|
|
30
|
+
registerAction(descriptor('navegar', ['row', 'detail']));
|
|
31
|
+
expect(getAction('navegar')?.surfaces).toEqual(['row', 'detail']);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('lista sólo los descriptores cuya superficie coincide', () => {
|
|
35
|
+
registerAction(descriptor('cabecera', ['header']));
|
|
36
|
+
expect(actionsFor('header').map((d) => d.type)).toEqual(['cabecera']);
|
|
37
|
+
expect(actionsFor('bulk')).toEqual([]);
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe('actionType', () => {
|
|
42
|
+
it('usa el tipo declarado', () => {
|
|
43
|
+
expect(actionType({ id: 'x', label: 'X', type: 'trigger' })).toBe('trigger');
|
|
44
|
+
expect(actionType({ id: 'x', label: 'X', type: 'copy-share', urlTemplate: '/p/{ref}' })).toBe(
|
|
45
|
+
'copy-share',
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it('una acción sin type es una navegación (NavigateAction.type es opcional)', () => {
|
|
50
|
+
expect(actionType({ id: 'x', label: 'X', urlTemplate: '/p/{ref}' })).toBe('navigate');
|
|
51
|
+
});
|
|
52
|
+
});
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Router } from '@angular/router';
|
|
2
|
+
|
|
3
|
+
import type { LucideIcon } from '@lucide/angular';
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
actionDescriptors,
|
|
7
|
+
isShareAction,
|
|
8
|
+
isTriggerAction,
|
|
9
|
+
resolveShare,
|
|
10
|
+
type ResolvedShare,
|
|
11
|
+
} from './descriptors';
|
|
12
|
+
import type { ResourceAction } from '../core/models/schema.model';
|
|
13
|
+
import type { SessionUser } from '../core/services/auth.service';
|
|
14
|
+
import type { ApiService } from '../core/services/api.service';
|
|
15
|
+
import type { NotificationService } from '../core/services/notification.service';
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Registro único de acciones (R3.9 · `refactormotor.md`).
|
|
19
|
+
*
|
|
20
|
+
* Es el gemelo del registro de render (`core/registry/render-registry.ts`):
|
|
21
|
+
* en lugar de «qué componente pinta un tipo de campo», responde «qué
|
|
22
|
+
* comportamiento ejecuta un tipo de acción» y permite **migrar el dispatch
|
|
23
|
+
* if/copiado-pegado de las vistas (R3.11) a datos + un solo `run`**.
|
|
24
|
+
*
|
|
25
|
+
* Como los descriptores son objetos planos de módulo (no los inyecta Angular),
|
|
26
|
+
* todo lo que necesiten llegar por el `ActionContext`: los puertos (`api`,
|
|
27
|
+
* `router`, `toast`) y los datos (`record`, `resource`, `vars`).
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Superficies donde una acción puede aparecer. */
|
|
31
|
+
export type ActionSurface = 'row' | 'detail' | 'header' | 'bulk' | 'post-create';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Lo que aporta el llamador al ejecutar una acción: el registro sobre el que
|
|
35
|
+
* corre y los puertos que el descriptor usará para actuar.
|
|
36
|
+
*
|
|
37
|
+
* `record` es `null` en las superficies sin fila (`header`, `bulk`), para que
|
|
38
|
+
* un descriptor pueda distinguir «sin registro» de «registro vacío».
|
|
39
|
+
*/
|
|
40
|
+
export interface ActionRequest {
|
|
41
|
+
record: Record<string, unknown> | null;
|
|
42
|
+
/** Id del recurso (schema) dueño de la acción. */
|
|
43
|
+
resource: string;
|
|
44
|
+
api: ApiService;
|
|
45
|
+
router: Router;
|
|
46
|
+
toast: NotificationService;
|
|
47
|
+
user: SessionUser | null;
|
|
48
|
+
/**
|
|
49
|
+
* `keyField` del schema. **R3.12 (desviación nº 3 del contrato):** con
|
|
50
|
+
* `record` forma la clave que necesita la ruta lógica de `trigger`
|
|
51
|
+
* (`/admin/<recurso>/<key>/actions/<name>`). Lo aporta la vista, que es
|
|
52
|
+
* quien tiene el schema.
|
|
53
|
+
*/
|
|
54
|
+
keyField: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Contexto que recibe el descriptor: la petición del llamador más el contexto
|
|
59
|
+
* de interpolación ya resuelto. `ActionService.run` lo arma, así que ninguna
|
|
60
|
+
* vista repite `buildInterpolationContext(record)`.
|
|
61
|
+
*/
|
|
62
|
+
export interface ActionContext extends ActionRequest {
|
|
63
|
+
/** `{...configGlobal, ...record, public_base_url}` — listo para las plantillas. */
|
|
64
|
+
vars: Record<string, unknown>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Resultado que la vista puede leer para saber si debe recargar. */
|
|
68
|
+
export interface ActionResult {
|
|
69
|
+
/** `false` cuando la acción no llegó a ejecutarse; el error ya se notificó. */
|
|
70
|
+
ok: boolean;
|
|
71
|
+
/** `true` cuando tocó datos y la vista debe recargar su listado/detalle. */
|
|
72
|
+
reload?: boolean;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export interface ActionDescriptor {
|
|
76
|
+
/** Clave del dispatch: el `type` del schema (`'navigate'` no lo declara). */
|
|
77
|
+
type: string;
|
|
78
|
+
/** Superficies donde esta acción puede aparecer. */
|
|
79
|
+
surfaces: ActionSurface[];
|
|
80
|
+
run: (ctx: ActionContext, action: ResourceAction) => Promise<ActionResult>;
|
|
81
|
+
/** `true` si el descriptor exige confirmación antes de ejecutar. */
|
|
82
|
+
confirm?: boolean;
|
|
83
|
+
/**
|
|
84
|
+
* Icono lucide que representa la acción donde el espacio es reducido
|
|
85
|
+
* (la fila del listado). Sin él, la vista muestra `action.label`.
|
|
86
|
+
*/
|
|
87
|
+
icon?: LucideIcon;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export { isShareAction, isTriggerAction, resolveShare, type ResolvedShare };
|
|
91
|
+
|
|
92
|
+
const ACCIONES = new Map<string, ActionDescriptor>();
|
|
93
|
+
|
|
94
|
+
/** Registra (o reemplaza) un descriptor de acción. */
|
|
95
|
+
export function registerAction(descriptor: ActionDescriptor): void {
|
|
96
|
+
ACCIONES.set(descriptor.type, descriptor);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function getAction(type: string): ActionDescriptor | undefined {
|
|
100
|
+
return ACCIONES.get(type);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** Descriptores disponibles en una superficie. */
|
|
104
|
+
export function actionsFor(surface: ActionSurface): ActionDescriptor[] {
|
|
105
|
+
return [...ACCIONES.values()].filter((descriptor) => descriptor.surfaces.includes(surface));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Clave de dispatch de una acción. `NavigateAction.type` es **opcional**
|
|
110
|
+
* (schema.model.ts), así que una acción sin `type` es una navegación.
|
|
111
|
+
*/
|
|
112
|
+
export function actionType(action: ResourceAction): string {
|
|
113
|
+
return action.type ?? 'navigate';
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/** Tipos registrados. Sólo para comprobaciones y tests. */
|
|
117
|
+
export function actionTypes(): string[] {
|
|
118
|
+
return [...ACCIONES.keys()];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Catálogo a scope de módulo: se registra al importar, sin esperar a DI,
|
|
122
|
+
// igual que el registro de render (mismo motivo: `buildFormSchema()` es una
|
|
123
|
+
// función pura que consulta sin inyectar nada).
|
|
124
|
+
for (const descriptor of actionDescriptors) {
|
|
125
|
+
registerAction(descriptor);
|
|
126
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { TestBed } from '@angular/core/testing';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { ActionService } from './action-service';
|
|
5
|
+
import { registerAction, type ActionContext, type ActionDescriptor } from './action-registry';
|
|
6
|
+
import { actionDescriptors } from './descriptors';
|
|
7
|
+
import type { ResourceAction } from '../core/models/schema.model';
|
|
8
|
+
import type { ApiService } from '../core/services/api.service';
|
|
9
|
+
import { ConfigService } from '../core/services/config.service';
|
|
10
|
+
import type { NotificationService } from '../core/services/notification.service';
|
|
11
|
+
import type { Router } from '@angular/router';
|
|
12
|
+
|
|
13
|
+
/** Notificador falso: sólo el contrato que `ActionService` usa. */
|
|
14
|
+
function fakeToast(): NotificationService & { confirm: ReturnType<typeof vi.fn> } {
|
|
15
|
+
return {
|
|
16
|
+
confirm: vi.fn().mockResolvedValue(true),
|
|
17
|
+
error: vi.fn(),
|
|
18
|
+
success: vi.fn(),
|
|
19
|
+
info: vi.fn(),
|
|
20
|
+
dismiss: vi.fn(),
|
|
21
|
+
} as unknown as NotificationService & { confirm: ReturnType<typeof vi.fn> };
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function fakeRequest(toast: NotificationService) {
|
|
25
|
+
return {
|
|
26
|
+
record: { slug: 'tecla', ref: 'p1' },
|
|
27
|
+
resource: 'productos',
|
|
28
|
+
api: {} as ApiService,
|
|
29
|
+
router: { navigate: async () => true } as unknown as Router,
|
|
30
|
+
toast,
|
|
31
|
+
user: null,
|
|
32
|
+
keyField: 'ref',
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const COMPARTIR: ResourceAction = {
|
|
37
|
+
id: 'compartir',
|
|
38
|
+
label: 'Compartir',
|
|
39
|
+
type: 'copy-share',
|
|
40
|
+
urlTemplate: 'https://demo/p/{ref}',
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
describe('ActionService', () => {
|
|
44
|
+
let service: ActionService;
|
|
45
|
+
let toast: ReturnType<typeof fakeToast>;
|
|
46
|
+
const ensureLoaded = vi.fn<() => Promise<void>>().mockResolvedValue(undefined);
|
|
47
|
+
|
|
48
|
+
beforeEach(() => {
|
|
49
|
+
toast = fakeToast();
|
|
50
|
+
ensureLoaded.mockClear();
|
|
51
|
+
ensureLoaded.mockResolvedValue(undefined);
|
|
52
|
+
TestBed.configureTestingModule({
|
|
53
|
+
providers: [
|
|
54
|
+
// ConfigService real pediría HttpClient/API_URL; sólo aporta `vars`
|
|
55
|
+
// y el reintento de config que `copy-share` dispara.
|
|
56
|
+
{
|
|
57
|
+
provide: ConfigService,
|
|
58
|
+
useValue: {
|
|
59
|
+
ensureLoaded,
|
|
60
|
+
buildInterpolationContext: (record: Record<string, unknown>) => ({
|
|
61
|
+
...record,
|
|
62
|
+
public_base_url: 'https://demo',
|
|
63
|
+
}),
|
|
64
|
+
},
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
});
|
|
68
|
+
service = TestBed.inject(ActionService);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
// El `Map` del registro es estado de módulo **compartido entre ficheros**
|
|
72
|
+
// cuando vitest los ejecuta en el mismo worker: varios tests de éste
|
|
73
|
+
// sobrescriben `copy-share` / `navigate` / `trigger` con dobles.
|
|
74
|
+
// Sin restaurar aquí, `contract.spec` y `descriptors.spec` leen esos dobles
|
|
75
|
+
// y la suite se pone flaka **según el orden de ficheros** (falló 4 tests de
|
|
76
|
+
// `descriptors.spec` en una corrida y 0 en la siguiente, sin tocar código).
|
|
77
|
+
// Quien ensucia es quien limpia: el estado por defecto es el catálogo real.
|
|
78
|
+
afterEach(() => {
|
|
79
|
+
for (const descriptor of actionDescriptors) registerAction(descriptor);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it('ejecuta el descriptor y le entrega la petición + vars de interpolación', async () => {
|
|
83
|
+
const vistos: ActionContext[] = [];
|
|
84
|
+
registerAction({
|
|
85
|
+
type: 'copy-share',
|
|
86
|
+
surfaces: ['row', 'post-create'],
|
|
87
|
+
run: async (ctx) => {
|
|
88
|
+
vistos.push(ctx);
|
|
89
|
+
return { ok: true, reload: true };
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
const resultado = await service.run(COMPARTIR, fakeRequest(toast));
|
|
94
|
+
|
|
95
|
+
expect(resultado).toEqual({ ok: true, reload: true });
|
|
96
|
+
expect(vistos).toHaveLength(1);
|
|
97
|
+
expect(vistos[0].record).toEqual({ slug: 'tecla', ref: 'p1' });
|
|
98
|
+
expect(vistos[0].resource).toBe('productos');
|
|
99
|
+
expect(vistos[0].vars).toEqual({ slug: 'tecla', ref: 'p1', public_base_url: 'https://demo' });
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it('copy-share reintenta la config global ANTES de resolver vars', async () => {
|
|
103
|
+
// Regresión del «mensaje = sólo la URL»: si el load del bootstrap falló,
|
|
104
|
+
// el descriptor debe ver la config ya recargada, no el snapshot vacío.
|
|
105
|
+
let ensureHecho = false;
|
|
106
|
+
ensureLoaded.mockImplementation(async () => {
|
|
107
|
+
ensureHecho = true;
|
|
108
|
+
});
|
|
109
|
+
const varsAlEjecutar: Record<string, unknown>[] = [];
|
|
110
|
+
registerAction({
|
|
111
|
+
type: 'copy-share',
|
|
112
|
+
surfaces: ['row'],
|
|
113
|
+
run: async (ctx) => {
|
|
114
|
+
expect(ensureHecho).toBe(true);
|
|
115
|
+
varsAlEjecutar.push(ctx.vars);
|
|
116
|
+
return { ok: true };
|
|
117
|
+
},
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
await service.run(COMPARTIR, fakeRequest(toast));
|
|
121
|
+
|
|
122
|
+
expect(ensureLoaded).toHaveBeenCalledOnce();
|
|
123
|
+
expect(varsAlEjecutar[0]).toEqual({
|
|
124
|
+
slug: 'tecla',
|
|
125
|
+
ref: 'p1',
|
|
126
|
+
public_base_url: 'https://demo',
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it('acciones que no son copy-share no tocan la config', async () => {
|
|
131
|
+
const run = vi.fn<NonNullable<ActionDescriptor['run']>>().mockResolvedValue({ ok: true });
|
|
132
|
+
registerAction({ type: 'navigate', surfaces: ['row'], run });
|
|
133
|
+
|
|
134
|
+
await service.run({ id: 'ver', label: 'Ver', urlTemplate: '/p/{ref}' }, fakeRequest(toast));
|
|
135
|
+
|
|
136
|
+
expect(ensureLoaded).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('una acción sin type resuelve el descriptor navigate', async () => {
|
|
140
|
+
const run = vi.fn<NonNullable<ActionDescriptor['run']>>();
|
|
141
|
+
registerAction({ type: 'navigate', surfaces: ['row', 'detail'], run });
|
|
142
|
+
|
|
143
|
+
await service.run({ id: 'ver', label: 'Ver', urlTemplate: '/p/{ref}' }, fakeRequest(toast));
|
|
144
|
+
|
|
145
|
+
expect(run).toHaveBeenCalledOnce();
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
it('sin descriptor registrado rompe en lugar de ignorarse', async () => {
|
|
149
|
+
// `ResourceAction` es una unión cerrada y el registro es estado de
|
|
150
|
+
// módulo compartido entre tests: se fuerza un type que nadie registra
|
|
151
|
+
// (el cast a `unknown` sólo salva al compilador, no cambia el código).
|
|
152
|
+
const desconocida = { id: 'x', label: 'X', type: 'nadie' } as unknown as ResourceAction;
|
|
153
|
+
await expect(service.run(desconocida, fakeRequest(toast))).rejects.toThrow(
|
|
154
|
+
'no tiene descriptor registrado',
|
|
155
|
+
);
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
it('con confirmación rechazada no ejecuta y devuelve ok false', async () => {
|
|
159
|
+
toast.confirm.mockResolvedValue(false);
|
|
160
|
+
const run = vi.fn<NonNullable<ActionDescriptor['run']>>();
|
|
161
|
+
registerAction({
|
|
162
|
+
type: 'trigger',
|
|
163
|
+
surfaces: ['row'],
|
|
164
|
+
confirm: true,
|
|
165
|
+
run,
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
const resultado = await service.run(
|
|
169
|
+
{
|
|
170
|
+
id: 'publicar',
|
|
171
|
+
label: 'Publicar',
|
|
172
|
+
type: 'trigger',
|
|
173
|
+
confirmMessage: 'Se publicará para todos.',
|
|
174
|
+
},
|
|
175
|
+
fakeRequest(toast),
|
|
176
|
+
);
|
|
177
|
+
|
|
178
|
+
expect(toast.confirm).toHaveBeenCalledWith({
|
|
179
|
+
title: 'Publicar',
|
|
180
|
+
message: 'Se publicará para todos.',
|
|
181
|
+
confirmLabel: 'Confirmar',
|
|
182
|
+
cancelLabel: 'Cancelar',
|
|
183
|
+
});
|
|
184
|
+
expect(run).not.toHaveBeenCalled();
|
|
185
|
+
expect(resultado).toEqual({ ok: false });
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
it('con confirmación aceptada ejecuta el descriptor', async () => {
|
|
189
|
+
const run = vi.fn<NonNullable<ActionDescriptor['run']>>().mockResolvedValue({ ok: true });
|
|
190
|
+
registerAction({ type: 'trigger', surfaces: ['row'], confirm: true, run });
|
|
191
|
+
|
|
192
|
+
const resultado = await service.run(
|
|
193
|
+
{ id: 'duplicar', label: 'Duplicar', type: 'trigger' },
|
|
194
|
+
fakeRequest(toast),
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
expect(run).toHaveBeenCalledOnce();
|
|
198
|
+
expect(resultado).toEqual({ ok: true });
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('un error del descriptor se notifica y no se devuelve como éxito', async () => {
|
|
202
|
+
registerAction({
|
|
203
|
+
type: 'copy-share',
|
|
204
|
+
surfaces: ['row'],
|
|
205
|
+
run: async () => {
|
|
206
|
+
throw new Error('El backend respondió 500');
|
|
207
|
+
},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const resultado = await service.run(COMPARTIR, fakeRequest(toast));
|
|
211
|
+
|
|
212
|
+
expect(toast.error).toHaveBeenCalledWith('El backend respondió 500');
|
|
213
|
+
expect(resultado).toEqual({ ok: false });
|
|
214
|
+
});
|
|
215
|
+
});
|