@sd-angular/core 19.0.0-beta.82 → 19.0.0-beta.84
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/fesm2022/sd-angular-core-components-anchor-v2.mjs +9 -9
- package/fesm2022/sd-angular-core-components-anchor.mjs +12 -12
- package/fesm2022/sd-angular-core-components-avatar.mjs +3 -3
- package/fesm2022/sd-angular-core-components-badge.mjs +3 -3
- package/fesm2022/sd-angular-core-components-base.mjs +3 -3
- package/fesm2022/sd-angular-core-components-button.mjs +3 -3
- package/fesm2022/sd-angular-core-components-chart.mjs +12 -12
- package/fesm2022/sd-angular-core-components-code-editor.mjs +3 -3
- package/fesm2022/sd-angular-core-components-document-builder.mjs +6 -6
- package/fesm2022/sd-angular-core-components-editor.mjs +3 -3
- package/fesm2022/sd-angular-core-components-history.mjs +7 -7
- package/fesm2022/sd-angular-core-components-import-excel.mjs +10 -10
- package/fesm2022/sd-angular-core-components-mini-editor.mjs +3 -3
- package/fesm2022/sd-angular-core-components-modal.mjs +3 -3
- package/fesm2022/sd-angular-core-components-preview.mjs +3 -3
- package/fesm2022/sd-angular-core-components-query-builder.mjs +3 -3
- package/fesm2022/sd-angular-core-components-quick-action.mjs +3 -3
- package/fesm2022/sd-angular-core-components-section.mjs +6 -6
- package/fesm2022/sd-angular-core-components-side-drawer.mjs +3 -3
- package/fesm2022/sd-angular-core-components-tab-router.mjs +104 -51
- package/fesm2022/sd-angular-core-components-tab-router.mjs.map +1 -1
- package/fesm2022/sd-angular-core-components-table.mjs +96 -96
- package/fesm2022/sd-angular-core-components-upload-file.mjs +15 -15
- package/fesm2022/sd-angular-core-components-view.mjs +3 -3
- package/fesm2022/sd-angular-core-components-workflow.mjs +198 -198
- package/fesm2022/sd-angular-core-directives.mjs +21 -21
- package/fesm2022/sd-angular-core-forms-autocomplete.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-checkbox.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-chip-calendar.mjs +6 -6
- package/fesm2022/sd-angular-core-forms-chip.mjs +6 -6
- package/fesm2022/sd-angular-core-forms-date-range.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-date.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-datetime.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-directives.mjs +12 -12
- package/fesm2022/sd-angular-core-forms-input-number.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-input.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-label.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-radio.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-select.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-switch.mjs +3 -3
- package/fesm2022/sd-angular-core-forms-textarea.mjs +3 -3
- package/fesm2022/sd-angular-core-forms.mjs +4 -4
- package/fesm2022/sd-angular-core-handlers.mjs +3 -3
- package/fesm2022/sd-angular-core-interceptors.mjs +3 -3
- package/fesm2022/sd-angular-core-modules-auth.mjs +9 -9
- package/fesm2022/sd-angular-core-modules-authom.mjs +7 -7
- package/fesm2022/sd-angular-core-modules-keycloak.mjs +7 -7
- package/fesm2022/sd-angular-core-modules-layout.mjs +73 -73
- package/fesm2022/sd-angular-core-modules-permission.mjs +9 -9
- package/fesm2022/sd-angular-core-pipes.mjs +14 -14
- package/fesm2022/sd-angular-core-services-api.mjs +10 -10
- package/fesm2022/sd-angular-core-services-cache.mjs +3 -3
- package/fesm2022/sd-angular-core-services-confirm.mjs +6 -6
- package/fesm2022/sd-angular-core-services-docx.mjs +198 -71
- package/fesm2022/sd-angular-core-services-docx.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-excel.mjs +3 -3
- package/fesm2022/sd-angular-core-services-firebase.mjs +3 -3
- package/fesm2022/sd-angular-core-services-license.mjs +3 -3
- package/fesm2022/sd-angular-core-services-loading.mjs +3 -3
- package/fesm2022/sd-angular-core-services-notify.mjs +9 -9
- package/fesm2022/sd-angular-core-services-storage.mjs +3 -3
- package/package.json +57 -57
- package/sd-angular-core-19.0.0-beta.84.tgz +0 -0
- package/services/docx/src/lib/docx.service.d.ts +4 -2
- package/services/docx/src/lib/pandoc-core.d.ts +11 -0
- package/sd-angular-core-19.0.0-beta.82.tgz +0 -0
|
@@ -60,10 +60,10 @@ class SdQueryBuilder {
|
|
|
60
60
|
removeItem(parentGroup, index) {
|
|
61
61
|
parentGroup.rules.splice(index, 1);
|
|
62
62
|
}
|
|
63
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
64
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.
|
|
63
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBuilder, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
64
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: SdQueryBuilder, isStandalone: true, selector: "sd-query-builder", inputs: { group: "group" }, outputs: { groupChange: "groupChange" }, host: { listeners: { "document:click": "closeAll()" } }, ngImport: i0, template: "<div class=\"qb-container\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { $implicit: group, isRoot: true }\"></ng-container>\r\n</div>\r\n\r\n<ng-template #groupTemplate let-group let-isRoot=\"isRoot\" let-parent=\"parent\" let-index=\"index\">\r\n <div class=\"qb-group\">\r\n \r\n <div class=\"qb-header\" [class.is-open]=\"group.isOpen\">\r\n <div class=\"qb-condition-switch\">\r\n <button type=\"button\" \r\n [class.active]=\"group.condition === 'AND'\" \r\n (click)=\"toggleCondition(group, 'AND')\">AND</button>\r\n <button type=\"button\" \r\n [class.active]=\"group.condition === 'OR'\" \r\n (click)=\"toggleCondition(group, 'OR')\">OR</button>\r\n </div>\r\n\r\n <div class=\"qb-actions\">\r\n <button type=\"button\" class=\"btn-icon btn-add\" (click)=\"toggleDropdown(group, $event)\">+</button>\r\n \r\n <div class=\"qb-dropdown\" *ngIf=\"group.isOpen\" (click)=\"$event.stopPropagation()\" aria-hidden=\"true\">\r\n <button (click)=\"addGroup(group)\">+ Add Group</button>\r\n <button (click)=\"addRule(group)\">+ Add Condition</button>\r\n </div>\r\n\r\n <button *ngIf=\"!isRoot\" type=\"button\" class=\"btn-icon btn-remove\" (click)=\"removeItem(parent, index)\">\u2715</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"qb-body\">\r\n <div class=\"qb-list\">\r\n <div *ngFor=\"let item of group.rules; let i = index\" \r\n class=\"qb-treenode\"\r\n [class.is-group]=\"isGroup(item)\"\r\n [class.is-rule]=\"!isGroup(item)\">\r\n \r\n <ng-container *ngIf=\"!isGroup(item)\">\r\n <div class=\"qb-rule-card\">\r\n <div class=\"form-row\">\r\n <input class=\"form-input field-input\" [(ngModel)]=\"item.field\" placeholder=\"Select Field\">\r\n <ng-container *ngIf=\"item.field\">\r\n <div class=\"divider\"></div>\r\n <select class=\"form-input\" [(ngModel)]=\"item.operator\">\r\n <option value=\"Equal\">Equal</option>\r\n <option value=\"Not Equal\">Not Equal</option>\r\n </select>\r\n <div class=\"divider\"></div>\r\n <input class=\"form-input\" [(ngModel)]=\"item.value\" placeholder=\"Value\">\r\n </ng-container>\r\n <button type=\"button\" class=\"btn-icon btn-remove-rule\" (click)=\"removeItem(group, i)\">\u2715</button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-container *ngIf=\"isGroup(item)\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { $implicit: item, isRoot: false, parent: group, index: i }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</ng-template>", styles: [":host{display:block;font-family:Segoe UI,Roboto,sans-serif;font-size:14px;color:#333}.qb-container{padding:20px;background:#fff}.qb-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;position:relative;z-index:1}.qb-header.is-open{z-index:1000}.qb-condition-switch{display:inline-flex;border-radius:4px;overflow:hidden;background:#fff;border:1px solid #e0e0e0;height:32px}.qb-condition-switch button{border:none;background:transparent;padding:0 16px;font-weight:600;font-size:13px;cursor:pointer;color:#666;height:100%}.qb-condition-switch button.active{background:#6246a8;color:#fff}.qb-condition-switch button:hover:not(.active){background:#f5f5f5}.qb-actions{position:relative;display:flex;gap:4px}.qb-body{padding-left:28px}.qb-list{position:relative}.qb-treenode{position:relative;padding-left:24px;margin-bottom:12px}.qb-treenode:before{content:\"\";position:absolute;left:0;top:-12px;bottom:0;width:1px;border-left:1px dashed #c4c4c4}.qb-treenode:after{content:\"\";position:absolute;left:0;width:24px;height:1px;border-top:1px dashed #c4c4c4}.qb-treenode:first-child:before{top:-14px;height:calc(100% + 14px)}.qb-treenode.is-group:after{top:16px}.qb-treenode.is-group:last-child:before{height:auto;bottom:auto;top:-14px;height:30px}.qb-treenode.is-rule:after{top:26px}.qb-treenode.is-rule:last-child:before{height:auto;bottom:auto;top:-14px;height:40px}.qb-rule-card{background:#fafafa;border:1px solid #e0e0e0;border-radius:4px;padding:10px 12px;transition:.2s}.qb-rule-card:hover{border-color:#ccc}.form-row{display:flex;align-items:center;gap:12px;height:32px}.form-input{border:none;background:transparent;font-size:14px;color:#333;outline:none;height:100%}.form-input::placeholder{color:#aaa}.divider{width:1px;height:20px;background:#ddd}.btn-icon{background:transparent;border:none;cursor:pointer;font-size:18px;color:#666;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px}.btn-icon:hover{background:#f0f0f0;color:#6246a8}.btn-remove{color:#999}.btn-remove:hover{color:#d32f2f;background:#ffebee}.btn-remove-rule{margin-left:auto;color:#999}.btn-remove-rule:hover{color:#d32f2f;background:none}.qb-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 12px #00000026;border-radius:4px;min-width:160px;z-index:100}.qb-dropdown button{display:block;width:100%;text-align:left;background:none;border:none;padding:10px 14px;cursor:pointer;font-size:13px;color:#333}.qb-dropdown button:hover{background:#f3f0fa;color:#6246a8}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i1.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i2.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i2.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i2.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i2.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i2.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }] });
|
|
65
65
|
}
|
|
66
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
66
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQueryBuilder, decorators: [{
|
|
67
67
|
type: Component,
|
|
68
68
|
args: [{ selector: 'sd-query-builder', standalone: true, imports: [CommonModule, FormsModule], template: "<div class=\"qb-container\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { $implicit: group, isRoot: true }\"></ng-container>\r\n</div>\r\n\r\n<ng-template #groupTemplate let-group let-isRoot=\"isRoot\" let-parent=\"parent\" let-index=\"index\">\r\n <div class=\"qb-group\">\r\n \r\n <div class=\"qb-header\" [class.is-open]=\"group.isOpen\">\r\n <div class=\"qb-condition-switch\">\r\n <button type=\"button\" \r\n [class.active]=\"group.condition === 'AND'\" \r\n (click)=\"toggleCondition(group, 'AND')\">AND</button>\r\n <button type=\"button\" \r\n [class.active]=\"group.condition === 'OR'\" \r\n (click)=\"toggleCondition(group, 'OR')\">OR</button>\r\n </div>\r\n\r\n <div class=\"qb-actions\">\r\n <button type=\"button\" class=\"btn-icon btn-add\" (click)=\"toggleDropdown(group, $event)\">+</button>\r\n \r\n <div class=\"qb-dropdown\" *ngIf=\"group.isOpen\" (click)=\"$event.stopPropagation()\" aria-hidden=\"true\">\r\n <button (click)=\"addGroup(group)\">+ Add Group</button>\r\n <button (click)=\"addRule(group)\">+ Add Condition</button>\r\n </div>\r\n\r\n <button *ngIf=\"!isRoot\" type=\"button\" class=\"btn-icon btn-remove\" (click)=\"removeItem(parent, index)\">\u2715</button>\r\n </div>\r\n </div>\r\n\r\n <div class=\"qb-body\">\r\n <div class=\"qb-list\">\r\n <div *ngFor=\"let item of group.rules; let i = index\" \r\n class=\"qb-treenode\"\r\n [class.is-group]=\"isGroup(item)\"\r\n [class.is-rule]=\"!isGroup(item)\">\r\n \r\n <ng-container *ngIf=\"!isGroup(item)\">\r\n <div class=\"qb-rule-card\">\r\n <div class=\"form-row\">\r\n <input class=\"form-input field-input\" [(ngModel)]=\"item.field\" placeholder=\"Select Field\">\r\n <ng-container *ngIf=\"item.field\">\r\n <div class=\"divider\"></div>\r\n <select class=\"form-input\" [(ngModel)]=\"item.operator\">\r\n <option value=\"Equal\">Equal</option>\r\n <option value=\"Not Equal\">Not Equal</option>\r\n </select>\r\n <div class=\"divider\"></div>\r\n <input class=\"form-input\" [(ngModel)]=\"item.value\" placeholder=\"Value\">\r\n </ng-container>\r\n <button type=\"button\" class=\"btn-icon btn-remove-rule\" (click)=\"removeItem(group, i)\">\u2715</button>\r\n </div>\r\n </div>\r\n </ng-container>\r\n <ng-container *ngIf=\"isGroup(item)\">\r\n <ng-container *ngTemplateOutlet=\"groupTemplate; context: { $implicit: item, isRoot: false, parent: group, index: i }\"></ng-container>\r\n </ng-container>\r\n </div>\r\n </div>\r\n </div>\r\n\r\n </div>\r\n</ng-template>", styles: [":host{display:block;font-family:Segoe UI,Roboto,sans-serif;font-size:14px;color:#333}.qb-container{padding:20px;background:#fff}.qb-header{display:flex;align-items:center;gap:8px;margin-bottom:12px;position:relative;z-index:1}.qb-header.is-open{z-index:1000}.qb-condition-switch{display:inline-flex;border-radius:4px;overflow:hidden;background:#fff;border:1px solid #e0e0e0;height:32px}.qb-condition-switch button{border:none;background:transparent;padding:0 16px;font-weight:600;font-size:13px;cursor:pointer;color:#666;height:100%}.qb-condition-switch button.active{background:#6246a8;color:#fff}.qb-condition-switch button:hover:not(.active){background:#f5f5f5}.qb-actions{position:relative;display:flex;gap:4px}.qb-body{padding-left:28px}.qb-list{position:relative}.qb-treenode{position:relative;padding-left:24px;margin-bottom:12px}.qb-treenode:before{content:\"\";position:absolute;left:0;top:-12px;bottom:0;width:1px;border-left:1px dashed #c4c4c4}.qb-treenode:after{content:\"\";position:absolute;left:0;width:24px;height:1px;border-top:1px dashed #c4c4c4}.qb-treenode:first-child:before{top:-14px;height:calc(100% + 14px)}.qb-treenode.is-group:after{top:16px}.qb-treenode.is-group:last-child:before{height:auto;bottom:auto;top:-14px;height:30px}.qb-treenode.is-rule:after{top:26px}.qb-treenode.is-rule:last-child:before{height:auto;bottom:auto;top:-14px;height:40px}.qb-rule-card{background:#fafafa;border:1px solid #e0e0e0;border-radius:4px;padding:10px 12px;transition:.2s}.qb-rule-card:hover{border-color:#ccc}.form-row{display:flex;align-items:center;gap:12px;height:32px}.form-input{border:none;background:transparent;font-size:14px;color:#333;outline:none;height:100%}.form-input::placeholder{color:#aaa}.divider{width:1px;height:20px;background:#ddd}.btn-icon{background:transparent;border:none;cursor:pointer;font-size:18px;color:#666;width:32px;height:32px;display:flex;align-items:center;justify-content:center;border-radius:4px}.btn-icon:hover{background:#f0f0f0;color:#6246a8}.btn-remove{color:#999}.btn-remove:hover{color:#d32f2f;background:#ffebee}.btn-remove-rule{margin-left:auto;color:#999}.btn-remove-rule:hover{color:#d32f2f;background:none}.qb-dropdown{position:absolute;top:100%;left:0;margin-top:4px;background:#fff;border:1px solid #e0e0e0;box-shadow:0 4px 12px #00000026;border-radius:4px;min-width:160px;z-index:100}.qb-dropdown button{display:block;width:100%;text-align:left;background:none;border:none;padding:10px 14px;cursor:pointer;font-size:13px;color:#333}.qb-dropdown button:hover{background:#f3f0fa;color:#6246a8}\n"] }]
|
|
69
69
|
}], propDecorators: { group: [{
|
|
@@ -14,10 +14,10 @@ class SdQuickAction {
|
|
|
14
14
|
close = () => {
|
|
15
15
|
this.isOpened = false;
|
|
16
16
|
};
|
|
17
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
18
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.
|
|
17
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQuickAction, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
18
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: SdQuickAction, isStandalone: true, selector: "sd-quick-action", inputs: { _isOpened: ["isOpened", "_isOpened"] }, ngImport: i0, template: "<div class=\"c-quick-action d-flex align-items-center gap-16\" [class.active]=\"isOpened\">\r\n <div class=\"flex-1 T14R\">\r\n <ng-content select=\"[sdMessage]\"></ng-content>\r\n </div>\r\n <ng-content select=\"[sdAction]\"></ng-content>\r\n</div>\r\n", styles: [".c-quick-action{box-shadow:0 .4px .8px #0000001a,0 3px 6px #0003;border-radius:4px;position:fixed;min-width:320px;width:max-content;background-color:#fff;bottom:90px;left:0;right:0;margin:auto;visibility:hidden;opacity:0;pointer-events:none;transition:all .2s ease-in-out;transform:translate3d(0,100%,0);z-index:99}.c-quick-action.active{transform:translateZ(0);opacity:1;visibility:visible;pointer-events:all}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }] });
|
|
19
19
|
}
|
|
20
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
20
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdQuickAction, decorators: [{
|
|
21
21
|
type: Component,
|
|
22
22
|
args: [{ selector: 'sd-quick-action', standalone: true, imports: [CommonModule], template: "<div class=\"c-quick-action d-flex align-items-center gap-16\" [class.active]=\"isOpened\">\r\n <div class=\"flex-1 T14R\">\r\n <ng-content select=\"[sdMessage]\"></ng-content>\r\n </div>\r\n <ng-content select=\"[sdAction]\"></ng-content>\r\n</div>\r\n", styles: [".c-quick-action{box-shadow:0 .4px .8px #0000001a,0 3px 6px #0003;border-radius:4px;position:fixed;min-width:320px;width:max-content;background-color:#fff;bottom:90px;left:0;right:0;margin:auto;visibility:hidden;opacity:0;pointer-events:none;transition:all .2s ease-in-out;transform:translate3d(0,100%,0);z-index:99}.c-quick-action.active{transform:translateZ(0);opacity:1;visibility:visible;pointer-events:all}\n"] }]
|
|
23
23
|
}], ctorParameters: () => [], propDecorators: { _isOpened: [{
|
|
@@ -33,10 +33,10 @@ class SdSection extends SdBaseSecureComponent {
|
|
|
33
33
|
}
|
|
34
34
|
}
|
|
35
35
|
};
|
|
36
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
37
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.
|
|
36
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSection, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
37
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdSection, isStandalone: true, selector: "sd-section", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, subTitle: { classPropertyName: "subTitle", publicName: "subTitle", isSignal: true, isRequired: false, transformFunction: null }, icon: { classPropertyName: "icon", publicName: "icon", isSignal: true, isRequired: false, transformFunction: null }, iconColor: { classPropertyName: "iconColor", publicName: "iconColor", isSignal: true, isRequired: false, transformFunction: null }, collapsed: { classPropertyName: "collapsed", publicName: "collapsed", isSignal: true, isRequired: false, transformFunction: null }, collapsable: { classPropertyName: "collapsable", publicName: "collapsable", isSignal: true, isRequired: false, transformFunction: null }, hideHeader: { classPropertyName: "hideHeader", publicName: "hideHeader", isSignal: true, isRequired: false, transformFunction: null }, noPaddingBody: { classPropertyName: "noPaddingBody", publicName: "noPaddingBody", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { collapsed: "collapsedChange" }, usesInheritance: true, ngImport: i0, template: "@let _hideHeader = hideHeader();\r\n@let _collapsable = collapsable();\r\n@let _collapsed = collapsed();\r\n@let _title = title();\r\n@let _subTitle = subTitle();\r\n@let _iconColor = iconColor();\r\n<div class=\"rounded-8 bg-white\" [class.c-shadow-section]=\"!_collapsed\">\r\n @if (!_hideHeader) {\r\n <div\r\n class=\"d-flex align-items-center justify-content-between w-full px-16 py-8 cursor-pointer\"\r\n (click)=\"toggleCollapse()\"\r\n aria-hidden=\"true\">\r\n <div class=\"d-flex align-items-center\">\r\n <ng-content select=\"[sdHeaderLeft]\">\r\n @if (icon()) {\r\n <span\r\n class=\"mr-8 material-icons-outlined\"\r\n [class.text-primary]=\"_iconColor === 'primary'\"\r\n [class.text-secondary]=\"_iconColor === 'secondary'\"\r\n [class.text-error]=\"_iconColor === 'error'\"\r\n [class.text-warning]=\"_iconColor === 'warning'\"\r\n [class.text-success]=\"_iconColor === 'success'\">\r\n {{ icon() }}\r\n </span>\r\n }\r\n <div>\r\n @if (_title) {\r\n <div class=\"T16M\">{{ _title }}</div>\r\n }\r\n @if (_subTitle) {\r\n <div class=\"T12R text-secondary\">{{ _subTitle }}</div>\r\n }\r\n </div>\r\n </ng-content>\r\n </div>\r\n <div class=\"d-flex\">\r\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\r\n @if (_collapsable) {\r\n <div class=\"d-flex align-items-center\">\r\n @if (_collapsed) {\r\n <mat-icon>expand_more</mat-icon>\r\n } @else {\r\n <mat-icon>expand_less</mat-icon>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n @if (!_collapsed || _hideHeader) {\r\n <!-- N\u1EBFu header b\u1ECB \u1EA9n th\u00EC kh\u00F4ng c\u1EA7n border top -->\r\n <div [class.p-16]=\"!noPaddingBody()\" [class.c-no-padding-body]=\"noPaddingBody()\" [style.border-top]=\"_hideHeader ? 'none' : '1px solid #e6e6e6'\">\r\n <ng-content></ng-content>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{width:100%;display:block}.c-no-padding-body{padding:0!important}.c-shadow-section{box-shadow:-1px 0 #f2f2f2 inset,1px 0 #f2f2f2 inset,0 1px #f2f2f2 inset,0 -1px #f2f2f2 inset,0 1px 2px #2f31361a}.cursor-pointer{cursor:pointer}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i1.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }] });
|
|
38
38
|
}
|
|
39
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
39
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSection, decorators: [{
|
|
40
40
|
type: Component,
|
|
41
41
|
args: [{ selector: 'sd-section', imports: [MatIconModule], template: "@let _hideHeader = hideHeader();\r\n@let _collapsable = collapsable();\r\n@let _collapsed = collapsed();\r\n@let _title = title();\r\n@let _subTitle = subTitle();\r\n@let _iconColor = iconColor();\r\n<div class=\"rounded-8 bg-white\" [class.c-shadow-section]=\"!_collapsed\">\r\n @if (!_hideHeader) {\r\n <div\r\n class=\"d-flex align-items-center justify-content-between w-full px-16 py-8 cursor-pointer\"\r\n (click)=\"toggleCollapse()\"\r\n aria-hidden=\"true\">\r\n <div class=\"d-flex align-items-center\">\r\n <ng-content select=\"[sdHeaderLeft]\">\r\n @if (icon()) {\r\n <span\r\n class=\"mr-8 material-icons-outlined\"\r\n [class.text-primary]=\"_iconColor === 'primary'\"\r\n [class.text-secondary]=\"_iconColor === 'secondary'\"\r\n [class.text-error]=\"_iconColor === 'error'\"\r\n [class.text-warning]=\"_iconColor === 'warning'\"\r\n [class.text-success]=\"_iconColor === 'success'\">\r\n {{ icon() }}\r\n </span>\r\n }\r\n <div>\r\n @if (_title) {\r\n <div class=\"T16M\">{{ _title }}</div>\r\n }\r\n @if (_subTitle) {\r\n <div class=\"T12R text-secondary\">{{ _subTitle }}</div>\r\n }\r\n </div>\r\n </ng-content>\r\n </div>\r\n <div class=\"d-flex\">\r\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\r\n @if (_collapsable) {\r\n <div class=\"d-flex align-items-center\">\r\n @if (_collapsed) {\r\n <mat-icon>expand_more</mat-icon>\r\n } @else {\r\n <mat-icon>expand_less</mat-icon>\r\n }\r\n </div>\r\n }\r\n </div>\r\n </div>\r\n }\r\n @if (!_collapsed || _hideHeader) {\r\n <!-- N\u1EBFu header b\u1ECB \u1EA9n th\u00EC kh\u00F4ng c\u1EA7n border top -->\r\n <div [class.p-16]=\"!noPaddingBody()\" [class.c-no-padding-body]=\"noPaddingBody()\" [style.border-top]=\"_hideHeader ? 'none' : '1px solid #e6e6e6'\">\r\n <ng-content></ng-content>\r\n </div>\r\n }\r\n</div>\r\n", styles: [":host{width:100%;display:block}.c-no-padding-body{padding:0!important}.c-shadow-section{box-shadow:-1px 0 #f2f2f2 inset,1px 0 #f2f2f2 inset,0 1px #f2f2f2 inset,0 -1px #f2f2f2 inset,0 1px 2px #2f31361a}.cursor-pointer{cursor:pointer}\n"] }]
|
|
42
42
|
}], ctorParameters: () => [] });
|
|
@@ -48,10 +48,10 @@ class SdSectionItem {
|
|
|
48
48
|
return val || '150px';
|
|
49
49
|
},
|
|
50
50
|
});
|
|
51
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
52
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.
|
|
51
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSectionItem, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
52
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "19.2.21", type: SdSectionItem, isStandalone: true, selector: "sd-section-item", inputs: { label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: true, transformFunction: null }, labelWidth: { classPropertyName: "labelWidth", publicName: "labelWidth", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<div class=\"c-item\">\r\n <div class=\"T14R text-black400\" [style.width]=\"labelWidth()\">{{ label() }}</div>\r\n <div style=\"flex: 1;\"><ng-content></ng-content></div>\r\n</div>", styles: [":host{width:100%;display:block}.c-item{display:flex;flex-direction:row;align-items:center;column-gap:8px;padding:8px 16px;border-bottom:1px solid #f2f2f2}\n"] });
|
|
53
53
|
}
|
|
54
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
54
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSectionItem, decorators: [{
|
|
55
55
|
type: Component,
|
|
56
56
|
args: [{ selector: 'sd-section-item', standalone: true, template: "<div class=\"c-item\">\r\n <div class=\"T14R text-black400\" [style.width]=\"labelWidth()\">{{ label() }}</div>\r\n <div style=\"flex: 1;\"><ng-content></ng-content></div>\r\n</div>", styles: [":host{width:100%;display:block}.c-item{display:flex;flex-direction:row;align-items:center;column-gap:8px;padding:8px 16px;border-bottom:1px solid #f2f2f2}\n"] }]
|
|
57
57
|
}] });
|
|
@@ -107,10 +107,10 @@ class SdSideDrawer extends SdBaseSecureComponent {
|
|
|
107
107
|
const mouseLeave$ = fromEvent(element, 'mouseleave').pipe(map(() => false));
|
|
108
108
|
this.isHovered$ = merge(mouseEnter$, mouseLeave$).pipe(startWith(false), distinctUntilChanged(), takeUntil(this.#destroy$));
|
|
109
109
|
}
|
|
110
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
111
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.
|
|
110
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSideDrawer, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
111
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdSideDrawer, isStandalone: true, selector: "sd-side-drawer", inputs: { title: { classPropertyName: "title", publicName: "title", isSignal: true, isRequired: false, transformFunction: null }, width: { classPropertyName: "width", publicName: "width", isSignal: true, isRequired: false, transformFunction: null }, hideClose: { classPropertyName: "hideClose", publicName: "hideClose", isSignal: true, isRequired: false, transformFunction: null }, disableBackdropClose: { classPropertyName: "disableBackdropClose", publicName: "disableBackdropClose", isSignal: true, isRequired: false, transformFunction: null }, drawerClass: { classPropertyName: "drawerClass", publicName: "drawerClass", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { sdClosed: "sdClosed" }, viewQueries: [{ propertyName: "portal", first: true, predicate: CdkPortal, descendants: true, isSignal: true }], usesInheritance: true, ngImport: i0, template: "<ng-template cdkPortal>\r\n <div\r\n [id]=\"id\"\r\n class=\"sd-side-drawer\"\r\n [ngStyle]=\"{ width: width() }\"\r\n [ngClass]=\"drawerClass()\"\r\n [class.sd-side-drawer-active]=\"isOpened\"\r\n #drawerContainer>\r\n @if (isOpened) {\r\n <ng-container>\r\n <div class=\"sd-side-drawer-header\">\r\n <div class=\"sd-side-drawer-title\">\r\n <ng-content select=\"[sdHeaderLeft]\">\r\n {{ title() }}\r\n </ng-content>\r\n </div>\r\n <div class=\"sd-side-drawer-header-actions\">\r\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\r\n @if (!hideClose()) {\r\n <button type=\"button\" class=\"sd-side-drawer-close-btn\" (click)=\"close()\" aria-label=\"Close\">\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n width=\"24\"\r\n height=\"24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n fill=\"none\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n @if (isOpened) {\r\n <div class=\"sd-side-drawer-body\">\r\n <div class=\"sd-side-drawer-content\">\r\n <ng-content></ng-content>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"sd-side-drawer-footer\">\r\n <ng-content select=\"[sdFooter]\"></ng-content>\r\n </div>\r\n </ng-container>\r\n }\r\n </div>\r\n @if (isOpened) {\r\n <div\r\n aria-hidden=\"true\"\r\n class=\"sd-side-drawer-backdrop\"\r\n (click)=\"disableBackdropClose() ? null : close()\"\r\n (wheel)=\"preventScroll($event)\"\r\n (touchmove)=\"preventScroll($event)\"></div>\r\n }\r\n</ng-template>\r\n", styles: [".sd-side-drawer{position:fixed;right:0;top:0;bottom:0;background-color:#fff;z-index:999;display:flex;flex-direction:column;opacity:0;visibility:hidden;transform:translate3d(100%,0,0);transition:all .3s ease-in-out;pointer-events:none;box-shadow:0 .4px .8px #0000001a,0 3px 6px #0003}.sd-side-drawer-active{opacity:1;visibility:visible;pointer-events:all;transform:translateZ(0)}.sd-side-drawer-header{min-height:64px;padding:12px 16px;display:flex;align-items:center;justify-content:space-between}.sd-side-drawer-header-actions{display:flex;align-items:center;gap:8px}.sd-side-drawer-title{font-size:20px;line-height:28px;font-weight:500;flex:1;margin-right:16px}.sd-side-drawer-close-btn{background:transparent;border:none;padding:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--sd-secondary-color, #666);border-radius:50%;transition:background-color .2s,color .2s}.sd-side-drawer-close-btn:hover{background-color:#0000000a;color:var(--sd-primary-color, #333)}.sd-side-drawer-close-btn:focus{outline:none;background-color:#00000014}.sd-side-drawer-body{flex:1;position:relative}.sd-side-drawer-content{position:absolute;padding:0 16px 16px;inset:0;overflow:auto}.sd-side-drawer-footer{border-top:1px solid #dde0e5;padding:8px}.sd-side-drawer-backdrop{background-color:#0006;border:0;outline:none!important;position:fixed;inset:0;z-index:99;width:100%;height:100%;transition:all .3s ease-in-out}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "ngmodule", type: PortalModule }, { kind: "directive", type: i2.CdkPortal, selector: "[cdkPortal]", exportAs: ["cdkPortal"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
112
112
|
}
|
|
113
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
113
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdSideDrawer, decorators: [{
|
|
114
114
|
type: Component,
|
|
115
115
|
args: [{ selector: 'sd-side-drawer', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, PortalModule], template: "<ng-template cdkPortal>\r\n <div\r\n [id]=\"id\"\r\n class=\"sd-side-drawer\"\r\n [ngStyle]=\"{ width: width() }\"\r\n [ngClass]=\"drawerClass()\"\r\n [class.sd-side-drawer-active]=\"isOpened\"\r\n #drawerContainer>\r\n @if (isOpened) {\r\n <ng-container>\r\n <div class=\"sd-side-drawer-header\">\r\n <div class=\"sd-side-drawer-title\">\r\n <ng-content select=\"[sdHeaderLeft]\">\r\n {{ title() }}\r\n </ng-content>\r\n </div>\r\n <div class=\"sd-side-drawer-header-actions\">\r\n <ng-content select=\"[sdHeaderRight]\"></ng-content>\r\n @if (!hideClose()) {\r\n <button type=\"button\" class=\"sd-side-drawer-close-btn\" (click)=\"close()\" aria-label=\"Close\">\r\n <svg\r\n viewBox=\"0 0 24 24\"\r\n width=\"24\"\r\n height=\"24\"\r\n stroke=\"currentColor\"\r\n stroke-width=\"2\"\r\n fill=\"none\"\r\n stroke-linecap=\"round\"\r\n stroke-linejoin=\"round\">\r\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\"></line>\r\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\"></line>\r\n </svg>\r\n </button>\r\n }\r\n </div>\r\n </div>\r\n @if (isOpened) {\r\n <div class=\"sd-side-drawer-body\">\r\n <div class=\"sd-side-drawer-content\">\r\n <ng-content></ng-content>\r\n </div>\r\n </div>\r\n }\r\n <div class=\"sd-side-drawer-footer\">\r\n <ng-content select=\"[sdFooter]\"></ng-content>\r\n </div>\r\n </ng-container>\r\n }\r\n </div>\r\n @if (isOpened) {\r\n <div\r\n aria-hidden=\"true\"\r\n class=\"sd-side-drawer-backdrop\"\r\n (click)=\"disableBackdropClose() ? null : close()\"\r\n (wheel)=\"preventScroll($event)\"\r\n (touchmove)=\"preventScroll($event)\"></div>\r\n }\r\n</ng-template>\r\n", styles: [".sd-side-drawer{position:fixed;right:0;top:0;bottom:0;background-color:#fff;z-index:999;display:flex;flex-direction:column;opacity:0;visibility:hidden;transform:translate3d(100%,0,0);transition:all .3s ease-in-out;pointer-events:none;box-shadow:0 .4px .8px #0000001a,0 3px 6px #0003}.sd-side-drawer-active{opacity:1;visibility:visible;pointer-events:all;transform:translateZ(0)}.sd-side-drawer-header{min-height:64px;padding:12px 16px;display:flex;align-items:center;justify-content:space-between}.sd-side-drawer-header-actions{display:flex;align-items:center;gap:8px}.sd-side-drawer-title{font-size:20px;line-height:28px;font-weight:500;flex:1;margin-right:16px}.sd-side-drawer-close-btn{background:transparent;border:none;padding:8px;cursor:pointer;display:flex;align-items:center;justify-content:center;color:var(--sd-secondary-color, #666);border-radius:50%;transition:background-color .2s,color .2s}.sd-side-drawer-close-btn:hover{background-color:#0000000a;color:var(--sd-primary-color, #333)}.sd-side-drawer-close-btn:focus{outline:none;background-color:#00000014}.sd-side-drawer-body{flex:1;position:relative}.sd-side-drawer-content{position:absolute;padding:0 16px 16px;inset:0;overflow:auto}.sd-side-drawer-footer{border-top:1px solid #dde0e5;padding:8px}.sd-side-drawer-backdrop{background-color:#0006;border:0;outline:none!important;position:fixed;inset:0;z-index:99;width:100%;height:100%;transition:all .3s ease-in-out}\n"] }]
|
|
116
116
|
}], ctorParameters: () => [] });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
2
|
import { Injectable, Pipe, Input, ChangeDetectionStrategy, Component, HostListener, ViewChild, signal, inject, Injector, NgModuleFactory, createNgModule } from '@angular/core';
|
|
3
3
|
import * as i2 from '@angular/router';
|
|
4
|
-
import { Router, ActivatedRoute, RouterEvent, NavigationEnd } from '@angular/router';
|
|
4
|
+
import { Router, ActivatedRoute, RouterEvent, RoutesRecognized, NavigationEnd } from '@angular/router';
|
|
5
5
|
import * as i1 from '@angular/common';
|
|
6
6
|
import { CommonModule } from '@angular/common';
|
|
7
7
|
import * as i3 from '@angular/material/icon';
|
|
@@ -104,10 +104,10 @@ class SdTabRouterService {
|
|
|
104
104
|
};
|
|
105
105
|
// Gọi hàm này để thực hiện update tab
|
|
106
106
|
updateTab = (tab) => { };
|
|
107
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
108
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.
|
|
107
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
108
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterService, providedIn: 'root' });
|
|
109
109
|
}
|
|
110
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
110
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterService, decorators: [{
|
|
111
111
|
type: Injectable,
|
|
112
112
|
args: [{
|
|
113
113
|
providedIn: 'root',
|
|
@@ -119,10 +119,10 @@ class SdTabDecoratorService {
|
|
|
119
119
|
constructor(tabRouterService) {
|
|
120
120
|
SdTabDecoratorService.tabRouterService.next(tabRouterService);
|
|
121
121
|
}
|
|
122
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
123
|
-
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.
|
|
122
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabDecoratorService, deps: [{ token: SdTabRouterService }], target: i0.ɵɵFactoryTarget.Injectable });
|
|
123
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabDecoratorService, providedIn: 'root' });
|
|
124
124
|
}
|
|
125
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
125
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabDecoratorService, decorators: [{
|
|
126
126
|
type: Injectable,
|
|
127
127
|
args: [{
|
|
128
128
|
providedIn: 'root',
|
|
@@ -154,10 +154,10 @@ class SdTabInfoPipe {
|
|
|
154
154
|
icon: undefined,
|
|
155
155
|
};
|
|
156
156
|
}
|
|
157
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
158
|
-
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.
|
|
157
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabInfoPipe, deps: [{ token: SdTabRouterService }], target: i0.ɵɵFactoryTarget.Pipe });
|
|
158
|
+
static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "19.2.21", ngImport: i0, type: SdTabInfoPipe, isStandalone: true, name: "sdTabInfo" });
|
|
159
159
|
}
|
|
160
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
160
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabInfoPipe, decorators: [{
|
|
161
161
|
type: Pipe,
|
|
162
162
|
args: [{
|
|
163
163
|
name: 'sdTabInfo',
|
|
@@ -236,10 +236,10 @@ class SdTabRouterItemComponent {
|
|
|
236
236
|
this.tabRouterService.close(this.tab);
|
|
237
237
|
}
|
|
238
238
|
};
|
|
239
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
240
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.
|
|
239
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterItemComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: SdTabRouterService }, { token: i2.Router }], target: i0.ɵɵFactoryTarget.Component });
|
|
240
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdTabRouterItemComponent, isStandalone: true, selector: "sd-tab-router-item", inputs: { tab: "tab" }, ngImport: i0, template: "<a\r\n [href]=\"[tab.url]\"\r\n class=\"tab-router__item d-flex align-items-center gap-8\"\r\n [class.tab-router__item--active]=\"tab.isActive\"\r\n (click)=\"onTabClick($event)\"\r\n (mousedown)=\"onMousedown($event)\"\r\n (mouseup)=\"onMouseup($event)\">\r\n @let info = tabInfo | sdTabInfo: tab;\r\n @if (info) {\r\n <sd-badge\r\n style=\"overflow: hidden;white-space: nowrap;\"\r\n [icon]=\"info.icon\"\r\n [title]=\"info.icon\"\r\n [tooltip]=\"info.tooltip || info.name\"\r\n [title]=\"info.name\"\r\n [color]=\"info.color\"\r\n (click)=\"onTabClick($event)\"></sd-badge>\r\n <button\r\n aria-hidden=\"true\"\r\n class=\"tab-router__close d-flex align-items-center justify-content-center ml-auto p-0\"\r\n (click)=\"close($event)\"\r\n (mousedown)=\"$event.stopPropagation()\">\r\n <mat-icon aria-hidden=\"true\" fontIcon=\"close\"></mat-icon>\r\n </button>\r\n }\r\n</a>\r\n", styles: [":host{display:block;overflow:hidden;position:relative;flex:1 1 64px;max-width:240px}:host:after{content:\"\";position:absolute;right:0;top:0;bottom:0;height:16px;width:1px;background:#dde0e5;margin:auto}:host:last-child:after{content:none}:host::ng-deep .tab-router__item sd-badge .c-badge-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:184px}.flex-1{flex:1}.tab-router__icon{background-color:#5c6bc0;width:16px;height:16px;line-height:16px;text-align:center;font-size:10px;color:#fff;border-radius:2px;text-transform:uppercase}.tab-router__icon .mat-icon{height:10px;width:10px;font-size:10px}.tab-router__close{color:#757575;outline:none;border:0;background:none;border-radius:50%;height:16px;width:16px}.tab-router__close:hover{background-color:#0000001f}.tab-router__close .mat-icon{font-size:12px;height:12px;width:12px}.tab-router__item{background:#f2f3f4;padding:8px;color:inherit;text-decoration:none;font-size:12px;line-height:16px;overflow:hidden}.tab-router__item:hover{background-color:#fff}.tab-router__name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tab-router__item--active{border-radius:8px 8px 0 0;background-color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i3.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "component", type: SdBadge, selector: "sd-badge", inputs: ["type", "color", "primary", "secondary", "success", "info", "warning", "error", "fontSet", "title", "description", "tooltip", "icon", "size"], outputs: ["click"] }, { kind: "pipe", type: SdTabInfoPipe, name: "sdTabInfo" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
241
241
|
}
|
|
242
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
242
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterItemComponent, decorators: [{
|
|
243
243
|
type: Component,
|
|
244
244
|
args: [{ selector: 'sd-tab-router-item', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, MatIconModule, SdBadge, SdTabInfoPipe], template: "<a\r\n [href]=\"[tab.url]\"\r\n class=\"tab-router__item d-flex align-items-center gap-8\"\r\n [class.tab-router__item--active]=\"tab.isActive\"\r\n (click)=\"onTabClick($event)\"\r\n (mousedown)=\"onMousedown($event)\"\r\n (mouseup)=\"onMouseup($event)\">\r\n @let info = tabInfo | sdTabInfo: tab;\r\n @if (info) {\r\n <sd-badge\r\n style=\"overflow: hidden;white-space: nowrap;\"\r\n [icon]=\"info.icon\"\r\n [title]=\"info.icon\"\r\n [tooltip]=\"info.tooltip || info.name\"\r\n [title]=\"info.name\"\r\n [color]=\"info.color\"\r\n (click)=\"onTabClick($event)\"></sd-badge>\r\n <button\r\n aria-hidden=\"true\"\r\n class=\"tab-router__close d-flex align-items-center justify-content-center ml-auto p-0\"\r\n (click)=\"close($event)\"\r\n (mousedown)=\"$event.stopPropagation()\">\r\n <mat-icon aria-hidden=\"true\" fontIcon=\"close\"></mat-icon>\r\n </button>\r\n }\r\n</a>\r\n", styles: [":host{display:block;overflow:hidden;position:relative;flex:1 1 64px;max-width:240px}:host:after{content:\"\";position:absolute;right:0;top:0;bottom:0;height:16px;width:1px;background:#dde0e5;margin:auto}:host:last-child:after{content:none}:host::ng-deep .tab-router__item sd-badge .c-badge-title{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;width:184px}.flex-1{flex:1}.tab-router__icon{background-color:#5c6bc0;width:16px;height:16px;line-height:16px;text-align:center;font-size:10px;color:#fff;border-radius:2px;text-transform:uppercase}.tab-router__icon .mat-icon{height:10px;width:10px;font-size:10px}.tab-router__close{color:#757575;outline:none;border:0;background:none;border-radius:50%;height:16px;width:16px}.tab-router__close:hover{background-color:#0000001f}.tab-router__close .mat-icon{font-size:12px;height:12px;width:12px}.tab-router__item{background:#f2f3f4;padding:8px;color:inherit;text-decoration:none;font-size:12px;line-height:16px;overflow:hidden}.tab-router__item:hover{background-color:#fff}.tab-router__name{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.tab-router__item--active{border-radius:8px 8px 0 0;background-color:#fff}\n"] }]
|
|
245
245
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: SdTabRouterService }, { type: i2.Router }], propDecorators: { tab: [{
|
|
@@ -276,10 +276,10 @@ class SdTabRouterNavComponent {
|
|
|
276
276
|
onDrop = (event) => {
|
|
277
277
|
moveItemInArray(this.tabs, event.previousIndex, event.currentIndex);
|
|
278
278
|
};
|
|
279
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
280
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.
|
|
279
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterNavComponent, deps: [{ token: i0.ChangeDetectorRef }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component });
|
|
280
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.21", type: SdTabRouterNavComponent, isStandalone: true, selector: "sd-tab-router-nav", inputs: { tabs: "tabs" }, host: { listeners: { "window:resize": "onResize($event)" } }, viewQueries: [{ propertyName: "tabRouterNav", first: true, predicate: ["tabRouterNav"], descendants: true }], ngImport: i0, template: "<div\n #tabRouterNav\n cdkDropList\n cdkDropListLockAxis=\"x\"\n cdkDropListOrientation=\"horizontal\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"tab-router__nav tab-router__nav--{{ mode }} d-flex align-items-center flex-nowrap\"\n [class.d-none]=\"tabs.length > 1\">\n <ng-container *ngFor=\"let tab of tabs\">\n <sd-tab-router-item [tab]=\"tab\" cdkDrag [cdkDragBoundary]=\"elementRef?.nativeElement\"></sd-tab-router-item>\n </ng-container>\n</div>\n", styles: [".tab-router__nav{background:#f9f9f9;overflow:hidden}.tab-router__nav--compact::ng-deep .tab-router__name{display:none}.tab-router__nav--compact::ng-deep .tab-router__icon{margin:0!important}.tab-router__nav--compact::ng-deep .tab-router__item--active{min-width:240px}.tab-router__nav--compact::ng-deep .tab-router__item--active .tab-router__icon{margin-right:8px!important}.tab-router__nav--compact::ng-deep .tab-router__item--active .tab-router__name{display:-webkit-box}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i2$1.CdkDropList, selector: "[cdkDropList], cdk-drop-list", inputs: ["cdkDropListConnectedTo", "cdkDropListData", "cdkDropListOrientation", "id", "cdkDropListLockAxis", "cdkDropListDisabled", "cdkDropListSortingDisabled", "cdkDropListEnterPredicate", "cdkDropListSortPredicate", "cdkDropListAutoScrollDisabled", "cdkDropListAutoScrollStep", "cdkDropListElementContainer"], outputs: ["cdkDropListDropped", "cdkDropListEntered", "cdkDropListExited", "cdkDropListSorted"], exportAs: ["cdkDropList"] }, { kind: "directive", type: i2$1.CdkDrag, selector: "[cdkDrag]", inputs: ["cdkDragData", "cdkDragLockAxis", "cdkDragRootElement", "cdkDragBoundary", "cdkDragStartDelay", "cdkDragFreeDragPosition", "cdkDragDisabled", "cdkDragConstrainPosition", "cdkDragPreviewClass", "cdkDragPreviewContainer", "cdkDragScale"], outputs: ["cdkDragStarted", "cdkDragReleased", "cdkDragEnded", "cdkDragEntered", "cdkDragExited", "cdkDragDropped", "cdkDragMoved"], exportAs: ["cdkDrag"] }, { kind: "component", type: SdTabRouterItemComponent, selector: "sd-tab-router-item", inputs: ["tab"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
|
|
281
281
|
}
|
|
282
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
282
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterNavComponent, decorators: [{
|
|
283
283
|
type: Component,
|
|
284
284
|
args: [{ selector: 'sd-tab-router-nav', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, DragDropModule, SdTabRouterItemComponent], template: "<div\n #tabRouterNav\n cdkDropList\n cdkDropListLockAxis=\"x\"\n cdkDropListOrientation=\"horizontal\"\n (cdkDropListDropped)=\"onDrop($event)\"\n class=\"tab-router__nav tab-router__nav--{{ mode }} d-flex align-items-center flex-nowrap\"\n [class.d-none]=\"tabs.length > 1\">\n <ng-container *ngFor=\"let tab of tabs\">\n <sd-tab-router-item [tab]=\"tab\" cdkDrag [cdkDragBoundary]=\"elementRef?.nativeElement\"></sd-tab-router-item>\n </ng-container>\n</div>\n", styles: [".tab-router__nav{background:#f9f9f9;overflow:hidden}.tab-router__nav--compact::ng-deep .tab-router__name{display:none}.tab-router__nav--compact::ng-deep .tab-router__icon{margin:0!important}.tab-router__nav--compact::ng-deep .tab-router__item--active{min-width:240px}.tab-router__nav--compact::ng-deep .tab-router__item--active .tab-router__icon{margin-right:8px!important}.tab-router__nav--compact::ng-deep .tab-router__item--active .tab-router__name{display:-webkit-box}\n"] }]
|
|
285
285
|
}], ctorParameters: () => [{ type: i0.ChangeDetectorRef }, { type: i0.ElementRef }], propDecorators: { tabRouterNav: [{
|
|
@@ -301,16 +301,37 @@ class SdTabRouterOutletComponent {
|
|
|
301
301
|
#injector = inject(Injector);
|
|
302
302
|
#tabRouterService = inject(SdTabRouterService);
|
|
303
303
|
#sdNotifyService = inject(SdNotifyService);
|
|
304
|
+
// Inject để đảm bảo SdTabDecoratorService được khởi tạo (nó register BehaviorSubject
|
|
305
|
+
// tĩnh để @SdTab decorator có thể truy cập SdTabRouterService). Không dùng trực tiếp ở đây.
|
|
304
306
|
#tabDecoratorService = inject(SdTabDecoratorService);
|
|
305
307
|
#rootRoute;
|
|
306
308
|
#subscription = new Subscription();
|
|
309
|
+
// State của navigation hiện tại (replaceTab, switchTab, ...) được capture ở RoutesRecognized
|
|
310
|
+
// và dùng lại ở NavigationEnd. Lý do: tại NavigationEnd, getCurrentNavigation() đã trả về null,
|
|
311
|
+
// còn lastSuccessfulNavigation và window.history.state không đáng tin cậy với mọi case.
|
|
312
|
+
#pendingNavigationState = {};
|
|
307
313
|
constructor() {
|
|
308
314
|
this.#subscription.add(this.#router.events
|
|
309
|
-
.pipe(
|
|
315
|
+
.pipe(
|
|
316
|
+
// Một số event của Angular bọc trong wrapper có .routerEvent → unwrap về RouterEvent gốc.
|
|
317
|
+
map((event) => (event instanceof RouterEvent ? event : event.routerEvent)),
|
|
318
|
+
// Hybrid: cần CẢ HAI event vì mỗi event chứa data khác nhau ở thời điểm khác nhau.
|
|
319
|
+
// - RoutesRecognized: navigation đang in-flight → getCurrentNavigation().extras.state đọc được
|
|
320
|
+
// - NavigationEnd: navigation hoàn tất → routerState.root đã update với route mới (cần cho lazy routes)
|
|
321
|
+
filter(event => event instanceof RoutesRecognized || event instanceof NavigationEnd))
|
|
310
322
|
.subscribe(async (event) => {
|
|
323
|
+
if (event instanceof RoutesRecognized) {
|
|
324
|
+
// Capture state ngay lúc nav còn in-flight. Đây là điểm duy nhất chắc chắn
|
|
325
|
+
// getCurrentNavigation() trả về Navigation object với extras.state nguyên vẹn.
|
|
326
|
+
this.#pendingNavigationState = this.#router.getCurrentNavigation()?.extras?.state ?? {};
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
// NavigationEnd: dùng activatedRoute.snapshot và routerState.root MỚI nhất
|
|
330
|
+
// (chứa route component đã được activate, cả lazy lẫn standalone routes).
|
|
311
331
|
const route = this.#getActivatedRouteSnapshot(this.#activatedRoute.snapshot);
|
|
312
332
|
this.#rootRoute = this.#router.routerState.root;
|
|
313
|
-
await this.#activeRoute(event.urlAfterRedirects || event.url, route);
|
|
333
|
+
await this.#activeRoute(event.urlAfterRedirects || event.url, route, this.#pendingNavigationState);
|
|
334
|
+
this.#pendingNavigationState = {};
|
|
314
335
|
}));
|
|
315
336
|
this.#subscription.add(this.#tabRouterService.actions.subscribe((event) => {
|
|
316
337
|
if (event?.type === 'close') {
|
|
@@ -344,7 +365,7 @@ class SdTabRouterOutletComponent {
|
|
|
344
365
|
this.tabRouterNav?.checkUI();
|
|
345
366
|
}
|
|
346
367
|
};
|
|
347
|
-
#activeRoute = async (fullUrl, route) => {
|
|
368
|
+
#activeRoute = async (fullUrl, route, state = {}) => {
|
|
348
369
|
if (!route?.component)
|
|
349
370
|
return;
|
|
350
371
|
const component = route.component;
|
|
@@ -352,48 +373,58 @@ class SdTabRouterOutletComponent {
|
|
|
352
373
|
const params = { ...(route.params || {}) };
|
|
353
374
|
const data = { ...(route.data || {}) };
|
|
354
375
|
const [url] = fullUrl.split('?');
|
|
376
|
+
// Tab identity = hash(url + queryParams). Cùng key = cùng tab, không tạo lại.
|
|
355
377
|
const key = SdUtilities.hash({ url, queryParams });
|
|
356
378
|
let existedIndex = -1;
|
|
357
379
|
let activatedIndex = -1;
|
|
358
380
|
const currentTabs = this.tabs();
|
|
359
|
-
|
|
381
|
+
// QUAN TRỌNG: scan READ-ONLY, KHÔNG mutate tab.isActive trong loop này.
|
|
382
|
+
//
|
|
383
|
+
// Lý do: NavigationEnd có thể fire nhiều lần cho 1 user-action (do nested outlets,
|
|
384
|
+
// redirect, hoặc Angular internal). Vì #activeRoute là async (await getBestInjector),
|
|
385
|
+
// 2 invocations có thể chạy concurrent và interleave với nhau.
|
|
386
|
+
//
|
|
387
|
+
// Nếu mutate tab.isActive = false ở đây, call thứ 2 sẽ thấy isActive đã bị call 1
|
|
388
|
+
// set false rồi → không tìm thấy active tab → activatedIndex stay -1 → splice bỏ qua
|
|
389
|
+
// → tab cũ không bị remove → xuất hiện duplicate tabs.
|
|
390
|
+
//
|
|
391
|
+
// Cách fix: chỉ ĐỌC isActive, sau đó dùng .map() ở dưới để tạo tab objects mới qua spread.
|
|
392
|
+
for (let i = 0; i < currentTabs.length; i++) {
|
|
393
|
+
const tab = currentTabs[i];
|
|
360
394
|
if (tab.key === key) {
|
|
361
|
-
|
|
362
|
-
existedIndex = index;
|
|
395
|
+
existedIndex = i;
|
|
363
396
|
}
|
|
364
|
-
else {
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
this.#tabRouterService.pushEvent(tab, SdTabDeactivated);
|
|
368
|
-
}
|
|
369
|
-
tab.isActive = false;
|
|
397
|
+
else if (tab.isActive) {
|
|
398
|
+
activatedIndex = i;
|
|
399
|
+
this.#tabRouterService.pushEvent(tab, SdTabDeactivated);
|
|
370
400
|
}
|
|
371
|
-
}
|
|
372
|
-
const
|
|
373
|
-
|
|
374
|
-
//
|
|
401
|
+
}
|
|
402
|
+
const replaceTab = state['replaceTab'];
|
|
403
|
+
// Resolve injector phù hợp với route. Cần xử lý 3 trường hợp:
|
|
404
|
+
// - Standalone route (Angular đã set _injector trên routeConfig sau khi activate)
|
|
405
|
+
// - NgModule lazy load (cần createNgModule từ class)
|
|
406
|
+
// - Fallback: root injector
|
|
375
407
|
const getBestInjector = async (snapshot) => {
|
|
376
|
-
//
|
|
408
|
+
// Standalone route: Angular tự lưu environment injector trên routeConfig._injector
|
|
377
409
|
const routeInjector = snapshot._resolvedGui || snapshot.routeConfig?._injector;
|
|
378
410
|
if (routeInjector)
|
|
379
411
|
return routeInjector;
|
|
380
|
-
//
|
|
412
|
+
// NgModule lazy: phải gọi lại loadChildren() để lấy module class rồi createNgModule
|
|
381
413
|
const loadChildren = snapshot.parent?.routeConfig?.loadChildren;
|
|
382
414
|
if (typeof loadChildren === 'function') {
|
|
383
415
|
let loaded = await loadChildren();
|
|
384
|
-
// Unwrap Observable
|
|
385
416
|
if (isObservable(loaded)) {
|
|
386
417
|
loaded = await lastValueFrom(loaded);
|
|
387
418
|
}
|
|
388
|
-
//
|
|
419
|
+
// ES module có thể export default
|
|
389
420
|
if (loaded && typeof loaded === 'object' && 'default' in loaded) {
|
|
390
421
|
loaded = loaded.default;
|
|
391
422
|
}
|
|
392
|
-
//
|
|
423
|
+
// Angular cũ: NgModuleFactory
|
|
393
424
|
if (loaded instanceof NgModuleFactory) {
|
|
394
425
|
return loaded.create(this.#injector).injector;
|
|
395
426
|
}
|
|
396
|
-
//
|
|
427
|
+
// Angular mới: NgModule class. Bọc try/catch vì createNgModule throw nếu không phải NgModule.
|
|
397
428
|
if (typeof loaded === 'function' && !Array.isArray(loaded)) {
|
|
398
429
|
try {
|
|
399
430
|
return createNgModule(loaded, this.#injector).injector;
|
|
@@ -418,21 +449,37 @@ class SdTabRouterOutletComponent {
|
|
|
418
449
|
data,
|
|
419
450
|
tabInfoChanges: new Subject(),
|
|
420
451
|
};
|
|
452
|
+
// Tạo updatedTabs qua spread thay vì mutate (xem lý do ở for loop phía trên).
|
|
453
|
+
// Với tab có isActive không đổi: giữ nguyên reference (tránh trigger ngComponentOutlet
|
|
454
|
+
// re-evaluate không cần thiết). Với tab cần đổi isActive: tạo object mới qua spread,
|
|
455
|
+
// các nested fields (component, injector) vẫn giữ same reference nên component không bị recreate.
|
|
456
|
+
let updatedTabs = currentTabs.map(tab => {
|
|
457
|
+
if (tab.key === key)
|
|
458
|
+
return tab.isActive ? tab : { ...tab, isActive: true };
|
|
459
|
+
return tab.isActive ? { ...tab, isActive: false } : tab;
|
|
460
|
+
});
|
|
461
|
+
// replaceTab: thay vì mở tab mới song song, xoá tab đang active rồi mở tab mới ở cuối.
|
|
462
|
+
// Use case: từ tab "chi tiết" bấm "chỉnh sửa" với replaceTab → tab chi tiết bị xoá,
|
|
463
|
+
// tab chỉnh sửa thay thế (giữ số tab không tăng).
|
|
464
|
+
if (replaceTab && activatedIndex >= 0) {
|
|
465
|
+
updatedTabs = updatedTabs.filter((_, i) => i !== activatedIndex);
|
|
466
|
+
}
|
|
421
467
|
if (existedIndex >= 0) {
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
468
|
+
// Tab đã tồn tại (cùng url + queryParams) → CHỈ activate, KHÔNG thay tab object.
|
|
469
|
+
// Lý do: thay tab object = đổi reference của tab.injector → ngComponentOutlet recreate
|
|
470
|
+
// component → tab bị "reload" mỗi khi click lại hoặc navigate cùng URL.
|
|
471
|
+
//
|
|
472
|
+
// splice phía trên có thể đã shift index nếu activatedIndex < existedIndex.
|
|
473
|
+
const idx = replaceTab && activatedIndex >= 0 && activatedIndex < existedIndex
|
|
474
|
+
? existedIndex - 1
|
|
475
|
+
: existedIndex;
|
|
476
|
+
this.#tabRouterService.setCurrentTab(updatedTabs[idx]);
|
|
477
|
+
this.#tabRouterService.pushEvent(updatedTabs[idx], SdTabActivated);
|
|
428
478
|
this.tabs.set(updatedTabs);
|
|
429
479
|
}
|
|
430
480
|
else {
|
|
431
|
-
|
|
481
|
+
// Tab chưa tồn tại → thêm mới ở cuối.
|
|
432
482
|
this.#tabRouterService.setCurrentTab(newTab);
|
|
433
|
-
if (activatedIndex >= 0 && replaceTab) {
|
|
434
|
-
updatedTabs.splice(activatedIndex, 1);
|
|
435
|
-
}
|
|
436
483
|
this.tabs.set([...updatedTabs, newTab]);
|
|
437
484
|
if (this.tabs().length > 30) {
|
|
438
485
|
this.#sdNotifyService.warning('Bạn đã mở quá nhiều tab.');
|
|
@@ -440,12 +487,15 @@ class SdTabRouterOutletComponent {
|
|
|
440
487
|
}
|
|
441
488
|
this.tabRouterNav?.checkUI();
|
|
442
489
|
};
|
|
490
|
+
// Lần xuống deepest firstChild để lấy snapshot của route lá (route thực sự render component).
|
|
443
491
|
#getActivatedRouteSnapshot = (snapshot) => {
|
|
444
492
|
let node = snapshot;
|
|
445
493
|
while (node.firstChild)
|
|
446
494
|
node = node.firstChild;
|
|
447
495
|
return node;
|
|
448
496
|
};
|
|
497
|
+
// DFS tìm ActivatedRoute (không phải snapshot) trong tree theo component class.
|
|
498
|
+
// Cần ActivatedRoute thật vì SdOutletInjector sẽ inject nó vào component qua DI.
|
|
449
499
|
#getActivatedRoute = (activatedRoute, component) => {
|
|
450
500
|
if (activatedRoute.component === component)
|
|
451
501
|
return activatedRoute;
|
|
@@ -456,16 +506,19 @@ class SdTabRouterOutletComponent {
|
|
|
456
506
|
}
|
|
457
507
|
return null;
|
|
458
508
|
};
|
|
459
|
-
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.
|
|
460
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.
|
|
509
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterOutletComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
510
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdTabRouterOutletComponent, isStandalone: true, selector: "sd-tab-router-outlet", viewQueries: [{ propertyName: "tabRouterNav", first: true, predicate: ["tabRouterNav"], descendants: true }], ngImport: i0, template: "<sd-tab-router-nav [tabs]=\"tabs()\" #tabRouterNav></sd-tab-router-nav>\n\n<div class=\"tab-router__list\">\n @for (tab of tabs(); track tab.key) {\n <div class=\"tab-router__pane\" [class.active]=\"tab.isActive\" [id]=\"tab.key\">\n <div class=\"tab-router__content\">\n <ng-container *ngComponentOutlet=\"tab.component; injector: tab.injector\"></ng-container>\n </div>\n </div>\n } @empty {\n <div class=\"tab-router__empty\">\n </div>\n }\n</div>", styles: [":host{display:flex;flex-direction:column;width:100%;height:calc(100vh - 64px)}:host ::ng-deep .sd-loading{max-width:100%;max-height:100%}.tab-router__list{flex:1}.tab-router__pane{display:none;position:relative;height:100%;width:100%}.tab-router__pane.active{display:block}.tab-router__content{position:absolute;inset:0;overflow:auto;height:100%;width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgComponentOutlet, selector: "[ngComponentOutlet]", inputs: ["ngComponentOutlet", "ngComponentOutletInputs", "ngComponentOutletInjector", "ngComponentOutletContent", "ngComponentOutletNgModule", "ngComponentOutletNgModuleFactory"], exportAs: ["ngComponentOutlet"] }, { kind: "ngmodule", type: MatIconModule }, { kind: "ngmodule", type: MatTooltipModule }, { kind: "component", type: SdTabRouterNavComponent, selector: "sd-tab-router-nav", inputs: ["tabs"] }] });
|
|
461
511
|
}
|
|
462
|
-
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.
|
|
512
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTabRouterOutletComponent, decorators: [{
|
|
463
513
|
type: Component,
|
|
464
514
|
args: [{ selector: 'sd-tab-router-outlet', standalone: true, imports: [CommonModule, MatIconModule, MatTooltipModule, SdTabRouterNavComponent], template: "<sd-tab-router-nav [tabs]=\"tabs()\" #tabRouterNav></sd-tab-router-nav>\n\n<div class=\"tab-router__list\">\n @for (tab of tabs(); track tab.key) {\n <div class=\"tab-router__pane\" [class.active]=\"tab.isActive\" [id]=\"tab.key\">\n <div class=\"tab-router__content\">\n <ng-container *ngComponentOutlet=\"tab.component; injector: tab.injector\"></ng-container>\n </div>\n </div>\n } @empty {\n <div class=\"tab-router__empty\">\n </div>\n }\n</div>", styles: [":host{display:flex;flex-direction:column;width:100%;height:calc(100vh - 64px)}:host ::ng-deep .sd-loading{max-width:100%;max-height:100%}.tab-router__list{flex:1}.tab-router__pane{display:none;position:relative;height:100%;width:100%}.tab-router__pane.active{display:block}.tab-router__content{position:absolute;inset:0;overflow:auto;height:100%;width:100%}\n"] }]
|
|
465
515
|
}], ctorParameters: () => [], propDecorators: { tabRouterNav: [{
|
|
466
516
|
type: ViewChild,
|
|
467
517
|
args: ['tabRouterNav']
|
|
468
518
|
}] } });
|
|
519
|
+
// Custom Injector cho từng tab: override ActivatedRoute thành route của TAB ĐÓ
|
|
520
|
+
// (không phải route hiện tại của router). Nếu không override, mọi tab sẽ inject ActivatedRoute
|
|
521
|
+
// của route đang active → component cũ trong tab inactive nhận data sai khi user navigate.
|
|
469
522
|
class SdOutletInjector {
|
|
470
523
|
route;
|
|
471
524
|
parentInjector;
|