@sdcorejs/angular 19.0.8 → 19.0.10
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 +270 -140
- package/assets/scss/core/utilities/_grid.scss +25 -19
- package/components/anchor/src/models/sd-anchor.model.d.ts +1 -1
- package/components/document-builder/src/document-builder.component.d.ts +2 -2
- package/components/document-builder/src/plugins/paste-handler/filters/parse.d.ts +1 -1
- package/components/editor/src/plugins/image-upload/utils/batch.utils.d.ts +3 -3
- package/components/form-generic/src/components/form-builder/components/attribute-expression/attribute-expression.component.d.ts +10 -6
- package/components/form-generic/src/components/form-builder/components/attribute-expression/form-expression-query-adapter.d.ts +6 -0
- package/components/form-generic/src/components/form-builder/components/attribute-parameter/attribute-parameter.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/attribute-selection/attribute-selection.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/attribute-selection/components/build-queries/build-queries.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/attribute-selection/components/build-variables/build-variables.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/attribute-table/attribute-table.component.d.ts +2 -4
- package/components/form-generic/src/components/form-builder/components/attribute-template/attribute-template.component.d.ts +2 -4
- package/components/form-generic/src/components/form-builder/components/checkbox/attribute/checkbox-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/chip-calendar/attribute/chip-calendar-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/chip-calendar/control/chip-calendar-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/chip-string/attribute/chip-string-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/chip-string/control/chip-string-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/configure-validation/configure-validation.component.d.ts +6 -7
- package/components/form-generic/src/components/form-builder/components/datetime/attribute/datetime-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/datetime/control/datetime-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/expression-builder/expression-builder.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/html/attribute/components/build-queries/build-queries.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/html/control/html-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/number/attribute/number-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/number/control/number-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/radio/attribute/radio-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/radio/control/radio-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/select/attribute/select-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/select/control/select-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/table/attribute/table-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/table/control/table-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/textarea/attribute/textarea-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/textarea/control/textarea-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/textfield/attribute/textfield-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/textfield/control/textfield-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/upload/attribute/upload-attribute.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/components/upload/control/upload-control.component.d.ts +2 -2
- package/components/form-generic/src/components/form-builder/form-builder-layout.d.ts +22 -0
- package/components/form-generic/src/components/form-builder/form-builder.component.d.ts +24 -5
- package/components/form-generic/src/components/form-render/components/item/components/chip-calendar/chip-calendar.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/chip-string/chip-string.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/datetime/datetime.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/html/html.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/number/number.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/radio/radio.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/select/select.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/table/table.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/textarea/textarea.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/textfield/textfield.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/item/components/upload/upload.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/components/variable/variable.component.d.ts +2 -2
- package/components/form-generic/src/components/form-render/form-render.component.d.ts +4 -6
- package/components/form-generic/src/components/sd-feel-expression/sd-feel-expression.component.d.ts +2 -2
- package/components/form-generic/src/configurations/form.configuration.d.ts +1 -1
- package/components/form-generic/src/models/form-generic-component.model.d.ts +2 -2
- package/components/form-generic/src/services/form-generic.service.d.ts +1 -3
- package/components/import-excel/src/import-excel.model.d.ts +2 -2
- package/components/mini-editor/src/mini-editor.component.d.ts +4 -4
- package/components/modal/src/modal.component.d.ts +1 -1
- package/components/query-builder/src/query-builder.model.d.ts +4 -4
- package/components/splitter/src/splitter-state.service.d.ts +1 -1
- package/components/tab-router/src/models/tab-router.model.d.ts +2 -1
- package/components/tab-router/src/services/tab-router.service.d.ts +0 -1
- package/components/table/src/components/command/desktop-command.component.d.ts +8 -7
- package/components/table/src/components/command/pipes/command.pipe.d.ts +1 -12
- package/components/table/src/components/command/pipes/filter.pipe.d.ts +1 -8
- package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +1 -1
- package/components/table/src/models/table-column.model.d.ts +3 -2
- package/components/table/src/pipes/selection-disabled.pipe.d.ts +1 -1
- package/components/table/src/services/config.service.d.ts +4 -5
- package/components/table/src/services/table-filter/table-filter.service.d.ts +1 -0
- package/components/table/src/services/table-local/table-local.util.d.ts +19 -0
- package/components/table/src/services/table-reorder/table-reorder.util.d.ts +29 -0
- package/components/table/src/services/table-selection/table-selection.util.d.ts +8 -0
- package/components/table/src/services/tree/tree.util.d.ts +10 -1
- package/components/table/src/table.component.d.ts +6 -10
- package/components/view/src/view.component.d.ts +1 -1
- package/configurations/src/sd-core.configuration.d.ts +2 -2
- package/fesm2022/sdcorejs-angular-components-anchor.mjs +15 -18
- package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-avatar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs +0 -1
- package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-button.mjs +6 -7
- package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-ckeditor-styles.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-code-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs +17 -26
- package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-editor.mjs +7 -2
- package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs +711 -426
- package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs +35 -18
- package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs +0 -1
- package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-mini-editor.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-modal.mjs +25 -14
- package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs +1 -1
- package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-preview.mjs +33 -29
- package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs +15 -28
- package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs +2 -3
- package/fesm2022/sdcorejs-angular-components-query-builder.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-quick-action.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs +0 -1
- package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +3 -3
- package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-splitter.mjs +4 -4
- package/fesm2022/sdcorejs-angular-components-splitter.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-stepper.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs +31 -31
- package/fesm2022/sdcorejs-angular-components-tab-router.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tab.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-table.mjs +794 -849
- package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs +6 -6
- package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-components-view.mjs +3 -9
- package/fesm2022/sdcorejs-angular-components-view.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-configurations.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-directives.mjs +2 -7
- package/fesm2022/sdcorejs-angular-directives.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +0 -3
- package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-chip.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs +11 -5
- package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-date.mjs +2 -4
- package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs +2 -6
- package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs +3 -3
- package/fesm2022/sdcorejs-angular-forms-input-color.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs +1 -1
- package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-label.mjs +0 -1
- package/fesm2022/sdcorejs-angular-forms-label.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-radio.mjs +12 -2
- package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-select.mjs +45 -14
- package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs +2 -2
- package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-handlers.mjs +3 -6
- package/fesm2022/sdcorejs-angular-handlers.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-i18n.mjs +11 -7
- package/fesm2022/sdcorejs-angular-i18n.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-interceptors.mjs +0 -2
- package/fesm2022/sdcorejs-angular-interceptors.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-auth.mjs +14 -38
- package/fesm2022/sdcorejs-angular-modules-auth.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-authom.mjs +14 -14
- package/fesm2022/sdcorejs-angular-modules-authom.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs +22 -11
- package/fesm2022/sdcorejs-angular-modules-keycloak.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-layout.mjs +30 -27
- package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-modules-permission.mjs +5 -12
- package/fesm2022/sdcorejs-angular-modules-permission.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-pipes.mjs +71 -17
- package/fesm2022/sdcorejs-angular-pipes.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-api.mjs +0 -2
- package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-cache.mjs +5 -11
- package/fesm2022/sdcorejs-angular-services-cache.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-confirm.mjs +16 -21
- package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-docx.mjs +2 -8
- package/fesm2022/sdcorejs-angular-services-docx.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-excel.mjs +2 -2
- package/fesm2022/sdcorejs-angular-services-excel.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-license.mjs +5 -12
- package/fesm2022/sdcorejs-angular-services-license.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-notify.mjs +70 -87
- package/fesm2022/sdcorejs-angular-services-notify.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-services-storage.mjs +6 -15
- package/fesm2022/sdcorejs-angular-services-storage.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-data-state.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs +47 -5
- package/fesm2022/sdcorejs-angular-utilities-extensions.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular-utilities-models.mjs +18 -3
- package/fesm2022/sdcorejs-angular-utilities-models.mjs.map +1 -1
- package/fesm2022/sdcorejs-angular.mjs.map +1 -1
- package/forms/chip/src/chip.component.d.ts +2 -2
- package/forms/chip-calendar/src/chip-calendar.component.d.ts +2 -2
- package/forms/date-range/src/date-range.component.d.ts +2 -0
- package/forms/input-color/src/input-color.component.d.ts +2 -1
- package/forms/select/src/select-footer-action.directive.d.ts +5 -1
- package/forms/select/src/select.component.d.ts +4 -0
- package/modules/auth/guards/auth.guard.d.ts +2 -4
- package/modules/auth/guards/portal.guard.d.ts +2 -4
- package/modules/auth/services/auth.service.d.ts +3 -4
- package/modules/layout/modules/home/guards/home-redirect.guard.d.ts +3 -5
- package/modules/permission/src/configurations/permission.configuration.d.ts +3 -3
- package/modules/permission/src/guards/permission.guard.d.ts +2 -5
- package/package.json +2 -2
- package/pipes/index.d.ts +3 -0
- package/pipes/src/format-date.pipe.d.ts +8 -0
- package/pipes/src/format-datetime.pipe.d.ts +8 -0
- package/pipes/src/format-number.pipe.d.ts +1 -3
- package/pipes/src/view.pipe.d.ts +7 -0
- package/services/confirm/src/lib/components/dialog-confirm/dialog-confirm.component.d.ts +3 -3
- package/services/license/src/license.service.d.ts +2 -3
- package/services/notify/src/components/toast/toast.component.d.ts +9 -13
- package/services/notify/src/notify.service.d.ts +4 -5
- package/services/storage/src/storage.service.d.ts +3 -4
- package/utilities/extensions/index.d.ts +1 -0
- package/utilities/extensions/src/object.extension.d.ts +8 -0
- package/components/document-builder/src/plugins/paste-handler/filters/replacetabswithinprewithspaces.d.ts +0 -24
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-services-license.mjs","sources":["../../../projects/sdcorejs-angular/services/license/src/license.service.ts","../../../projects/sdcorejs-angular/services/license/sdcorejs-angular-services-license.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-services-license.mjs","sources":["../../../projects/sdcorejs-angular/services/license/src/license.service.ts","../../../projects/sdcorejs-angular/services/license/sdcorejs-angular-services-license.ts"],"sourcesContent":["import { inject, Injectable } from '@angular/core';\nimport { ISdCoreConfiguration, SD_CORE_CONFIGURATION } from '@sdcorejs/angular/configurations';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdLicenseService {\n readonly #SALT = 'angular-core-976e2fa6f8b44dadbc63f87b057a331f';\n #isValid = false;\n private readonly coreConfiguration: ISdCoreConfiguration | null = inject(SD_CORE_CONFIGURATION, { optional: true });\n\n constructor() {\n this.#verify();\n }\n\n enforceLicense = () => {\n if (!this.#isValid) {\n this.#throwSecurityError();\n }\n };\n\n #verify = () => {\n const hostname = window.location.hostname; // Ví dụ: store.uat.nexa.mobi\n\n // 1. Bypass Localhost\n if (this.#isLocalhost(hostname)) {\n this.#isValid = true;\n return;\n }\n\n const configKeys = this.#getConfiguredKeys();\n if (configKeys.length === 0) {\n this.#isValid = false;\n this.#throwSecurityError();\n return;\n }\n\n // 2. CHECK EXACT MATCH (Ưu tiên khớp chính xác 100%)\n // Hash chuỗi: \"store.uat.nexa.mobi\" + SALT\n if (this.#checkMatch(hostname, configKeys)) {\n this.#isValid = true;\n return;\n }\n\n // 3. CHECK WILDCARD MATCH (Thử các trường hợp có dấu *)\n const parts = hostname.split('.'); // [\"store\", \"uat\", \"nexa\", \"mobi\"]\n\n // Lặp để cắt dần subdomain bên trái và thêm \"*.\" vào\n // Điều kiện > 2 để đảm bảo không check các case vô nghĩa như *.mobi hay *.com\n while (parts.length > 2) {\n parts.shift(); // Bỏ phần đầu: [\"uat\", \"nexa\", \"mobi\"]\n\n // Tạo domain wildcard: \"*.uat.nexa.mobi\"\n const wildcardDomain = '*.' + parts.join('.');\n\n // Hash chuỗi: \"*.uat.nexa.mobi\" + SALT\n if (this.#checkMatch(wildcardDomain, configKeys)) {\n this.#isValid = true;\n return;\n }\n }\n\n // Nếu chạy hết vòng lặp mà không khớp cái nào\n this.#isValid = false;\n this.#throwSecurityError();\n };\n\n // Helper để check xem hash của input có nằm trong list key không\n #checkMatch = (inputString: string, validKeys: string[]): boolean => {\n const hash = this.#generateHash(inputString + this.#SALT);\n return validKeys.includes(hash);\n };\n\n #getConfiguredKeys = (): string[] => {\n const keyOrKeys = this.coreConfiguration?.licenseKey;\n if (!keyOrKeys) return [];\n return Array.isArray(keyOrKeys) ? keyOrKeys : [keyOrKeys];\n };\n\n #isLocalhost = (domain: string) => {\n return domain === 'localhost' || domain === '127.0.0.1' || domain.includes('localhost');\n };\n\n #generateHash = (input: string): string => {\n let hash = 0;\n if (input.length === 0) return hash.toString();\n for (let i = 0; i < input.length; i++) {\n const char = input.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash;\n }\n return btoa(hash.toString() + 'signed');\n };\n\n #throwSecurityError = () => {\n throw new Error(`[Security] Unauthorized usage of UI Lib on domain: ${window.location.hostname}`);\n };\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1,69 +1,62 @@
|
|
|
1
1
|
import { CommonModule, DOCUMENT } from '@angular/common';
|
|
2
2
|
import * as i0 from '@angular/core';
|
|
3
|
-
import { signal, SecurityContext, HostListener, Input, Component,
|
|
3
|
+
import { signal, computed, SecurityContext, HostListener, Input, ChangeDetectionStrategy, Component, inject, ApplicationRef, EnvironmentInjector, createComponent, Injectable } from '@angular/core';
|
|
4
4
|
import { Utilities } from '@sdcorejs/utils/fns';
|
|
5
|
-
import {
|
|
6
|
-
import { TranslatePipe } from '@sdcorejs/angular/i18n';
|
|
5
|
+
import { TranslatePipe, I18nService } from '@sdcorejs/angular/i18n';
|
|
7
6
|
import * as i2 from '@angular/platform-browser';
|
|
8
7
|
|
|
8
|
+
const TOAST_EXIT_ANIMATION_MS = 200;
|
|
9
9
|
class ToastComponent {
|
|
10
10
|
notifyService;
|
|
11
11
|
sanitizer;
|
|
12
12
|
data;
|
|
13
13
|
isExpanded = signal(false);
|
|
14
|
+
isClosing = signal(false);
|
|
15
|
+
hostClasses = computed(() => (this.isClosing() ? 'bg-white sd-toast sd-toast--closing' : 'bg-white sd-toast'));
|
|
14
16
|
MAX_SHOW = 2;
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
start;
|
|
18
|
-
remaining;
|
|
17
|
+
timer = null;
|
|
18
|
+
closeTimer = null;
|
|
19
|
+
start = 0;
|
|
20
|
+
remaining = 0;
|
|
19
21
|
constructor(notifyService, sanitizer) {
|
|
20
22
|
this.notifyService = notifyService;
|
|
21
23
|
this.sanitizer = sanitizer;
|
|
22
24
|
}
|
|
23
|
-
/**
|
|
24
|
-
* Sanitize HTML tường minh trước khi render qua `[innerHTML]` — strip
|
|
25
|
-
* `<script>`, event handler (`on*`), `javascript:` URL. Chỉ chạy ở nhánh
|
|
26
|
-
* `data.html === true`; nhánh mặc định render text (auto-escape) nên không
|
|
27
|
-
* có sink này. // why: phơi rõ việc sanitize cho review/scanner bảo mật.
|
|
28
|
-
*/
|
|
29
|
-
sanitizeHtml(value) {
|
|
30
|
-
return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';
|
|
31
|
-
}
|
|
32
|
-
/** Message ở dạng chuỗi đơn (chỉ dùng khi !isMultiMessage). */
|
|
33
|
-
get singleMessage() {
|
|
34
|
-
return this.data.message;
|
|
35
|
-
}
|
|
36
25
|
ngOnInit() {
|
|
37
|
-
// Khởi tạo thời gian còn lại bằng duration ban đầu
|
|
38
26
|
this.remaining = this.data.duration;
|
|
39
|
-
// Bắt đầu đếm ngược ngay khi hiện ra
|
|
40
27
|
this.resumeTimer();
|
|
41
28
|
}
|
|
42
29
|
ngOnDestroy() {
|
|
43
|
-
|
|
30
|
+
this.clearAutoDismissTimer();
|
|
31
|
+
this.clearCloseTimer();
|
|
44
32
|
}
|
|
45
|
-
// --- Logic Pause/Resume ---
|
|
46
33
|
pauseTimer() {
|
|
47
|
-
if (this.timer) {
|
|
48
|
-
|
|
49
|
-
clearTimeout(this.timer);
|
|
50
|
-
this.timer = null;
|
|
51
|
-
// Tính toán thời gian đã trôi qua để trừ đi
|
|
52
|
-
this.remaining -= Date.now() - this.start;
|
|
34
|
+
if (!this.timer || this.isClosing()) {
|
|
35
|
+
return;
|
|
53
36
|
}
|
|
37
|
+
clearTimeout(this.timer);
|
|
38
|
+
this.timer = null;
|
|
39
|
+
this.remaining = Math.max(this.remaining - (Date.now() - this.start), 0);
|
|
54
40
|
}
|
|
55
41
|
resumeTimer() {
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
this.start = Date.now();
|
|
59
|
-
this.timer = setTimeout(() => {
|
|
60
|
-
this.close();
|
|
61
|
-
}, this.remaining);
|
|
42
|
+
if (this.remaining <= 0 || this.timer || this.isClosing()) {
|
|
43
|
+
return;
|
|
62
44
|
}
|
|
45
|
+
this.start = Date.now();
|
|
46
|
+
this.timer = setTimeout(() => {
|
|
47
|
+
this.close();
|
|
48
|
+
}, this.remaining);
|
|
63
49
|
}
|
|
64
|
-
// --- Logic cũ ---
|
|
65
50
|
close() {
|
|
66
|
-
this.
|
|
51
|
+
if (this.isClosing()) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
this.clearAutoDismissTimer();
|
|
55
|
+
this.isClosing.set(true);
|
|
56
|
+
this.closeTimer = setTimeout(() => {
|
|
57
|
+
this.closeTimer = null;
|
|
58
|
+
this.notifyService.remove(this.data.id);
|
|
59
|
+
}, TOAST_EXIT_ANIMATION_MS);
|
|
67
60
|
}
|
|
68
61
|
onActionClick() {
|
|
69
62
|
this.data.onAction?.();
|
|
@@ -71,6 +64,12 @@ class ToastComponent {
|
|
|
71
64
|
toggleExpand() {
|
|
72
65
|
this.isExpanded.update(v => !v);
|
|
73
66
|
}
|
|
67
|
+
sanitizeHtml(value) {
|
|
68
|
+
return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';
|
|
69
|
+
}
|
|
70
|
+
get singleMessage() {
|
|
71
|
+
return this.data.message;
|
|
72
|
+
}
|
|
74
73
|
get isMultiMessage() {
|
|
75
74
|
return Array.isArray(this.data.message);
|
|
76
75
|
}
|
|
@@ -89,48 +88,38 @@ class ToastComponent {
|
|
|
89
88
|
get hasMore() {
|
|
90
89
|
return this.isMultiMessage && this.messages.length > this.MAX_SHOW;
|
|
91
90
|
}
|
|
92
|
-
/** autoid theo loại toast để E2E chọn trực tiếp, vd `services-notify-toast-success`. */
|
|
93
91
|
get autoId() {
|
|
94
92
|
return `services-notify-toast-${this.data.type}`;
|
|
95
93
|
}
|
|
96
|
-
/** Gộp message (mảng nối ' | ') để phơi ra data-message cho E2E. */
|
|
97
94
|
get dataMessage() {
|
|
98
95
|
return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;
|
|
99
96
|
}
|
|
97
|
+
clearAutoDismissTimer() {
|
|
98
|
+
if (!this.timer) {
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
clearTimeout(this.timer);
|
|
102
|
+
this.timer = null;
|
|
103
|
+
}
|
|
104
|
+
clearCloseTimer() {
|
|
105
|
+
if (!this.closeTimer) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
clearTimeout(this.closeTimer);
|
|
109
|
+
this.closeTimer = null;
|
|
110
|
+
}
|
|
100
111
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ToastComponent, deps: [{ token: SdNotifyService }, { token: i2.DomSanitizer }], target: i0.ɵɵFactoryTarget.Component });
|
|
101
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ToastComponent, isStandalone: true, selector: "toast", inputs: { data: "data" }, host: { listeners: { "mouseenter": "pauseTimer()", "mouseleave": "resumeTimer()" }, properties: { "
|
|
102
|
-
trigger('toastAnimation', [
|
|
103
|
-
transition(':enter', [
|
|
104
|
-
style({ transform: 'translateX(100%)', opacity: 0 }),
|
|
105
|
-
animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))
|
|
106
|
-
]),
|
|
107
|
-
transition(':leave', [
|
|
108
|
-
animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))
|
|
109
|
-
])
|
|
110
|
-
])
|
|
111
|
-
] });
|
|
112
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ToastComponent, isStandalone: true, selector: "toast", inputs: { data: "data" }, host: { listeners: { "mouseenter": "pauseTimer()", "mouseleave": "resumeTimer()" }, properties: { "class": "hostClasses()", "attr.data-autoid": "autoId", "attr.data-type": "data.type", "attr.data-title": "data.title ?? null", "attr.data-message": "dataMessage" } }, ngImport: i0, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">×</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- Text rendering is the default safe path; HTML is opt-in and sanitized. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n } @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button\n type=\"button\"\n class=\"toggle-more btn-link\"\n [class]=\"'text-' + data.type\"\n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button\n class=\"btn-action\"\n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>\n", styles: [":host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto;animation:sd-toast-enter .3s ease-out}:host(.sd-toast--closing){pointer-events:none;animation:sd-toast-leave .2s ease-in forwards}@keyframes sd-toast-enter{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes sd-toast-leave{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){:host,:host(.sd-toast--closing){animation:none}}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
112
113
|
}
|
|
113
114
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ToastComponent, decorators: [{
|
|
114
115
|
type: Component,
|
|
115
|
-
args: [{ selector: 'toast', standalone: true, imports: [CommonModule, TranslatePipe],
|
|
116
|
-
|
|
117
|
-
transition(':enter', [
|
|
118
|
-
style({ transform: 'translateX(100%)', opacity: 0 }),
|
|
119
|
-
animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))
|
|
120
|
-
]),
|
|
121
|
-
transition(':leave', [
|
|
122
|
-
animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))
|
|
123
|
-
])
|
|
124
|
-
])
|
|
125
|
-
], host: {
|
|
126
|
-
'[@toastAnimation]': 'true',
|
|
127
|
-
'[class]': '"bg-white sd-toast"',
|
|
128
|
-
// E2E hooks đọc bởi sd-autoid-inspector: autoid theo loại + state type/title/message.
|
|
116
|
+
args: [{ selector: 'toast', standalone: true, imports: [CommonModule, TranslatePipe], changeDetection: ChangeDetectionStrategy.OnPush, host: {
|
|
117
|
+
'[class]': 'hostClasses()',
|
|
129
118
|
'[attr.data-autoid]': 'autoId',
|
|
130
119
|
'[attr.data-type]': 'data.type',
|
|
131
120
|
'[attr.data-title]': 'data.title ?? null',
|
|
132
|
-
'[attr.data-message]': 'dataMessage'
|
|
133
|
-
}, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\"
|
|
121
|
+
'[attr.data-message]': 'dataMessage',
|
|
122
|
+
}, template: "<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">×</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- Text rendering is the default safe path; HTML is opt-in and sanitized. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n } @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button\n type=\"button\"\n class=\"toggle-more btn-link\"\n [class]=\"'text-' + data.type\"\n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button\n class=\"btn-action\"\n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>\n", styles: [":host{display:flex;width:350px;border-radius:8px;box-shadow:0 4px 12px #00000026;margin-bottom:12px;overflow:hidden;position:relative;font-family:Roboto,sans-serif;pointer-events:auto;animation:sd-toast-enter .3s ease-out}:host(.sd-toast--closing){pointer-events:none;animation:sd-toast-leave .2s ease-in forwards}@keyframes sd-toast-enter{0%{transform:translate(100%);opacity:0}to{transform:translate(0);opacity:1}}@keyframes sd-toast-leave{0%{transform:translate(0);opacity:1}to{transform:translate(100%);opacity:0}}@media(prefers-reduced-motion:reduce){:host,:host(.sd-toast--closing){animation:none}}.sd-toast__bar{width:6px;flex-shrink:0}.sd-toast__icon{padding:12px 0 0 12px;flex-shrink:0}.sd-toast__icon svg{width:24px;height:24px}.sd-toast__content{flex-grow:1;padding:12px;display:flex;flex-direction:column}.sd-toast__header{display:flex;justify-content:space-between;align-items:flex-start;margin-bottom:4px}.sd-toast__title{font-size:16px;font-weight:600}.sd-toast__close{background:none;border:none;font-size:16px;cursor:pointer;line-height:1;padding:0;opacity:.5;transition:opacity .2s}.sd-toast__close:hover{opacity:1}.sd-toast__body{font-size:14px;line-height:1.4;word-break:break-word}.sd-toast__actions{display:flex;justify-content:flex-end;margin-top:8px}.msg-list{padding-left:0;list-style:none;margin:0}.toggle-more{background:none;border:none;padding:0;font:inherit;cursor:pointer;font-size:13px;margin-top:4px;font-weight:500;text-align:left}.toggle-more:hover{text-decoration:underline}.toggle-more:focus-visible{outline:2px solid currentColor;outline-offset:2px}.btn-action{border:none;padding:6px 12px;border-radius:4px;font-weight:600;cursor:pointer;font-size:13px;transition:filter .2s}.btn-action:hover{filter:brightness(.95)}\n"] }]
|
|
134
123
|
}], ctorParameters: () => [{ type: SdNotifyService }, { type: i2.DomSanitizer }], propDecorators: { data: [{
|
|
135
124
|
type: Input,
|
|
136
125
|
args: [{ required: true }]
|
|
@@ -165,9 +154,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
165
154
|
}] });
|
|
166
155
|
|
|
167
156
|
class SdNotifyService {
|
|
168
|
-
appRef;
|
|
169
|
-
injector;
|
|
170
|
-
document;
|
|
171
157
|
toasts = signal([]);
|
|
172
158
|
// Constants
|
|
173
159
|
#DEBOUNCE_TIME = 500;
|
|
@@ -177,10 +163,11 @@ class SdNotifyService {
|
|
|
177
163
|
// State
|
|
178
164
|
#buffer = {};
|
|
179
165
|
#timers = {};
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
166
|
+
appRef = inject(ApplicationRef);
|
|
167
|
+
injector = inject(EnvironmentInjector);
|
|
168
|
+
document = inject(DOCUMENT);
|
|
169
|
+
#i18n = inject(I18nService);
|
|
170
|
+
constructor() {
|
|
184
171
|
this.#initContainer();
|
|
185
172
|
}
|
|
186
173
|
#initContainer() {
|
|
@@ -206,14 +193,14 @@ class SdNotifyService {
|
|
|
206
193
|
this.#addToBuffer('error', message, option);
|
|
207
194
|
}
|
|
208
195
|
remove(id) {
|
|
209
|
-
this.toasts.update(
|
|
196
|
+
this.toasts.update(current => current.filter(t => t.id !== id));
|
|
210
197
|
}
|
|
211
198
|
clearAll() {
|
|
212
199
|
this.toasts.set([]);
|
|
213
200
|
this.#clearAllTimers();
|
|
214
201
|
}
|
|
215
202
|
clearByType(type) {
|
|
216
|
-
this.toasts.update(
|
|
203
|
+
this.toasts.update(current => current.filter(t => t.type !== type));
|
|
217
204
|
}
|
|
218
205
|
// Private helpers
|
|
219
206
|
#addImmediate(type, message, option) {
|
|
@@ -225,9 +212,9 @@ class SdNotifyService {
|
|
|
225
212
|
duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,
|
|
226
213
|
actionLabel: option?.actionLabel,
|
|
227
214
|
onAction: option?.onAction,
|
|
228
|
-
html: option?.html
|
|
215
|
+
html: option?.html,
|
|
229
216
|
};
|
|
230
|
-
this.toasts.update(
|
|
217
|
+
this.toasts.update(current => {
|
|
231
218
|
const updated = [newToast, ...current];
|
|
232
219
|
return updated.slice(0, this.#MAX_TOASTS);
|
|
233
220
|
});
|
|
@@ -253,8 +240,7 @@ class SdNotifyService {
|
|
|
253
240
|
this.#timers[type] = null;
|
|
254
241
|
if (messages.length === 0)
|
|
255
242
|
return;
|
|
256
|
-
const
|
|
257
|
-
const title = option?.title ?? typeLabel;
|
|
243
|
+
const title = option?.title ?? this.#i18n.t(`core.notify.type.${type}`);
|
|
258
244
|
const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;
|
|
259
245
|
const newToast = {
|
|
260
246
|
id: Utilities.generateUuid(),
|
|
@@ -264,9 +250,9 @@ class SdNotifyService {
|
|
|
264
250
|
duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,
|
|
265
251
|
actionLabel: option?.actionLabel,
|
|
266
252
|
onAction: option?.onAction,
|
|
267
|
-
html: option?.html
|
|
253
|
+
html: option?.html,
|
|
268
254
|
};
|
|
269
|
-
this.toasts.update(
|
|
255
|
+
this.toasts.update(current => {
|
|
270
256
|
const updated = [newToast, ...current];
|
|
271
257
|
return updated.slice(0, this.#MAX_TOASTS);
|
|
272
258
|
});
|
|
@@ -278,7 +264,7 @@ class SdNotifyService {
|
|
|
278
264
|
});
|
|
279
265
|
this.#timers = {};
|
|
280
266
|
}
|
|
281
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, deps: [
|
|
267
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
282
268
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, providedIn: 'root' });
|
|
283
269
|
}
|
|
284
270
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdNotifyService, decorators: [{
|
|
@@ -286,10 +272,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
286
272
|
args: [{
|
|
287
273
|
providedIn: 'root',
|
|
288
274
|
}]
|
|
289
|
-
}], ctorParameters: () => [
|
|
290
|
-
type: Inject,
|
|
291
|
-
args: [DOCUMENT]
|
|
292
|
-
}] }] });
|
|
275
|
+
}], ctorParameters: () => [] });
|
|
293
276
|
|
|
294
277
|
/**
|
|
295
278
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-services-notify.mjs","sources":["../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.ts","../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.html","../../../projects/sdcorejs-angular/services/notify/src/components/toast-container.component.ts","../../../projects/sdcorejs-angular/services/notify/src/notify.service.ts","../../../projects/sdcorejs-angular/services/notify/sdcorejs-angular-services-notify.ts"],"sourcesContent":["import { Component, Input, signal, HostListener, OnInit, OnDestroy, SecurityContext } from '@angular/core';\nimport { CommonModule } from '@angular/common';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { animate, style, transition, trigger } from '@angular/animations';\nimport { TranslatePipe } from '@sdcorejs/angular/i18n';\nimport { ToastData } from '../../notify.model';\nimport { SdNotifyService } from '../../notify.service';\n\n@Component({\n selector: 'toast',\n standalone: true,\n imports: [CommonModule, TranslatePipe],\n templateUrl: './toast.component.html',\n styleUrls: ['./toast.component.scss'],\n animations: [\n trigger('toastAnimation', [\n transition(':enter', [\n style({ transform: 'translateX(100%)', opacity: 0 }),\n animate('300ms ease-out', style({ transform: 'translateX(0)', opacity: 1 }))\n ]),\n transition(':leave', [\n animate('200ms ease-in', style({ transform: 'translateX(100%)', opacity: 0 }))\n ])\n ])\n ],\n host: {\n '[@toastAnimation]': 'true',\n '[class]': '\"bg-white sd-toast\"',\n // E2E hooks đọc bởi sd-autoid-inspector: autoid theo loại + state type/title/message.\n '[attr.data-autoid]': 'autoId',\n '[attr.data-type]': 'data.type',\n '[attr.data-title]': 'data.title ?? null',\n '[attr.data-message]': 'dataMessage'\n }\n})\nexport class ToastComponent implements OnInit, OnDestroy {\n @Input({ required: true }) data!: ToastData;\n \n isExpanded = signal(false);\n readonly MAX_SHOW = 2;\n\n // --- Logic Timer ---\n private timer: any;\n private start!: number;\n private remaining!: number;\n\n constructor(\n private notifyService: SdNotifyService,\n private sanitizer: DomSanitizer\n ) {}\n\n /**\n * Sanitize HTML tường minh trước khi render qua `[innerHTML]` — strip\n * `<script>`, event handler (`on*`), `javascript:` URL. Chỉ chạy ở nhánh\n * `data.html === true`; nhánh mặc định render text (auto-escape) nên không\n * có sink này. // why: phơi rõ việc sanitize cho review/scanner bảo mật.\n */\n sanitizeHtml(value: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';\n }\n\n /** Message ở dạng chuỗi đơn (chỉ dùng khi !isMultiMessage). */\n get singleMessage(): string {\n return this.data.message as string;\n }\n\n ngOnInit() {\n // Khởi tạo thời gian còn lại bằng duration ban đầu\n this.remaining = this.data.duration;\n // Bắt đầu đếm ngược ngay khi hiện ra\n this.resumeTimer();\n }\n\n ngOnDestroy() {\n clearTimeout(this.timer);\n }\n\n // --- Logic Pause/Resume ---\n\n @HostListener('mouseenter')\n pauseTimer() {\n if (this.timer) {\n // Xóa timer hiện tại\n clearTimeout(this.timer);\n this.timer = null;\n // Tính toán thời gian đã trôi qua để trừ đi\n this.remaining -= Date.now() - this.start;\n }\n }\n\n @HostListener('mouseleave')\n resumeTimer() {\n // Chỉ chạy tiếp nếu còn thời gian và timer chưa chạy\n if (this.remaining > 0 && !this.timer) {\n this.start = Date.now();\n this.timer = setTimeout(() => {\n this.close();\n }, this.remaining);\n }\n }\n\n // --- Logic cũ ---\n\n close() {\n this.notifyService.remove(this.data.id);\n }\n\n onActionClick() {\n this.data.onAction?.();\n }\n\n toggleExpand() {\n this.isExpanded.update(v => !v);\n }\n\n get isMultiMessage(): boolean {\n return Array.isArray(this.data.message);\n }\n\n get messages(): string[] {\n return this.data.message as string[];\n }\n\n get displayMessages(): string[] {\n const msgs = this.messages;\n if (this.isExpanded()) return msgs;\n return msgs.slice(0, this.MAX_SHOW);\n }\n\n get restCount(): number {\n return this.messages.length - this.MAX_SHOW;\n }\n\n get hasMore(): boolean {\n return this.isMultiMessage && this.messages.length > this.MAX_SHOW;\n }\n\n /** autoid theo loại toast để E2E chọn trực tiếp, vd `services-notify-toast-success`. */\n get autoId(): string {\n return `services-notify-toast-${this.data.type}`;\n }\n\n /** Gộp message (mảng nối ' | ') để phơi ra data-message cho E2E. */\n get dataMessage(): string {\n return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;\n }\n}","<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">✕</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- why: mặc định render TEXT (auto-escape) — an toàn. Chỉ render HTML\n (đã sanitize) khi data.html=true. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n }\n @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button type=\"button\" \n class=\"toggle-more btn-link\" \n [class]=\"'text-' + data.type\" \n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button class=\"btn-action\" \n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>","import { Component, signal, WritableSignal } from '@angular/core';\nimport { ToastData } from '../notify.model';\nimport { ToastComponent } from './toast/toast.component';\n\n@Component({\n selector: 'toast-container',\n standalone: true,\n imports: [ToastComponent],\n template: `\n <div class=\"toast-container\">\n @for (toast of toasts(); track toast.id) {\n <toast [data]=\"toast\"> </toast>\n }\n </div>\n `,\n styles: [\n `\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n pointer-events: none; /* Cho phép click xuyên qua vùng trống */\n }\n sd-toast {\n pointer-events: auto; /* Bật lại click cho toast */\n }\n `,\n ],\n})\nexport class ToastContainerComponent {\n toasts: WritableSignal<ToastData[]> = signal([]);\n}\n","import { DOCUMENT } from '@angular/common';\nimport {\n ApplicationRef,\n createComponent,\n EnvironmentInjector,\n Inject,\n Injectable,\n signal\n} from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { ToastContainerComponent } from './components/toast-container.component';\nimport { NotifyOption, ToastData, ToastType } from './notify.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdNotifyService {\n readonly toasts = signal<ToastData[]>([]);\n\n // Constants\n readonly #DEBOUNCE_TIME = 500;\n readonly #DEFAULT_SUCCESS_DURATION = 3000;\n readonly #DEFAULT_BUFFERED_DURATION = 5000;\n readonly #MAX_TOASTS = 5;\n\n // State\n #buffer: Record<string, string[]> = {};\n #timers: Record<string, ReturnType<typeof setTimeout> | null> = {};\n\n constructor(\n private appRef: ApplicationRef,\n private injector: EnvironmentInjector,\n @Inject(DOCUMENT) private document: Document\n ) {\n this.#initContainer();\n }\n\n #initContainer() {\n const componentRef = createComponent(ToastContainerComponent, {\n environmentInjector: this.injector,\n });\n componentRef.instance.toasts = this.toasts;\n this.appRef.attachView(componentRef.hostView);\n const domElem = (componentRef.hostView as any).rootNodes[0] as HTMLElement;\n this.document.body.appendChild(domElem);\n }\n\n // Public API\n success(message: string, option?: NotifyOption) {\n this.#addImmediate('success', message, option);\n }\n\n info(message: string, option?: NotifyOption) {\n this.#addImmediate('info', message, option);\n }\n\n warning(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('warning', message, option);\n }\n\n error(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('error', message, option);\n }\n\n remove(id: string) {\n this.toasts.update((current) => current.filter((t) => t.id !== id));\n }\n\n clearAll() {\n this.toasts.set([]);\n this.#clearAllTimers();\n }\n\n clearByType(type: ToastType) {\n this.toasts.update((current) => current.filter((t) => t.type !== type));\n }\n\n // Private helpers\n #addImmediate(type: ToastType, message: string, option?: NotifyOption) {\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message,\n title: option?.title,\n duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #addToBuffer(type: ToastType, message: string | string[], option?: NotifyOption) {\n if (!this.#buffer[type]) {\n this.#buffer[type] = [];\n }\n \n const msgs = Array.isArray(message) ? message : [message];\n this.#buffer[type].push(...msgs);\n\n // Clear existing timer and set new one\n if (this.#timers[type]) {\n clearTimeout(this.#timers[type]!);\n }\n\n this.#timers[type] = setTimeout(() => {\n this.#flushBuffer(type, option);\n }, this.#DEBOUNCE_TIME);\n }\n\n #flushBuffer(type: ToastType, option?: NotifyOption) {\n const messages = [...new Set(this.#buffer[type])];\n \n // Cleanup\n this.#buffer[type] = [];\n this.#timers[type] = null;\n\n if (messages.length === 0) return;\n\n const typeLabel = type === 'error' ? 'Error' : 'Warning';\n const title = option?.title ?? typeLabel;\n const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;\n\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message: messages.length === 1 ? messages[0] : messages, \n title: finalTitle,\n duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html\n };\n\n this.toasts.update((current) => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #clearAllTimers() {\n Object.values(this.#timers).forEach(timer => {\n if (timer) clearTimeout(timer);\n });\n this.#timers = {};\n }\n}","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.SdNotifyService"],"mappings":";;;;;;;;MAmCa,cAAc,CAAA;AAYf,IAAA,aAAA;AACA,IAAA,SAAA;AAZiB,IAAA,IAAI;AAE/B,IAAA,UAAU,GAAG,MAAM,CAAC,KAAK,CAAC;IACjB,QAAQ,GAAG,CAAC;;AAGb,IAAA,KAAK;AACL,IAAA,KAAK;AACL,IAAA,SAAS;IAEjB,WAAA,CACU,aAA8B,EAC9B,SAAuB,EAAA;QADvB,IAAA,CAAA,aAAa,GAAb,aAAa;QACb,IAAA,CAAA,SAAS,GAAT,SAAS;IAChB;AAEH;;;;;AAKG;AACH,IAAA,YAAY,CAAC,KAAa,EAAA;AACxB,QAAA,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,eAAe,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE;IACnE;;AAGA,IAAA,IAAI,aAAa,GAAA;AACf,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,OAAiB;IACpC;IAEA,QAAQ,GAAA;;QAEN,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ;;QAEnC,IAAI,CAAC,WAAW,EAAE;IACpB;IAEA,WAAW,GAAA;AACT,QAAA,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;IAC1B;;IAKA,UAAU,GAAA;AACR,QAAA,IAAI,IAAI,CAAC,KAAK,EAAE;;AAEd,YAAA,YAAY,CAAC,IAAI,CAAC,KAAK,CAAC;AACxB,YAAA,IAAI,CAAC,KAAK,GAAG,IAAI;;YAEjB,IAAI,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,KAAK;QAC3C;IACF;IAGA,WAAW,GAAA;;QAET,IAAI,IAAI,CAAC,SAAS,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE;AACrC,YAAA,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE;AACvB,YAAA,IAAI,CAAC,KAAK,GAAG,UAAU,CAAC,MAAK;gBAC3B,IAAI,CAAC,KAAK,EAAE;AACd,YAAA,CAAC,EAAE,IAAI,CAAC,SAAS,CAAC;QACpB;IACF;;IAIA,KAAK,GAAA;QACH,IAAI,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC;IACzC;IAEA,aAAa,GAAA;AACX,QAAA,IAAI,CAAC,IAAI,CAAC,QAAQ,IAAI;IACxB;IAEA,YAAY,GAAA;AACV,QAAA,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;IACjC;AAEA,IAAA,IAAI,cAAc,GAAA;QAChB,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC;IACzC;AAEA,IAAA,IAAI,QAAQ,GAAA;AACV,QAAA,OAAO,IAAI,CAAC,IAAI,CAAC,OAAmB;IACtC;AAEA,IAAA,IAAI,eAAe,GAAA;AACjB,QAAA,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ;QAC1B,IAAI,IAAI,CAAC,UAAU,EAAE;AAAE,YAAA,OAAO,IAAI;QAClC,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,QAAQ,CAAC;IACrC;AAEA,IAAA,IAAI,SAAS,GAAA;QACX,OAAO,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;IAC7C;AAEA,IAAA,IAAI,OAAO,GAAA;AACT,QAAA,OAAO,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,QAAQ;IACpE;;AAGA,IAAA,IAAI,MAAM,GAAA;AACR,QAAA,OAAO,yBAAyB,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE;IAClD;;AAGA,IAAA,IAAI,WAAW,GAAA;AACb,QAAA,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,OAAO;IAC7F;wGA9GW,cAAc,EAAA,IAAA,EAAA,CAAA,EAAA,KAAA,EAAAA,eAAA,EAAA,EAAA,EAAA,KAAA,EAAA,EAAA,CAAA,YAAA,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAd,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,cAAc,wYCnC3B,kiFAkEM,EAAA,MAAA,EAAA,CAAA,80CAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,UAAA,EAAA,IAAA,EDvDM,YAAY,EAAA,EAAA,EAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAE,aAAa,EAAA,IAAA,EAAA,WAAA,EAAA,CAAA,EAAA,UAAA,EAGzB;YACV,OAAO,CAAC,gBAAgB,EAAE;gBACxB,UAAU,CAAC,QAAQ,EAAE;oBACnB,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACpD,oBAAA,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;iBAC5E,CAAC;gBACF,UAAU,CAAC,QAAQ,EAAE;AACnB,oBAAA,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;iBAC9E;aACF;AACF,SAAA,EAAA,CAAA;;4FAWU,cAAc,EAAA,UAAA,EAAA,CAAA;kBA3B1B,SAAS;+BACE,OAAO,EAAA,UAAA,EACL,IAAI,EAAA,OAAA,EACP,CAAC,YAAY,EAAE,aAAa,CAAC,EAAA,UAAA,EAG1B;wBACV,OAAO,CAAC,gBAAgB,EAAE;4BACxB,UAAU,CAAC,QAAQ,EAAE;gCACnB,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;AACpD,gCAAA,OAAO,CAAC,gBAAgB,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,eAAe,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;6BAC5E,CAAC;4BACF,UAAU,CAAC,QAAQ,EAAE;AACnB,gCAAA,OAAO,CAAC,eAAe,EAAE,KAAK,CAAC,EAAE,SAAS,EAAE,kBAAkB,EAAE,OAAO,EAAE,CAAC,EAAE,CAAC;6BAC9E;yBACF;qBACF,EAAA,IAAA,EACK;AACJ,wBAAA,mBAAmB,EAAE,MAAM;AAC3B,wBAAA,SAAS,EAAE,qBAAqB;;AAEhC,wBAAA,oBAAoB,EAAE,QAAQ;AAC9B,wBAAA,kBAAkB,EAAE,WAAW;AAC/B,wBAAA,mBAAmB,EAAE,oBAAoB;AACzC,wBAAA,qBAAqB,EAAE;AACxB,qBAAA,EAAA,QAAA,EAAA,kiFAAA,EAAA,MAAA,EAAA,CAAA,80CAAA,CAAA,EAAA;4GAG0B,IAAI,EAAA,CAAA;sBAA9B,KAAK;uBAAC,EAAE,QAAQ,EAAE,IAAI,EAAE;gBA4CzB,UAAU,EAAA,CAAA;sBADT,YAAY;uBAAC,YAAY;gBAY1B,WAAW,EAAA,CAAA;sBADV,YAAY;uBAAC,YAAY;;;ME1Df,uBAAuB,CAAA;AAClC,IAAA,MAAM,GAAgC,MAAM,CAAC,EAAE,CAAC;wGADrC,uBAAuB,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,SAAA,EAAA,CAAA;AAAvB,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,oBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,IAAA,EAAA,uBAAuB,EAAA,YAAA,EAAA,IAAA,EAAA,QAAA,EAAA,iBAAA,EAAA,QAAA,EAAA,EAAA,EAAA,QAAA,EAxBxB;;;;;;AAMT,EAAA,CAAA,EAAA,QAAA,EAAA,IAAA,EAAA,MAAA,EAAA,CAAA,yJAAA,CAAA,EAAA,YAAA,EAAA,CAAA,EAAA,IAAA,EAAA,WAAA,EAAA,IAAA,EAPS,cAAc,EAAA,QAAA,EAAA,OAAA,EAAA,MAAA,EAAA,CAAA,MAAA,CAAA,EAAA,CAAA,EAAA,CAAA;;4FAyBb,uBAAuB,EAAA,UAAA,EAAA,CAAA;kBA5BnC,SAAS;AACE,YAAA,IAAA,EAAA,CAAA,EAAA,QAAA,EAAA,iBAAiB,cACf,IAAI,EAAA,OAAA,EACP,CAAC,cAAc,CAAC,EAAA,QAAA,EACf;;;;;;AAMT,EAAA,CAAA,EAAA,MAAA,EAAA,CAAA,yJAAA,CAAA,EAAA;;;MCEU,eAAe,CAAA;AAchB,IAAA,MAAA;AACA,IAAA,QAAA;AACkB,IAAA,QAAA;AAfnB,IAAA,MAAM,GAAG,MAAM,CAAc,EAAE,CAAC;;IAGhC,cAAc,GAAG,GAAG;IACpB,yBAAyB,GAAG,IAAI;IAChC,0BAA0B,GAAG,IAAI;IACjC,WAAW,GAAG,CAAC;;IAGxB,OAAO,GAA6B,EAAE;IACtC,OAAO,GAAyD,EAAE;AAElE,IAAA,WAAA,CACU,MAAsB,EACtB,QAA6B,EACX,QAAkB,EAAA;QAFpC,IAAA,CAAA,MAAM,GAAN,MAAM;QACN,IAAA,CAAA,QAAQ,GAAR,QAAQ;QACU,IAAA,CAAA,QAAQ,GAAR,QAAQ;QAElC,IAAI,CAAC,cAAc,EAAE;IACvB;IAEA,cAAc,GAAA;AACZ,QAAA,MAAM,YAAY,GAAG,eAAe,CAAC,uBAAuB,EAAE;YAC5D,mBAAmB,EAAE,IAAI,CAAC,QAAQ;AACnC,SAAA,CAAC;QACF,YAAY,CAAC,QAAQ,CAAC,MAAM,GAAG,IAAI,CAAC,MAAM;QAC1C,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,QAAQ,CAAC;QAC7C,MAAM,OAAO,GAAI,YAAY,CAAC,QAAgB,CAAC,SAAS,CAAC,CAAC,CAAgB;QAC1E,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC;IACzC;;IAGA,OAAO,CAAC,OAAe,EAAE,MAAqB,EAAA;QAC5C,IAAI,CAAC,aAAa,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC;IAChD;IAEA,IAAI,CAAC,OAAe,EAAE,MAAqB,EAAA;QACzC,IAAI,CAAC,aAAa,CAAC,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC;IAC7C;IAEA,OAAO,CAAC,OAA0B,EAAE,MAAqB,EAAA;QACvD,IAAI,CAAC,YAAY,CAAC,SAAS,EAAE,OAAO,EAAE,MAAM,CAAC;IAC/C;IAEA,KAAK,CAAC,OAA0B,EAAE,MAAqB,EAAA;QACrD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,CAAC;IAC7C;AAEA,IAAA,MAAM,CAAC,EAAU,EAAA;QACf,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;IACrE;IAEA,QAAQ,GAAA;AACN,QAAA,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;QACnB,IAAI,CAAC,eAAe,EAAE;IACxB;AAEA,IAAA,WAAW,CAAC,IAAe,EAAA;QACzB,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,IAAI,CAAC,CAAC;IACzE;;AAGA,IAAA,aAAa,CAAC,IAAe,EAAE,OAAe,EAAE,MAAqB,EAAA;AACnE,QAAA,MAAM,QAAQ,GAAc;AAC1B,YAAA,EAAE,EAAE,SAAS,CAAC,YAAY,EAAE;YAC5B,IAAI;YACJ,OAAO;YACP,KAAK,EAAE,MAAM,EAAE,KAAK;AACpB,YAAA,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,IAAI,CAAC,yBAAyB;YAC5D,WAAW,EAAE,MAAM,EAAE,WAAW;YAChC,QAAQ,EAAE,MAAM,EAAE,QAAQ;YAC1B,IAAI,EAAE,MAAM,EAAE;SACf;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAI;YAC7B,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC;YACtC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;AAC3C,QAAA,CAAC,CAAC;IACJ;AAEA,IAAA,YAAY,CAAC,IAAe,EAAE,OAA0B,EAAE,MAAqB,EAAA;QAC7E,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;AACvB,YAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;QACzB;AAEA,QAAA,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,GAAG,OAAO,GAAG,CAAC,OAAO,CAAC;QACzD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC;;AAGhC,QAAA,IAAI,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;YACtB,YAAY,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAE,CAAC;QACnC;QAEA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,UAAU,CAAC,MAAK;AACnC,YAAA,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC;AACjC,QAAA,CAAC,EAAE,IAAI,CAAC,cAAc,CAAC;IACzB;IAEA,YAAY,CAAC,IAAe,EAAE,MAAqB,EAAA;AACjD,QAAA,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;;AAGjD,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,EAAE;AACvB,QAAA,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI;AAEzB,QAAA,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;YAAE;AAE3B,QAAA,MAAM,SAAS,GAAG,IAAI,KAAK,OAAO,GAAG,OAAO,GAAG,SAAS;AACxD,QAAA,MAAM,KAAK,GAAG,MAAM,EAAE,KAAK,IAAI,SAAS;QACxC,MAAM,UAAU,GAAG,QAAQ,CAAC,MAAM,GAAG,CAAC,GAAG,GAAG,KAAK,CAAA,EAAA,EAAK,QAAQ,CAAC,MAAM,GAAG,GAAG,KAAK;AAEhF,QAAA,MAAM,QAAQ,GAAc;AAC1B,YAAA,EAAE,EAAE,SAAS,CAAC,YAAY,EAAE;YAC5B,IAAI;AACJ,YAAA,OAAO,EAAE,QAAQ,CAAC,MAAM,KAAK,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,GAAG,QAAQ;AACvD,YAAA,KAAK,EAAE,UAAU;AACjB,YAAA,QAAQ,EAAE,MAAM,EAAE,QAAQ,IAAI,IAAI,CAAC,0BAA0B;YAC7D,WAAW,EAAE,MAAM,EAAE,WAAW;YAChC,QAAQ,EAAE,MAAM,EAAE,QAAQ;YAC1B,IAAI,EAAE,MAAM,EAAE;SACf;QAED,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,OAAO,KAAI;YAC7B,MAAM,OAAO,GAAG,CAAC,QAAQ,EAAE,GAAG,OAAO,CAAC;YACtC,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,IAAI,CAAC,WAAW,CAAC;AAC3C,QAAA,CAAC,CAAC;IACJ;IAEA,eAAe,GAAA;AACb,QAAA,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,CAAC,KAAK,IAAG;AAC1C,YAAA,IAAI,KAAK;gBAAE,YAAY,CAAC,KAAK,CAAC;AAChC,QAAA,CAAC,CAAC;AACF,QAAA,IAAI,CAAC,OAAO,GAAG,EAAE;IACnB;AArIW,IAAA,OAAA,IAAA,GAAA,EAAA,CAAA,kBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,mFAgBhB,QAAQ,EAAA,CAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,UAAA,EAAA,CAAA;AAhBP,IAAA,OAAA,KAAA,GAAA,EAAA,CAAA,qBAAA,CAAA,EAAA,UAAA,EAAA,QAAA,EAAA,OAAA,EAAA,SAAA,EAAA,QAAA,EAAA,EAAA,EAAA,IAAA,EAAA,eAAe,cAFd,MAAM,EAAA,CAAA;;4FAEP,eAAe,EAAA,UAAA,EAAA,CAAA;kBAH3B,UAAU;AAAC,YAAA,IAAA,EAAA,CAAA;AACV,oBAAA,UAAU,EAAE,MAAM;AACnB,iBAAA;;0BAiBI,MAAM;2BAAC,QAAQ;;;AChCpB;;AAEG;;;;"}
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-services-notify.mjs","sources":["../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.ts","../../../projects/sdcorejs-angular/services/notify/src/components/toast/toast.component.html","../../../projects/sdcorejs-angular/services/notify/src/components/toast-container.component.ts","../../../projects/sdcorejs-angular/services/notify/src/notify.service.ts","../../../projects/sdcorejs-angular/services/notify/sdcorejs-angular-services-notify.ts"],"sourcesContent":["import { CommonModule } from '@angular/common';\nimport {\n ChangeDetectionStrategy,\n Component,\n HostListener,\n Input,\n OnDestroy,\n OnInit,\n SecurityContext,\n computed,\n signal,\n} from '@angular/core';\nimport { DomSanitizer } from '@angular/platform-browser';\nimport { TranslatePipe } from '@sdcorejs/angular/i18n';\n\nimport { ToastData } from '../../notify.model';\nimport { SdNotifyService } from '../../notify.service';\n\nconst TOAST_EXIT_ANIMATION_MS = 200;\n\n@Component({\n selector: 'toast',\n standalone: true,\n imports: [CommonModule, TranslatePipe],\n templateUrl: './toast.component.html',\n styleUrl: './toast.component.scss',\n changeDetection: ChangeDetectionStrategy.OnPush,\n host: {\n '[class]': 'hostClasses()',\n '[attr.data-autoid]': 'autoId',\n '[attr.data-type]': 'data.type',\n '[attr.data-title]': 'data.title ?? null',\n '[attr.data-message]': 'dataMessage',\n },\n})\nexport class ToastComponent implements OnInit, OnDestroy {\n @Input({ required: true }) data!: ToastData;\n\n readonly isExpanded = signal(false);\n readonly isClosing = signal(false);\n readonly hostClasses = computed(() => (this.isClosing() ? 'bg-white sd-toast sd-toast--closing' : 'bg-white sd-toast'));\n readonly MAX_SHOW = 2;\n\n private timer: ReturnType<typeof setTimeout> | null = null;\n private closeTimer: ReturnType<typeof setTimeout> | null = null;\n private start = 0;\n private remaining = 0;\n\n constructor(\n private notifyService: SdNotifyService,\n private sanitizer: DomSanitizer\n ) {}\n\n ngOnInit(): void {\n this.remaining = this.data.duration;\n this.resumeTimer();\n }\n\n ngOnDestroy(): void {\n this.clearAutoDismissTimer();\n this.clearCloseTimer();\n }\n\n @HostListener('mouseenter')\n pauseTimer(): void {\n if (!this.timer || this.isClosing()) {\n return;\n }\n\n clearTimeout(this.timer);\n this.timer = null;\n this.remaining = Math.max(this.remaining - (Date.now() - this.start), 0);\n }\n\n @HostListener('mouseleave')\n resumeTimer(): void {\n if (this.remaining <= 0 || this.timer || this.isClosing()) {\n return;\n }\n\n this.start = Date.now();\n this.timer = setTimeout(() => {\n this.close();\n }, this.remaining);\n }\n\n close(): void {\n if (this.isClosing()) {\n return;\n }\n\n this.clearAutoDismissTimer();\n this.isClosing.set(true);\n this.closeTimer = setTimeout(() => {\n this.closeTimer = null;\n this.notifyService.remove(this.data.id);\n }, TOAST_EXIT_ANIMATION_MS);\n }\n\n onActionClick(): void {\n this.data.onAction?.();\n }\n\n toggleExpand(): void {\n this.isExpanded.update(v => !v);\n }\n\n sanitizeHtml(value: string): string {\n return this.sanitizer.sanitize(SecurityContext.HTML, value) ?? '';\n }\n\n get singleMessage(): string {\n return this.data.message as string;\n }\n\n get isMultiMessage(): boolean {\n return Array.isArray(this.data.message);\n }\n\n get messages(): string[] {\n return this.data.message as string[];\n }\n\n get displayMessages(): string[] {\n const msgs = this.messages;\n if (this.isExpanded()) return msgs;\n return msgs.slice(0, this.MAX_SHOW);\n }\n\n get restCount(): number {\n return this.messages.length - this.MAX_SHOW;\n }\n\n get hasMore(): boolean {\n return this.isMultiMessage && this.messages.length > this.MAX_SHOW;\n }\n\n get autoId(): string {\n return `services-notify-toast-${this.data.type}`;\n }\n\n get dataMessage(): string {\n return Array.isArray(this.data.message) ? this.data.message.join(' | ') : this.data.message;\n }\n\n private clearAutoDismissTimer(): void {\n if (!this.timer) {\n return;\n }\n\n clearTimeout(this.timer);\n this.timer = null;\n }\n\n private clearCloseTimer(): void {\n if (!this.closeTimer) {\n return;\n }\n\n clearTimeout(this.closeTimer);\n this.closeTimer = null;\n }\n}\n","<div class=\"sd-toast__bar\" [class]=\"'bg-' + data.type\"></div>\n\n<div class=\"sd-toast__icon\" [class]=\"'text-' + data.type\">\n @switch (data.type) {\n @case ('success') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-2 15l-5-5 1.41-1.41L10 14.17l7.59-7.59L19 8l-9 9z\"/></svg>\n }\n @case ('info') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-6h2v6zm0-8h-2V7h2v2z\"/></svg>\n }\n @case ('warning') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M1 21h22L12 2 1 21zm12-3h-2v-2h2v2zm0-4h-2v-4h2v4z\"/></svg>\n }\n @case ('error') {\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\"><path d=\"M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm1 15h-2v-2h2v2zm0-4h-2V7h2v6z\"/></svg>\n }\n }\n</div>\n\n<div class=\"sd-toast__content\">\n <div class=\"sd-toast__header\">\n <strong class=\"sd-toast__title\">{{ data.title || ('core.notify.type.' + data.type | translate) }}</strong>\n <button class=\"sd-toast__close\" (click)=\"close()\">×</button>\n </div>\n\n <div class=\"sd-toast__body\">\n @if (!isMultiMessage) {\n <!-- Text rendering is the default safe path; HTML is opt-in and sanitized. -->\n @if (data.html) {\n <div [innerHTML]=\"sanitizeHtml(singleMessage)\"></div>\n } @else {\n <div>{{ singleMessage }}</div>\n }\n } @else {\n <ul class=\"msg-list\">\n @for (msg of displayMessages; track msg) {\n @if (data.html) {\n <li [innerHTML]=\"sanitizeHtml('- ' + msg)\"></li>\n } @else {\n <li>{{ '- ' + msg }}</li>\n }\n }\n </ul>\n\n @if (hasMore) {\n <button\n type=\"button\"\n class=\"toggle-more btn-link\"\n [class]=\"'text-' + data.type\"\n (click)=\"toggleExpand()\">\n {{ isExpanded() ? ('core.notify.show-less' | translate) : ('core.notify.show-more' | translate: { count: restCount }) }}\n </button>\n }\n }\n </div>\n\n @if (data.actionLabel) {\n <div class=\"sd-toast__actions\">\n <button\n class=\"btn-action\"\n [class]=\"'bg-' + data.type + '-light text-' + data.type\"\n (click)=\"onActionClick()\">\n {{ data.actionLabel }}\n </button>\n </div>\n }\n</div>\n","import { Component, signal, WritableSignal } from '@angular/core';\nimport { ToastData } from '../notify.model';\nimport { ToastComponent } from './toast/toast.component';\n\n@Component({\n selector: 'toast-container',\n standalone: true,\n imports: [ToastComponent],\n template: `\n <div class=\"toast-container\">\n @for (toast of toasts(); track toast.id) {\n <toast [data]=\"toast\"> </toast>\n }\n </div>\n `,\n styles: [\n `\n .toast-container {\n position: fixed;\n top: 20px;\n right: 20px;\n z-index: 9999;\n display: flex;\n flex-direction: column;\n pointer-events: none; /* Cho phép click xuyên qua vùng trống */\n }\n sd-toast {\n pointer-events: auto; /* Bật lại click cho toast */\n }\n `,\n ],\n})\nexport class ToastContainerComponent {\n toasts: WritableSignal<ToastData[]> = signal([]);\n}\n","import { DOCUMENT } from '@angular/common';\nimport { ApplicationRef, createComponent, EnvironmentInjector, inject, Injectable, signal } from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\n\nimport { I18nService } from '@sdcorejs/angular/i18n';\n\nimport { ToastContainerComponent } from './components/toast-container.component';\nimport { NotifyOption, ToastData, ToastType } from './notify.model';\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdNotifyService {\n readonly toasts = signal<ToastData[]>([]);\n\n // Constants\n readonly #DEBOUNCE_TIME = 500;\n readonly #DEFAULT_SUCCESS_DURATION = 3000;\n readonly #DEFAULT_BUFFERED_DURATION = 5000;\n readonly #MAX_TOASTS = 5;\n\n // State\n #buffer: Record<string, string[]> = {};\n #timers: Record<string, ReturnType<typeof setTimeout> | null> = {};\n private readonly appRef = inject(ApplicationRef);\n private readonly injector = inject(EnvironmentInjector);\n private readonly document = inject(DOCUMENT);\n readonly #i18n = inject(I18nService);\n\n constructor() {\n this.#initContainer();\n }\n\n #initContainer() {\n const componentRef = createComponent(ToastContainerComponent, {\n environmentInjector: this.injector,\n });\n componentRef.instance.toasts = this.toasts;\n this.appRef.attachView(componentRef.hostView);\n const domElem = (componentRef.hostView as any).rootNodes[0] as HTMLElement;\n this.document.body.appendChild(domElem);\n }\n\n // Public API\n success(message: string, option?: NotifyOption) {\n this.#addImmediate('success', message, option);\n }\n\n info(message: string, option?: NotifyOption) {\n this.#addImmediate('info', message, option);\n }\n\n warning(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('warning', message, option);\n }\n\n error(message: string | string[], option?: NotifyOption) {\n this.#addToBuffer('error', message, option);\n }\n\n remove(id: string) {\n this.toasts.update(current => current.filter(t => t.id !== id));\n }\n\n clearAll() {\n this.toasts.set([]);\n this.#clearAllTimers();\n }\n\n clearByType(type: ToastType) {\n this.toasts.update(current => current.filter(t => t.type !== type));\n }\n\n // Private helpers\n #addImmediate(type: ToastType, message: string, option?: NotifyOption) {\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message,\n title: option?.title,\n duration: option?.duration ?? this.#DEFAULT_SUCCESS_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html,\n };\n\n this.toasts.update(current => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #addToBuffer(type: ToastType, message: string | string[], option?: NotifyOption) {\n if (!this.#buffer[type]) {\n this.#buffer[type] = [];\n }\n\n const msgs = Array.isArray(message) ? message : [message];\n this.#buffer[type].push(...msgs);\n\n // Clear existing timer and set new one\n if (this.#timers[type]) {\n clearTimeout(this.#timers[type]!);\n }\n\n this.#timers[type] = setTimeout(() => {\n this.#flushBuffer(type, option);\n }, this.#DEBOUNCE_TIME);\n }\n\n #flushBuffer(type: ToastType, option?: NotifyOption) {\n const messages = [...new Set(this.#buffer[type])];\n\n // Cleanup\n this.#buffer[type] = [];\n this.#timers[type] = null;\n\n if (messages.length === 0) return;\n\n const title = option?.title ?? this.#i18n.t(`core.notify.type.${type}`);\n const finalTitle = messages.length > 1 ? `${title} (${messages.length})` : title;\n\n const newToast: ToastData = {\n id: Utilities.generateUuid(),\n type,\n message: messages.length === 1 ? messages[0] : messages,\n title: finalTitle,\n duration: option?.duration ?? this.#DEFAULT_BUFFERED_DURATION,\n actionLabel: option?.actionLabel,\n onAction: option?.onAction,\n html: option?.html,\n };\n\n this.toasts.update(current => {\n const updated = [newToast, ...current];\n return updated.slice(0, this.#MAX_TOASTS);\n });\n }\n\n #clearAllTimers() {\n Object.values(this.#timers).forEach(timer => {\n if (timer) clearTimeout(timer);\n });\n this.#timers = {};\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":["i1.SdNotifyService"],"mappings":"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"}
|
|
@@ -1,20 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { InjectionToken,
|
|
2
|
+
import { InjectionToken, inject, Injectable } from '@angular/core';
|
|
3
3
|
import { Utilities } from '@sdcorejs/utils/fns';
|
|
4
4
|
import { BehaviorSubject } from 'rxjs';
|
|
5
5
|
import { map } from 'rxjs/operators';
|
|
6
6
|
|
|
7
7
|
const SD_STORAGE_CONFIG = new InjectionToken('storage.configuration');
|
|
8
8
|
|
|
9
|
-
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
10
9
|
class SdStorageService {
|
|
11
|
-
configuration;
|
|
12
10
|
// Dùng Map để quản lý bộ nhớ tốt hơn Record
|
|
13
11
|
#memoryCache = new Map();
|
|
14
12
|
#subjects = new Map();
|
|
15
|
-
|
|
16
|
-
this.configuration = configuration;
|
|
17
|
-
}
|
|
13
|
+
configuration = inject(SD_STORAGE_CONFIG, { optional: true });
|
|
18
14
|
create(key, option) {
|
|
19
15
|
if (!key)
|
|
20
16
|
throw new Error('Key is required');
|
|
@@ -71,7 +67,7 @@ class SdStorageService {
|
|
|
71
67
|
setSilent,
|
|
72
68
|
has,
|
|
73
69
|
remove,
|
|
74
|
-
// @ts-
|
|
70
|
+
// @ts-expect-error: Bổ sung vào interface nếu cần
|
|
75
71
|
destroy,
|
|
76
72
|
subject: subject,
|
|
77
73
|
observer: subject.asObservable().pipe(map(() => get())),
|
|
@@ -100,7 +96,7 @@ class SdStorageService {
|
|
|
100
96
|
// Convert string date back to Date object nếu cần
|
|
101
97
|
entry = {
|
|
102
98
|
data: parsed.data,
|
|
103
|
-
createdOn: new Date(parsed.createdOn)
|
|
99
|
+
createdOn: new Date(parsed.createdOn),
|
|
104
100
|
};
|
|
105
101
|
// Sync ngược vào RAM
|
|
106
102
|
this.#memoryCache.set(key, entry);
|
|
@@ -151,7 +147,7 @@ class SdStorageService {
|
|
|
151
147
|
return val;
|
|
152
148
|
return JSON.parse(JSON.stringify(val));
|
|
153
149
|
}
|
|
154
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, deps: [
|
|
150
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
155
151
|
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, providedIn: 'root' });
|
|
156
152
|
}
|
|
157
153
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdStorageService, decorators: [{
|
|
@@ -159,12 +155,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
159
155
|
args: [{
|
|
160
156
|
providedIn: 'root',
|
|
161
157
|
}]
|
|
162
|
-
}]
|
|
163
|
-
type: Inject,
|
|
164
|
-
args: [SD_STORAGE_CONFIG]
|
|
165
|
-
}, {
|
|
166
|
-
type: Optional
|
|
167
|
-
}] }] });
|
|
158
|
+
}] });
|
|
168
159
|
|
|
169
160
|
/**
|
|
170
161
|
* Generated bundle index. Do not edit.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-services-storage.mjs","sources":["../../../projects/sdcorejs-angular/services/storage/src/storage.model.ts","../../../projects/sdcorejs-angular/services/storage/src/storage.service.ts","../../../projects/sdcorejs-angular/services/storage/sdcorejs-angular-services-storage.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface SdStorageOption<T = any> {\n type?: 'session';\n default?: T;\n args?: Record<string, any>;\n}\n\nexport interface SdStorage<T = any> {\n get: () => T;\n set: (data: T) => void;\n // Ghi vào storage nhưng KHÔNG emit subject. Dùng cho thay đổi UI-only\n // (vd: column width) để tránh re-trigger các subscriber gây reload data.\n setSilent: (data: T) => void;\n has: () => boolean;\n remove: () => void;\n subject: BehaviorSubject<T>;\n observer: Observable<T>;\n}\n\nexport interface ISdStorageConfiguration {\n key?: (key: string) => string;\n}\n\nexport const SD_STORAGE_CONFIG = new InjectionToken<ISdStorageConfiguration>('storage.configuration');\n","/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Inject, Injectable, Optional } from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { BehaviorSubject } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { ISdStorageConfiguration, SD_STORAGE_CONFIG, SdStorage, SdStorageOption } from './storage.model';\n\ninterface StorageCacheEntry<T> {\n data: T;\n createdOn: Date;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdStorageService {\n // Dùng Map để quản lý bộ nhớ tốt hơn Record\n #memoryCache = new Map<string, StorageCacheEntry<any>>();\n #subjects = new Map<string, BehaviorSubject<any>>();\n\n constructor(\n @Inject(SD_STORAGE_CONFIG)\n @Optional()\n private configuration: ISdStorageConfiguration\n ) {}\n\n create<T = any>(key: string | object, option?: SdStorageOption<T>): SdStorage<T> {\n if (!key) throw new Error('Key is required');\n\n // 1. Xử lý Key\n let hashKey: string;\n if (typeof key === 'string') {\n hashKey = key;\n } else if (typeof key === 'object') {\n hashKey = Utilities.hash(key);\n } else {\n throw new Error('Invalid key type');\n }\n\n if (this.configuration?.key) {\n hashKey = this.configuration.key(hashKey);\n }\n\n // 2. Init Subject: Đọc dữ liệu từ Storage ngay lập tức để Subject có giá trị đúng\n if (!this.#subjects.has(hashKey)) {\n const existingData = this.#internalGet<T>(hashKey, option);\n // Fix Type: as T | undefined\n this.#subjects.set(hashKey, new BehaviorSubject<T | undefined>(existingData ?? option?.default));\n }\n\n const subject = this.#subjects.get(hashKey)!;\n\n // 3. Define Helpers\n const get = () => {\n const val = this.#internalGet<T>(hashKey, option);\n return (val ?? option?.default) as T;\n };\n\n const set = (data: T) => {\n this.#internalSet(hashKey, data, option);\n subject.next(data);\n };\n\n const setSilent = (data: T) => {\n this.#internalSet(hashKey, data, option);\n // Cố tình KHÔNG gọi subject.next — consumer dùng kênh riêng để thông báo\n };\n\n const has = () => {\n return this.#internalGet<T>(hashKey, option) !== undefined;\n };\n\n const remove = () => {\n this.#internalRemove(hashKey, option);\n subject.next(undefined);\n };\n\n // Hàm dọn dẹp bộ nhớ (quan trọng)\n const destroy = () => {\n subject.complete();\n this.#subjects.delete(hashKey);\n this.#memoryCache.delete(hashKey);\n };\n\n return {\n get,\n set,\n setSilent,\n has,\n remove,\n // @ts-ignore: Bổ sung vào interface nếu cần\n destroy,\n subject: subject,\n observer: subject.asObservable().pipe(map(() => get())),\n };\n }\n\n // --- PRIVATE CORE LOGIC ---\n\n /**\n * Helper xác định loại Storage đang dùng\n */\n #getStorage(option?: SdStorageOption<any>): Storage {\n return option?.type === 'session' ? sessionStorage : localStorage;\n }\n\n /**\n * Đọc dữ liệu: Memory -> Storage -> Parse\n */\n #internalGet<T>(key: string, option?: SdStorageOption<T>): T | undefined {\n // 1. Check Memory (Nhanh nhất)\n let entry = this.#memoryCache.get(key);\n\n // 2. Check Storage (Nếu memory chưa có hoặc mới F5)\n if (!entry) {\n const storage = this.#getStorage(option);\n const raw = storage.getItem(key);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n // Convert string date back to Date object nếu cần\n entry = {\n data: parsed.data,\n createdOn: new Date(parsed.createdOn)\n };\n // Sync ngược vào RAM\n this.#memoryCache.set(key, entry);\n } catch (e) {\n console.warn('Storage parse error', e);\n this.#internalRemove(key, option); // Dữ liệu lỗi -> Xóa\n }\n }\n }\n\n if (!entry) return undefined;\n\n return this.#deepClone(entry.data);\n }\n\n /**\n * Ghi dữ liệu: Memory -> Storage\n */\n #internalSet<T>(key: string, data: T, option?: SdStorageOption<T>): void {\n const clonedData = this.#deepClone(data);\n\n const entry: StorageCacheEntry<T> = {\n data: clonedData,\n createdOn: new Date(),\n };\n\n // 1. Lưu Memory\n this.#memoryCache.set(key, entry);\n\n // 2. Lưu Storage\n const storage = this.#getStorage(option);\n try {\n storage.setItem(key, JSON.stringify(entry));\n } catch (e) {\n console.error('Storage quota exceeded', e);\n }\n }\n\n /**\n * Xóa dữ liệu\n */\n #internalRemove(key: string, option?: SdStorageOption<any>): void {\n this.#memoryCache.delete(key);\n const storage = this.#getStorage(option);\n storage.removeItem(key);\n }\n\n /**\n * Deep Clone an toàn\n */\n #deepClone<T>(val: T): T {\n if (val === undefined || val === null) return val;\n return JSON.parse(JSON.stringify(val));\n }\n}","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-services-storage.mjs","sources":["../../../projects/sdcorejs-angular/services/storage/src/storage.model.ts","../../../projects/sdcorejs-angular/services/storage/src/storage.service.ts","../../../projects/sdcorejs-angular/services/storage/sdcorejs-angular-services-storage.ts"],"sourcesContent":["import { InjectionToken } from '@angular/core';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface SdStorageOption<T = any> {\n type?: 'session';\n default?: T;\n args?: Record<string, any>;\n}\n\nexport interface SdStorage<T = any> {\n get: () => T;\n set: (data: T) => void;\n // Ghi vào storage nhưng KHÔNG emit subject. Dùng cho thay đổi UI-only\n // (vd: column width) để tránh re-trigger các subscriber gây reload data.\n setSilent: (data: T) => void;\n has: () => boolean;\n remove: () => void;\n subject: BehaviorSubject<T>;\n observer: Observable<T>;\n}\n\nexport interface ISdStorageConfiguration {\n key?: (key: string) => string;\n}\n\nexport const SD_STORAGE_CONFIG = new InjectionToken<ISdStorageConfiguration>('storage.configuration');\n","import { inject, Injectable } from '@angular/core';\nimport { Utilities } from '@sdcorejs/utils/fns';\nimport { BehaviorSubject } from 'rxjs';\nimport { map } from 'rxjs/operators';\nimport { ISdStorageConfiguration, SD_STORAGE_CONFIG, SdStorage, SdStorageOption } from './storage.model';\n\ninterface StorageCacheEntry<T> {\n data: T;\n createdOn: Date;\n}\n\n@Injectable({\n providedIn: 'root',\n})\nexport class SdStorageService {\n // Dùng Map để quản lý bộ nhớ tốt hơn Record\n #memoryCache = new Map<string, StorageCacheEntry<any>>();\n #subjects = new Map<string, BehaviorSubject<any>>();\n private readonly configuration: ISdStorageConfiguration | null = inject(SD_STORAGE_CONFIG, { optional: true });\n\n create<T = any>(key: string | object, option?: SdStorageOption<T>): SdStorage<T> {\n if (!key) throw new Error('Key is required');\n\n // 1. Xử lý Key\n let hashKey: string;\n if (typeof key === 'string') {\n hashKey = key;\n } else if (typeof key === 'object') {\n hashKey = Utilities.hash(key);\n } else {\n throw new Error('Invalid key type');\n }\n\n if (this.configuration?.key) {\n hashKey = this.configuration.key(hashKey);\n }\n\n // 2. Init Subject: Đọc dữ liệu từ Storage ngay lập tức để Subject có giá trị đúng\n if (!this.#subjects.has(hashKey)) {\n const existingData = this.#internalGet<T>(hashKey, option);\n // Fix Type: as T | undefined\n this.#subjects.set(hashKey, new BehaviorSubject<T | undefined>(existingData ?? option?.default));\n }\n\n const subject = this.#subjects.get(hashKey)!;\n\n // 3. Define Helpers\n const get = () => {\n const val = this.#internalGet<T>(hashKey, option);\n return (val ?? option?.default) as T;\n };\n\n const set = (data: T) => {\n this.#internalSet(hashKey, data, option);\n subject.next(data);\n };\n\n const setSilent = (data: T) => {\n this.#internalSet(hashKey, data, option);\n // Cố tình KHÔNG gọi subject.next — consumer dùng kênh riêng để thông báo\n };\n\n const has = () => {\n return this.#internalGet<T>(hashKey, option) !== undefined;\n };\n\n const remove = () => {\n this.#internalRemove(hashKey, option);\n subject.next(undefined);\n };\n\n // Hàm dọn dẹp bộ nhớ (quan trọng)\n const destroy = () => {\n subject.complete();\n this.#subjects.delete(hashKey);\n this.#memoryCache.delete(hashKey);\n };\n\n return {\n get,\n set,\n setSilent,\n has,\n remove,\n // @ts-expect-error: Bổ sung vào interface nếu cần\n destroy,\n subject: subject,\n observer: subject.asObservable().pipe(map(() => get())),\n };\n }\n\n // --- PRIVATE CORE LOGIC ---\n\n /**\n * Helper xác định loại Storage đang dùng\n */\n #getStorage(option?: SdStorageOption<any>): Storage {\n return option?.type === 'session' ? sessionStorage : localStorage;\n }\n\n /**\n * Đọc dữ liệu: Memory -> Storage -> Parse\n */\n #internalGet<T>(key: string, option?: SdStorageOption<T>): T | undefined {\n // 1. Check Memory (Nhanh nhất)\n let entry = this.#memoryCache.get(key);\n\n // 2. Check Storage (Nếu memory chưa có hoặc mới F5)\n if (!entry) {\n const storage = this.#getStorage(option);\n const raw = storage.getItem(key);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n // Convert string date back to Date object nếu cần\n entry = {\n data: parsed.data,\n createdOn: new Date(parsed.createdOn),\n };\n // Sync ngược vào RAM\n this.#memoryCache.set(key, entry);\n } catch (e) {\n console.warn('Storage parse error', e);\n this.#internalRemove(key, option); // Dữ liệu lỗi -> Xóa\n }\n }\n }\n\n if (!entry) return undefined;\n\n return this.#deepClone(entry.data);\n }\n\n /**\n * Ghi dữ liệu: Memory -> Storage\n */\n #internalSet<T>(key: string, data: T, option?: SdStorageOption<T>): void {\n const clonedData = this.#deepClone(data);\n\n const entry: StorageCacheEntry<T> = {\n data: clonedData,\n createdOn: new Date(),\n };\n\n // 1. Lưu Memory\n this.#memoryCache.set(key, entry);\n\n // 2. Lưu Storage\n const storage = this.#getStorage(option);\n try {\n storage.setItem(key, JSON.stringify(entry));\n } catch (e) {\n console.error('Storage quota exceeded', e);\n }\n }\n\n /**\n * Xóa dữ liệu\n */\n #internalRemove(key: string, option?: SdStorageOption<any>): void {\n this.#memoryCache.delete(key);\n const storage = this.#getStorage(option);\n storage.removeItem(key);\n }\n\n /**\n * Deep Clone an toàn\n */\n #deepClone<T>(val: T): T {\n if (val === undefined || val === null) return val;\n return JSON.parse(JSON.stringify(val));\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sdcorejs-angular-utilities-data-state.mjs","sources":["../../../projects/sdcorejs-angular/utilities/data-state/src/data-state.ts","../../../projects/sdcorejs-angular/utilities/data-state/sdcorejs-angular-utilities-data-state.ts"],"sourcesContent":["/**\n * Serialize a value for the `data-value` attribute consumed by QA automation.\n *\n * null/undefined/empty-string → ''.\n * Date → ISO string.\n * Array / object → JSON.stringify, '' on failure (circular refs).\n * Primitives → String().\n */\nexport function sdSerializeDataValue(value: unknown): string {\n if (value === null || value === undefined || value === '') return '';\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {\n try {
|
|
1
|
+
{"version":3,"file":"sdcorejs-angular-utilities-data-state.mjs","sources":["../../../projects/sdcorejs-angular/utilities/data-state/src/data-state.ts","../../../projects/sdcorejs-angular/utilities/data-state/sdcorejs-angular-utilities-data-state.ts"],"sourcesContent":["/**\n * Serialize a value for the `data-value` attribute consumed by QA automation.\n *\n * null/undefined/empty-string → ''.\n * Date → ISO string.\n * Array / object → JSON.stringify, '' on failure (circular refs).\n * Primitives → String().\n */\nexport function sdSerializeDataValue(value: unknown): string {\n if (value === null || value === undefined || value === '') return '';\n if (value instanceof Date) return value.toISOString();\n if (Array.isArray(value) || (typeof value === 'object' && value !== null)) {\n try {\n return JSON.stringify(value);\n } catch {\n return '';\n }\n }\n return String(value);\n}\n\n/** True when the value should drive `data-empty=\"true\"` on a form control. */\nexport function sdIsEmpty(value: unknown): boolean {\n if (value === null || value === undefined || value === '') return true;\n if (Array.isArray(value)) return value.length === 0;\n return false;\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":"AAAA;;;;;;;AAOG;AACG,SAAU,oBAAoB,CAAC,KAAc,EAAA;IACjD,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,EAAE;IACpE,IAAI,KAAK,YAAY,IAAI;AAAE,QAAA,OAAO,KAAK,CAAC,WAAW,EAAE;AACrD,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC,EAAE;AACzE,QAAA,IAAI;AACF,YAAA,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC;QAC9B;AAAE,QAAA,MAAM;AACN,YAAA,OAAO,EAAE;QACX;IACF;AACA,IAAA,OAAO,MAAM,CAAC,KAAK,CAAC;AACtB;AAEA;AACM,SAAU,SAAS,CAAC,KAAc,EAAA;IACtC,IAAI,KAAK,KAAK,IAAI,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,EAAE;AAAE,QAAA,OAAO,IAAI;AACtE,IAAA,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;AAAE,QAAA,OAAO,KAAK,CAAC,MAAM,KAAK,CAAC;AACnD,IAAA,OAAO,KAAK;AACd;;AC1BA;;AAEG;;;;"}
|