@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
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resuelve una plantilla de texto reemplazando marcadores `{key}` o `{nested.key}`
|
|
3
|
+
* con los valores presentes en el objeto `data` o en el contexto adicional `extraContext`.
|
|
4
|
+
*
|
|
5
|
+
* Si un valor es `null` o `undefined`, se reemplaza por una cadena vacía.
|
|
6
|
+
* No arroja excepciones ante claves no encontradas ni ante objetos nulos.
|
|
7
|
+
*/
|
|
8
|
+
export function interpolateTemplate(
|
|
9
|
+
template: string,
|
|
10
|
+
data: Record<string, unknown> = {},
|
|
11
|
+
extraContext: Record<string, unknown> = {},
|
|
12
|
+
): string {
|
|
13
|
+
if (!template) return '';
|
|
14
|
+
|
|
15
|
+
return template.replace(/\{([a-zA-Z0-9_.]+)\}/g, (match, key: string) => {
|
|
16
|
+
const val = getDeepValue(data, key) ?? getDeepValue(extraContext, key);
|
|
17
|
+
if (val === null || val === undefined) {
|
|
18
|
+
return '';
|
|
19
|
+
}
|
|
20
|
+
return String(val);
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getDeepValue(obj: Record<string, unknown>, path: string): unknown {
|
|
25
|
+
if (!obj || typeof obj !== 'object') return undefined;
|
|
26
|
+
const parts = path.split('.');
|
|
27
|
+
let current: unknown = obj;
|
|
28
|
+
for (const part of parts) {
|
|
29
|
+
if (current === null || current === undefined || typeof current !== 'object') {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
current = (current as Record<string, unknown>)[part];
|
|
33
|
+
}
|
|
34
|
+
return current;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface ParsedRoute {
|
|
38
|
+
path: string;
|
|
39
|
+
queryParams: Record<string, string>;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Descompone una ruta relativa o absoluta en path y queryParams para routerLink.
|
|
44
|
+
*/
|
|
45
|
+
export function parseNavigationUrl(url: string): ParsedRoute {
|
|
46
|
+
if (!url) return { path: '', queryParams: {} };
|
|
47
|
+
const [path, queryString] = url.split('?');
|
|
48
|
+
const queryParams: Record<string, string> = {};
|
|
49
|
+
if (queryString) {
|
|
50
|
+
const params = new URLSearchParams(queryString);
|
|
51
|
+
params.forEach((value, key) => {
|
|
52
|
+
queryParams[key] = value;
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
return { path: path || '', queryParams };
|
|
56
|
+
}
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { setSchemaCatalog } from '../registry/schema-catalog';
|
|
2
|
+
import { testCatalog } from '../testing/test-catalog';
|
|
3
|
+
|
|
4
|
+
setSchemaCatalog(testCatalog);
|
|
5
|
+
|
|
6
|
+
import { Component } from '@angular/core';
|
|
7
|
+
import { TestBed } from '@angular/core/testing';
|
|
8
|
+
import { provideRouter, Router } from '@angular/router';
|
|
9
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
10
|
+
|
|
11
|
+
import { roleGuard } from '../guards/auth.guard';
|
|
12
|
+
import type { NavigationTarget, SelectionBinding } from '../models/schema.model';
|
|
13
|
+
import { AuthService } from '../services/auth.service';
|
|
14
|
+
import {
|
|
15
|
+
buildSegments,
|
|
16
|
+
targetRoutePattern,
|
|
17
|
+
ViewNavigationService,
|
|
18
|
+
} from './view-navigation.service';
|
|
19
|
+
|
|
20
|
+
@Component({ standalone: true, template: 'lista' })
|
|
21
|
+
class ListaStub {}
|
|
22
|
+
|
|
23
|
+
@Component({ standalone: true, template: 'detalle' })
|
|
24
|
+
class DetalleStub {}
|
|
25
|
+
|
|
26
|
+
/** Target declarado sobre el schema real `orders` (registry front). */
|
|
27
|
+
const target: NavigationTarget = { resource: 'orders', view: 'detail', params: ['key'] };
|
|
28
|
+
|
|
29
|
+
function rutaBinding(mode: 'route'): SelectionBinding {
|
|
30
|
+
return { on: 'open', mode, target };
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
describe('buildSegments (key-only, sin concat de strings)', () => {
|
|
34
|
+
it('sustituye :key por la clave COMPLETA sin partirla (clave A/B)', () => {
|
|
35
|
+
expect(buildSegments(['orders', ':key'], 'A/B')).toEqual(['orders', 'A/B']);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('sustituye params en posición media y mantiene literales', () => {
|
|
39
|
+
expect(buildSegments(['orders', ':key', 'edit'], '7')).toEqual(['orders', '7', 'edit']);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it('param distinto de :key ⇒ falla ruido (sin fuente declarada)', () => {
|
|
43
|
+
expect(() => buildSegments(['orders', ':otro'], 'x')).toThrow('sin fuente declarada');
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
describe('targetRoutePattern (misma fuente que el validador)', () => {
|
|
48
|
+
it('resuelve el patrón de la ruta destino desde la registry', () => {
|
|
49
|
+
expect(targetRoutePattern({ resource: 'orders', view: 'detail', params: ['key'] })).toEqual([
|
|
50
|
+
'orders',
|
|
51
|
+
':key',
|
|
52
|
+
]);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it('recurso o vista destino inexistente ⇒ undefined (nada navega)', () => {
|
|
56
|
+
expect(targetRoutePattern({ resource: 'nope', view: 'detail' })).toBeUndefined();
|
|
57
|
+
expect(targetRoutePattern({ resource: 'orders', view: 'nope' })).toBeUndefined();
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
describe('ViewNavigationService.dispatch', () => {
|
|
62
|
+
function servicio(auth = { isAuthenticated: () => true, canAccess: () => true }) {
|
|
63
|
+
TestBed.configureTestingModule({
|
|
64
|
+
providers: [
|
|
65
|
+
provideRouter([
|
|
66
|
+
{
|
|
67
|
+
path: 'orders',
|
|
68
|
+
component: ListaStub,
|
|
69
|
+
data: { resourceId: 'orders' },
|
|
70
|
+
canActivate: [roleGuard],
|
|
71
|
+
},
|
|
72
|
+
{
|
|
73
|
+
path: 'orders/:key',
|
|
74
|
+
component: DetalleStub,
|
|
75
|
+
data: { resourceId: 'orders' },
|
|
76
|
+
canActivate: [roleGuard],
|
|
77
|
+
},
|
|
78
|
+
]),
|
|
79
|
+
{ provide: AuthService, useValue: auth },
|
|
80
|
+
],
|
|
81
|
+
});
|
|
82
|
+
return {
|
|
83
|
+
nav: TestBed.inject(ViewNavigationService),
|
|
84
|
+
router: TestBed.inject(Router),
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
it('target declarado ⇒ navega con la clave codificada y el guard corre', async () => {
|
|
89
|
+
const { nav, router } = servicio();
|
|
90
|
+
const ok = await nav.dispatch(rutaBinding('route'), {
|
|
91
|
+
resource: 'orders',
|
|
92
|
+
key: 'A/B',
|
|
93
|
+
action: 'open',
|
|
94
|
+
});
|
|
95
|
+
expect(ok).toBe(true);
|
|
96
|
+
// El router serializa :key ⇒ '/' de la clave queda codificado, sin partir
|
|
97
|
+
expect(router.url).toBe('/orders/A%2FB');
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it('patrón sin params navega a los segmentos literales', async () => {
|
|
101
|
+
const { nav, router } = servicio();
|
|
102
|
+
const ok = await nav.dispatch(
|
|
103
|
+
{ on: 'open', mode: 'route', target: { resource: 'orders', view: 'create' } },
|
|
104
|
+
{ resource: 'orders', key: 'ignorada', action: 'open' },
|
|
105
|
+
);
|
|
106
|
+
expect(ok).toBe(true);
|
|
107
|
+
expect(router.url).toBe('/orders/new');
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it('acción que no es la declarada ⇒ sin navegación (scope del target)', async () => {
|
|
111
|
+
const { nav, router } = servicio();
|
|
112
|
+
const espia = vi.spyOn(router, 'navigate');
|
|
113
|
+
await nav.dispatch(rutaBinding('route'), { resource: 'orders', key: '1', action: 'select' });
|
|
114
|
+
expect(espia).not.toHaveBeenCalled();
|
|
115
|
+
expect(router.url).toBe('/');
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it('sin binding declarado ⇒ inerte aunque el evento pida mutar', async () => {
|
|
119
|
+
const { nav, router } = servicio();
|
|
120
|
+
const espia = vi.spyOn(router, 'navigate');
|
|
121
|
+
await nav.dispatch(undefined, { resource: 'orders', key: '1', action: 'move' });
|
|
122
|
+
expect(espia).not.toHaveBeenCalled();
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
it('mode inline/none ⇒ sin navegación (comportamiento local del leaf)', async () => {
|
|
126
|
+
const { nav, router } = servicio();
|
|
127
|
+
const espia = vi.spyOn(router, 'navigate');
|
|
128
|
+
await nav.dispatch(
|
|
129
|
+
{ on: 'open', mode: 'inline' },
|
|
130
|
+
{ resource: 'orders', key: '1', action: 'open' },
|
|
131
|
+
);
|
|
132
|
+
await nav.dispatch(
|
|
133
|
+
{ on: 'open', mode: 'none' },
|
|
134
|
+
{ resource: 'orders', key: '1', action: 'open' },
|
|
135
|
+
);
|
|
136
|
+
expect(espia).not.toHaveBeenCalled();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
it('target sin recurso/vista declarado en el registry ⇒ falla ruido, no navega', async () => {
|
|
140
|
+
const { nav, router } = servicio();
|
|
141
|
+
const espia = vi.spyOn(router, 'navigate');
|
|
142
|
+
await expect(
|
|
143
|
+
nav.dispatch(
|
|
144
|
+
{ on: 'open', mode: 'route', target: { resource: 'no-existe', view: 'x' } },
|
|
145
|
+
{ resource: 'no-existe', key: '1', action: 'open' },
|
|
146
|
+
),
|
|
147
|
+
).rejects.toThrow('no existe');
|
|
148
|
+
expect(espia).not.toHaveBeenCalled();
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('guard bloquea (sin permiso) ⇒ URL intacta y sin payload del registro', async () => {
|
|
152
|
+
const { nav, router } = servicio({ isAuthenticated: () => true, canAccess: () => false });
|
|
153
|
+
const espia = vi.spyOn(router, 'navigate');
|
|
154
|
+
const ok = await nav.dispatch(rutaBinding('route'), {
|
|
155
|
+
resource: 'orders',
|
|
156
|
+
key: 'A/B',
|
|
157
|
+
action: 'open',
|
|
158
|
+
});
|
|
159
|
+
expect(ok).toBe(false);
|
|
160
|
+
expect(espia).toHaveBeenCalledWith(['/', 'orders', 'A/B']);
|
|
161
|
+
expect(router.url).toBe('/'); // ningún dato del registro llegó a la URL
|
|
162
|
+
// guard bloqueado ⇒ el origen NO queda registrado (nada que volver a ver)
|
|
163
|
+
await nav.back();
|
|
164
|
+
expect(router.url).toBe('/');
|
|
165
|
+
});
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe('ViewNavigationService.back (D4)', () => {
|
|
169
|
+
function servicio() {
|
|
170
|
+
TestBed.configureTestingModule({
|
|
171
|
+
providers: [
|
|
172
|
+
provideRouter([
|
|
173
|
+
{ path: 'orders', component: ListaStub, data: { resourceId: 'orders' } },
|
|
174
|
+
{ path: 'orders/:key', component: DetalleStub, data: { resourceId: 'orders' } },
|
|
175
|
+
]),
|
|
176
|
+
{ provide: AuthService, useValue: { isAuthenticated: () => true, canAccess: () => true } },
|
|
177
|
+
],
|
|
178
|
+
});
|
|
179
|
+
return {
|
|
180
|
+
nav: TestBed.inject(ViewNavigationService),
|
|
181
|
+
router: TestBed.inject(Router),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
it('con dispatch previo ⇒ vuelve al origen registrado', async () => {
|
|
186
|
+
const { nav, router } = servicio();
|
|
187
|
+
await nav.dispatch(rutaBinding('route'), { resource: 'orders', key: '9', action: 'open' });
|
|
188
|
+
expect(router.url).toBe('/orders/9');
|
|
189
|
+
await nav.back();
|
|
190
|
+
expect(router.url).toBe('/');
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
it('returnPath declarado ⇒ navega ahí (gana sobre el origen)', async () => {
|
|
194
|
+
const { nav, router } = servicio();
|
|
195
|
+
await nav.dispatch(rutaBinding('route'), { resource: 'orders', key: '9', action: 'open' });
|
|
196
|
+
await nav.back('orders');
|
|
197
|
+
expect(router.url).toBe('/orders');
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('deep link sin dispatch ⇒ cae a la raíz del recurso de la ruta', async () => {
|
|
201
|
+
const { nav, router } = servicio();
|
|
202
|
+
await router.navigate(['/orders/XYZ']); // entrada directa: no hay origen en memoria
|
|
203
|
+
expect(router.url).toBe('/orders/XYZ');
|
|
204
|
+
await nav.back();
|
|
205
|
+
expect(router.url).toBe('/orders'); // raíz declarada del resourceId de la ruta
|
|
206
|
+
});
|
|
207
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { Injectable, inject } from '@angular/core';
|
|
2
|
+
import { Router, type ActivatedRouteSnapshot } from '@angular/router';
|
|
3
|
+
|
|
4
|
+
import type { NavigationTarget, SelectionBinding } from '../models/schema.model';
|
|
5
|
+
import { getSchema } from '../registry/schema-catalog';
|
|
6
|
+
import type { SelectionEvent } from '../../views/composer/view-context';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* R9.9 · D4 — Servicio de navegación declarativa.
|
|
10
|
+
*
|
|
11
|
+
* Un leaf (hoja) sólo NAVEGA cuando hay `select` declarado; el motor vive
|
|
12
|
+
* aquí, no en cada hoja. El flujo completo es:
|
|
13
|
+
*
|
|
14
|
+
* `ctx.emit/leaf → dispatch → buildSegments(pattern, {key}) →
|
|
15
|
+
* router.navigate(matriz de comandos) → roleGuard`
|
|
16
|
+
*
|
|
17
|
+
* Invariantes:
|
|
18
|
+
*
|
|
19
|
+
* - **key-only**: la ÚNICA identidad que viaja es `event.key`. Los comandos
|
|
20
|
+
* son una matriz de segmentos (nunca concat de strings) ⇒ el Router
|
|
21
|
+
* codifica la clave (una clave `A/B` queda `A%2FB` en la URL) y ningún
|
|
22
|
+
* dato del registro llega a la URL.
|
|
23
|
+
* - **Fail closed**: sin binding, con acción distinta a la declarada o con
|
|
24
|
+
* `mode` distinto de `route`, NO hay navegación — simplemente no ocurre.
|
|
25
|
+
* - **Fail loud**: un target que el registry no declara lanza ruido antes de
|
|
26
|
+
* navegar (el validador pre-render ya lo garantiza; esto es defensa en
|
|
27
|
+
* profundidad).
|
|
28
|
+
* - **Guards reales**: `router.navigate` ⇒ `roleGuard` corre igual que en
|
|
29
|
+
* cualquier navegación declarada. Si el guard bloquea, no se registra
|
|
30
|
+
* origen (no hay «atrás» que una sesión sin permiso deba volver a ver).
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Patrón de segmentos de la ruta destino (`['orders', ':key']`). Usa la
|
|
35
|
+
* MISMA fuente que el validador (`getSchema`) para que lo validado en build
|
|
36
|
+
* sea exactamente lo navegado en runtime. Inexistente ⇒ `undefined` (la
|
|
37
|
+
* defensa en profundidad: el validador pre-render garantiza que esto no
|
|
38
|
+
* ocurre en schemas válidos).
|
|
39
|
+
*/
|
|
40
|
+
export function targetRoutePattern(target: NavigationTarget): string[] | undefined {
|
|
41
|
+
const destino = getSchema(target.resource);
|
|
42
|
+
const vista = destino?.uiViews?.[target.view];
|
|
43
|
+
if (!destino || !vista) return undefined;
|
|
44
|
+
const relativa = vista.path ?? '';
|
|
45
|
+
return [destino.id, ...(relativa === '' ? [] : relativa.split('/'))];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Segmentos de la ruta destino con la clave sustituida. La clave es SIEMPRE
|
|
50
|
+
* un segmento completo (no se parte en `/`); un param distinto de `:key` no
|
|
51
|
+
* tiene fuente declarada ⇒ falla ruido, no navega a medias.
|
|
52
|
+
*/
|
|
53
|
+
export function buildSegments(pattern: readonly string[], key: string): string[] {
|
|
54
|
+
const commands: string[] = [];
|
|
55
|
+
for (const segmento of pattern) {
|
|
56
|
+
if (segmento.startsWith(':')) {
|
|
57
|
+
const nombre = segmento.slice(1);
|
|
58
|
+
if (nombre !== 'key') {
|
|
59
|
+
throw new Error(`Destino ':${nombre}' sin fuente declarada en SelectionEvent`);
|
|
60
|
+
}
|
|
61
|
+
commands.push(key);
|
|
62
|
+
} else {
|
|
63
|
+
commands.push(segmento);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return commands;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
@Injectable({ providedIn: 'root' })
|
|
70
|
+
export class ViewNavigationService {
|
|
71
|
+
private readonly router = inject(Router);
|
|
72
|
+
|
|
73
|
+
/** URL desde la que se navegó la última selección válida (origen del «atrás»). */
|
|
74
|
+
private origen: string | undefined;
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Transición declarada de un leaf. Inerte si no hay binding, si la acción
|
|
78
|
+
* no es la declarada o si el modo no es `route`; falla ruido si el target
|
|
79
|
+
* no existe en el registry.
|
|
80
|
+
*
|
|
81
|
+
* @returns `true` si el Router completó la navegación (guards aceptados).
|
|
82
|
+
*/
|
|
83
|
+
async dispatch(binding: SelectionBinding | undefined, event: SelectionEvent): Promise<boolean> {
|
|
84
|
+
if (binding === undefined) return false; // sin declaración ⇒ inerte
|
|
85
|
+
if (binding.on !== event.action) return false; // fuera del scope declarado
|
|
86
|
+
if (binding.mode !== 'route') return false; // comportamiento local del leaf
|
|
87
|
+
|
|
88
|
+
const pattern = targetRoutePattern(binding.target);
|
|
89
|
+
if (pattern === undefined) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
`Destino '${binding.target.resource}/${binding.target.view}' no existe en el registry`,
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const urlOrigen = this.router.url;
|
|
96
|
+
const ok = await this.router.navigate(['/', ...buildSegments(pattern, event.key)]);
|
|
97
|
+
if (ok) this.origen = urlOrigen;
|
|
98
|
+
return ok;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Forma de volver (D4). Orden: `returnPath` declarado por el leaf ⇒
|
|
103
|
+
* origen registrado por `dispatch` ⇒ raíz del recurso declarado en la ruta
|
|
104
|
+
* actual (entrada directa a la URL: no hay origen en memoria). Fallback
|
|
105
|
+
* final: la raíz de la aplicación.
|
|
106
|
+
*/
|
|
107
|
+
async back(returnPath?: string): Promise<boolean> {
|
|
108
|
+
if (returnPath !== undefined) return this.router.navigate([returnPath]);
|
|
109
|
+
|
|
110
|
+
if (this.origen !== undefined) {
|
|
111
|
+
const destino = this.origen;
|
|
112
|
+
this.origen = undefined;
|
|
113
|
+
return this.router.navigateByUrl(destino);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const recurso = this.recursoActual();
|
|
117
|
+
return recurso === undefined
|
|
118
|
+
? this.router.navigate(['/'])
|
|
119
|
+
: this.router.navigate(['/', recurso]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/** Primer `resourceId` declarado en el árbol de rutas activo. */
|
|
123
|
+
private recursoActual(): string | undefined {
|
|
124
|
+
let nodo: ActivatedRouteSnapshot | null = this.router.routerState.snapshot.root;
|
|
125
|
+
while (nodo !== null) {
|
|
126
|
+
const id = nodo.data?.['resourceId'];
|
|
127
|
+
if (id !== undefined) return String(id);
|
|
128
|
+
nodo = nodo.firstChild;
|
|
129
|
+
}
|
|
130
|
+
return undefined;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
+
|
|
3
|
+
/** Descriptor del campo `boolean` (R3.3). */
|
|
4
|
+
export const descriptor: RenderDescriptor = {
|
|
5
|
+
kind: 'field',
|
|
6
|
+
type: 'boolean',
|
|
7
|
+
since: '1.0',
|
|
8
|
+
load: () => import('./boolean').then((module) => module.FieldBoolean),
|
|
9
|
+
seed: 'boolean',
|
|
10
|
+
surfaces: ['form', 'display'],
|
|
11
|
+
};
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
import { FormField } from '@angular/forms/signals';
|
|
3
|
+
|
|
4
|
+
import type { BooleanField } from '../../core/models/schema.model';
|
|
5
|
+
import { FieldAria } from '../field-aria';
|
|
6
|
+
import { childTree, type RootTree } from '../field-node';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Interruptor booleano (base.md §5.1) con `trueLabel`/`falseLabel`.
|
|
10
|
+
* Checkbox nativo + `formField` (se estiliza como switch con `peer-checked`).
|
|
11
|
+
*/
|
|
12
|
+
@Component({
|
|
13
|
+
selector: 'app-field-boolean',
|
|
14
|
+
imports: [FieldAria, FormField],
|
|
15
|
+
template: `
|
|
16
|
+
<label class="inline-flex cursor-pointer items-center gap-2">
|
|
17
|
+
<input type="checkbox" class="peer sr-only" [id]="state().name()" [formField]="node()" />
|
|
18
|
+
<span
|
|
19
|
+
class="relative h-5 w-9 shrink-0 rounded-full bg-neutral/30 transition-colors peer-checked:bg-primary after:absolute after:top-0.5 after:left-0.5 after:h-4 after:w-4 after:rounded-full after:bg-white after:transition-transform after:content-[''] peer-checked:after:translate-x-4"
|
|
20
|
+
></span>
|
|
21
|
+
<span class="text-sm">{{ currentLabel() }}</span>
|
|
22
|
+
</label>
|
|
23
|
+
`,
|
|
24
|
+
})
|
|
25
|
+
export class FieldBoolean {
|
|
26
|
+
readonly field = input.required<BooleanField>();
|
|
27
|
+
readonly tree = input.required<RootTree>();
|
|
28
|
+
|
|
29
|
+
protected readonly node = computed(() => childTree<boolean>(this.tree(), this.field().key));
|
|
30
|
+
protected readonly state = computed(() => this.node()());
|
|
31
|
+
|
|
32
|
+
protected readonly currentLabel = computed(() => {
|
|
33
|
+
const on = this.state().value() === true;
|
|
34
|
+
const field = this.field();
|
|
35
|
+
if (on) {
|
|
36
|
+
return field.trueLabel ?? 'Sí';
|
|
37
|
+
}
|
|
38
|
+
return field.falseLabel ?? 'No';
|
|
39
|
+
});
|
|
40
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
+
|
|
3
|
+
/** Descriptor del campo `color` (selector de color). */
|
|
4
|
+
export const descriptor: RenderDescriptor = {
|
|
5
|
+
kind: 'field',
|
|
6
|
+
type: 'color',
|
|
7
|
+
since: '1.0',
|
|
8
|
+
load: () => import('./color').then((module) => module.FieldColor),
|
|
9
|
+
// Nace como cadena (hexadecimal #RRGGBB).
|
|
10
|
+
seed: 'string',
|
|
11
|
+
surfaces: ['form', 'display'],
|
|
12
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
import { FormField } from '@angular/forms/signals';
|
|
3
|
+
|
|
4
|
+
import type { ColorField } from '../../core/models/schema.model';
|
|
5
|
+
import { FieldAria } from '../field-aria';
|
|
6
|
+
import { childTree, type RootTree } from '../field-node';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Selector de color (`<input type="color">`, base.md §5.1 `color`).
|
|
10
|
+
*
|
|
11
|
+
* Devuelve un string hexadecimal `#RRGGBB`. El navegador muestra su propia
|
|
12
|
+
* paleta de colores; no hay validación de formato extra porque el input
|
|
13
|
+
* `type="color"` ya garantiza el formato válido.
|
|
14
|
+
*/
|
|
15
|
+
@Component({
|
|
16
|
+
selector: 'app-field-color',
|
|
17
|
+
imports: [FieldAria, FormField],
|
|
18
|
+
template: `
|
|
19
|
+
<input
|
|
20
|
+
type="color"
|
|
21
|
+
class="field-input h-10 w-full p-0 border-0 bg-transparent focus-visible:outline-0 cursor-pointer"
|
|
22
|
+
[id]="state().name()"
|
|
23
|
+
[formField]="node()"
|
|
24
|
+
[title]="field().label"
|
|
25
|
+
/>
|
|
26
|
+
`,
|
|
27
|
+
})
|
|
28
|
+
export class FieldColor {
|
|
29
|
+
readonly field = input.required<ColorField>();
|
|
30
|
+
readonly tree = input.required<RootTree>();
|
|
31
|
+
|
|
32
|
+
protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
|
|
33
|
+
protected readonly state = computed(() => this.node()());
|
|
34
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
+
|
|
3
|
+
/** Descriptor del campo `currency` (R3.3). */
|
|
4
|
+
export const descriptor: RenderDescriptor = {
|
|
5
|
+
kind: 'field',
|
|
6
|
+
type: 'currency',
|
|
7
|
+
since: '1.0',
|
|
8
|
+
load: () => import('./currency').then((module) => module.FieldCurrency),
|
|
9
|
+
// Nace como `null`: no puede ser `0`, que sería un valor ya cargado.
|
|
10
|
+
seed: 'number',
|
|
11
|
+
surfaces: ['form', 'display'],
|
|
12
|
+
};
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
import { FormField } from '@angular/forms/signals';
|
|
3
|
+
|
|
4
|
+
import type { CurrencyField } from '../../core/models/schema.model';
|
|
5
|
+
import { FieldAria } from '../field-aria';
|
|
6
|
+
import { childTree, type RootTree } from '../field-node';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Número con prefijo de moneda (base.md §5.1). El valor sigue siendo número
|
|
10
|
+
* puro; el prefijo sale del campo `currencyFrom` (código ISO, ej. `PEN`)
|
|
11
|
+
* traducido a símbolo con Intl.
|
|
12
|
+
*/
|
|
13
|
+
@Component({
|
|
14
|
+
selector: 'app-field-currency',
|
|
15
|
+
imports: [FieldAria, FormField],
|
|
16
|
+
template: `
|
|
17
|
+
<div class="flex items-center gap-2">
|
|
18
|
+
@if (symbol(); as symbol) {
|
|
19
|
+
<span class="shrink-0 text-sm text-neutral" data-testid="currency-symbol">{{
|
|
20
|
+
symbol
|
|
21
|
+
}}</span>
|
|
22
|
+
}
|
|
23
|
+
<input
|
|
24
|
+
type="number"
|
|
25
|
+
step="any"
|
|
26
|
+
class="field-input"
|
|
27
|
+
[id]="state().name()"
|
|
28
|
+
[formField]="node()"
|
|
29
|
+
[placeholder]="field().placeholder ?? ''"
|
|
30
|
+
/>
|
|
31
|
+
</div>
|
|
32
|
+
`,
|
|
33
|
+
})
|
|
34
|
+
export class FieldCurrency {
|
|
35
|
+
readonly field = input.required<CurrencyField>();
|
|
36
|
+
readonly tree = input.required<RootTree>();
|
|
37
|
+
|
|
38
|
+
protected readonly node = computed(() => childTree<number>(this.tree(), this.field().key));
|
|
39
|
+
protected readonly state = computed(() => this.node()());
|
|
40
|
+
|
|
41
|
+
/** Código ISO de la moneda desde el campo hermano (`currencyFrom`). */
|
|
42
|
+
private readonly currencyCode = computed(() => {
|
|
43
|
+
const from = this.field().currencyFrom;
|
|
44
|
+
if (!from) {
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
const value = childTree<unknown>(this.tree(), from)().value();
|
|
48
|
+
return typeof value === 'string' && value !== '' ? value : null;
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
/** Símbolo localizado (`S/` para PEN); el código crudo si Intl lo rechaza. */
|
|
52
|
+
protected readonly symbol = computed(() => {
|
|
53
|
+
const code = this.currencyCode();
|
|
54
|
+
if (code === null) {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
try {
|
|
58
|
+
const parts = new Intl.NumberFormat('es-PE', {
|
|
59
|
+
style: 'currency',
|
|
60
|
+
currency: code,
|
|
61
|
+
}).formatToParts(0);
|
|
62
|
+
return parts.find((part) => part.type === 'currency')?.value ?? code;
|
|
63
|
+
} catch {
|
|
64
|
+
return code;
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RenderDescriptor } from '../../core/registry/render-registry';
|
|
2
|
+
|
|
3
|
+
/** Descriptor del campo `date` (R3.3). */
|
|
4
|
+
export const descriptor: RenderDescriptor = {
|
|
5
|
+
kind: 'field',
|
|
6
|
+
type: 'date',
|
|
7
|
+
since: '1.0',
|
|
8
|
+
load: () => import('./date').then((module) => module.FieldDate),
|
|
9
|
+
// Nace como cadena: los textos, fechas, urls, selects y relacionales.
|
|
10
|
+
seed: 'string',
|
|
11
|
+
surfaces: ['form', 'display'],
|
|
12
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { Component, computed, input } from '@angular/core';
|
|
2
|
+
import { FormField } from '@angular/forms/signals';
|
|
3
|
+
|
|
4
|
+
import type { DateField } from '../../core/models/schema.model';
|
|
5
|
+
import { FieldAria } from '../field-aria';
|
|
6
|
+
import { childTree, type RootTree } from '../field-node';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Selector de fecha ISO `YYYY-MM-DD` (base.md §5.1).
|
|
10
|
+
*
|
|
11
|
+
* `min`/`max` del schema se validan con reglas propias en
|
|
12
|
+
* `pages/form-view/form-schema.ts`: `minDate`/`maxDate` de Signal Forms
|
|
13
|
+
* exigen valores `Date` y este modelo guarda strings. `FormField` solo setea
|
|
14
|
+
* los atributos nativos `min`/`max` cuando existen las reglas `min()`/`max()`
|
|
15
|
+
* (que aplican a números).
|
|
16
|
+
*/
|
|
17
|
+
@Component({
|
|
18
|
+
selector: 'app-field-date',
|
|
19
|
+
imports: [FieldAria, FormField],
|
|
20
|
+
template: `
|
|
21
|
+
<input
|
|
22
|
+
type="date"
|
|
23
|
+
class="field-input"
|
|
24
|
+
[id]="state().name()"
|
|
25
|
+
[formField]="node()"
|
|
26
|
+
[placeholder]="field().placeholder ?? ''"
|
|
27
|
+
/>
|
|
28
|
+
`,
|
|
29
|
+
})
|
|
30
|
+
export class FieldDate {
|
|
31
|
+
readonly field = input.required<DateField>();
|
|
32
|
+
readonly tree = input.required<RootTree>();
|
|
33
|
+
|
|
34
|
+
protected readonly node = computed(() => childTree<string>(this.tree(), this.field().key));
|
|
35
|
+
protected readonly state = computed(() => this.node()());
|
|
36
|
+
}
|