@sd-angular/core 19.0.47 → 19.0.49
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-table.mjs +67 -26
- package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
- package/fesm2022/sd-angular-core-directives.mjs +20 -3
- package/fesm2022/sd-angular-core-directives.mjs.map +1 -1
- package/fesm2022/sd-angular-core-forms-select.mjs +23 -4
- package/fesm2022/sd-angular-core-forms-select.mjs.map +1 -1
- package/fesm2022/sd-angular-core-pipes.mjs +11 -21
- package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
- package/fesm2022/sd-angular-core-services-excel.mjs +34 -0
- package/fesm2022/sd-angular-core-services-excel.mjs.map +1 -1
- package/package.json +9 -9
- package/services/excel/src/lib/excel.model.d.ts +29 -0
- package/services/excel/src/lib/excel.service.d.ts +30 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { input, inject, TemplateRef, Directive, booleanAttribute, computed, ChangeDetectionStrategy, Component, model, output, isSignal, effect, Pipe, ChangeDetectorRef, signal, untracked, viewChild, InjectionToken, Injectable, EventEmitter, Input, Output, ViewChild, ElementRef,
|
|
2
|
+
import { input, inject, TemplateRef, Directive, booleanAttribute, computed, ChangeDetectionStrategy, Component, model, output, isSignal, effect, Pipe, ChangeDetectorRef, signal, untracked, viewChild, InjectionToken, Injectable, DestroyRef, EventEmitter, Input, Output, ViewChild, ElementRef, NgZone, afterNextRender, Renderer2, contentChild, contentChildren } from '@angular/core';
|
|
3
3
|
import { trigger, state, style, transition, animate } from '@angular/animations';
|
|
4
4
|
import * as i4 from '@angular/material/paginator';
|
|
5
5
|
import { MatPaginatorIntl, MatPaginator, MatPaginatorModule } from '@angular/material/paginator';
|
|
@@ -9,7 +9,7 @@ import * as i6 from '@angular/material/table';
|
|
|
9
9
|
import { MatTable, MatTableModule } from '@angular/material/table';
|
|
10
10
|
import { ɵSD_LOCAL_DATE_TIME as _SD_LOCAL_DATE_TIME, SdDateTimeService } from '@sd-angular/core/services/date-time';
|
|
11
11
|
import { SdNotifyService } from '@sd-angular/core/services/notify';
|
|
12
|
-
import { Subject, fromEvent, Subscription, isObservable, firstValueFrom } from 'rxjs';
|
|
12
|
+
import { Subject, merge, fromEvent, take, Subscription, isObservable, firstValueFrom } from 'rxjs';
|
|
13
13
|
import { debounceTime, startWith, map, switchMap, filter } from 'rxjs/operators';
|
|
14
14
|
import { DateUtilities, StringUtilities } from '@sd-angular/core/utilities';
|
|
15
15
|
import { CdkTableModule, CdkColumnDef } from '@angular/cdk/table';
|
|
@@ -1442,15 +1442,12 @@ class ConfigComponent {
|
|
|
1442
1442
|
// dragDisabled không nên là signal (mutated trong drag handlers, không trigger render).
|
|
1443
1443
|
dragDisabled = true;
|
|
1444
1444
|
#setting;
|
|
1445
|
+
#dragRelease;
|
|
1445
1446
|
// ==========================================
|
|
1446
|
-
// 5.
|
|
1447
|
+
// 5. Dọn listener của phiên kéo hiện tại
|
|
1447
1448
|
// ==========================================
|
|
1448
1449
|
constructor() {
|
|
1449
|
-
|
|
1450
|
-
.pipe(takeUntilDestroyed())
|
|
1451
|
-
.subscribe(() => {
|
|
1452
|
-
this.dragDisabled = true;
|
|
1453
|
-
});
|
|
1450
|
+
inject(DestroyRef).onDestroy(() => this.#dragRelease?.unsubscribe());
|
|
1454
1451
|
}
|
|
1455
1452
|
// ==========================================
|
|
1456
1453
|
// 6. PUBLIC API
|
|
@@ -1464,19 +1461,20 @@ class ConfigComponent {
|
|
|
1464
1461
|
this.modal()?.open();
|
|
1465
1462
|
};
|
|
1466
1463
|
close = () => {
|
|
1464
|
+
this.handleMouseUp();
|
|
1467
1465
|
this.modal()?.close();
|
|
1468
1466
|
};
|
|
1469
1467
|
onSave = () => {
|
|
1470
1468
|
const cfg = this.configuration();
|
|
1471
1469
|
if (cfg)
|
|
1472
1470
|
this.#setting?.set(cfg);
|
|
1473
|
-
this.
|
|
1471
|
+
this.close();
|
|
1474
1472
|
this.#ref.detectChanges();
|
|
1475
1473
|
};
|
|
1476
1474
|
onReset = async () => {
|
|
1477
1475
|
this.#confirmService.confirm(this.#i18n.t('core.component.table.config.confirm-reset')).then(() => {
|
|
1478
1476
|
this.#setting?.remove();
|
|
1479
|
-
this.
|
|
1477
|
+
this.close();
|
|
1480
1478
|
this.#ref.detectChanges();
|
|
1481
1479
|
});
|
|
1482
1480
|
};
|
|
@@ -1489,12 +1487,20 @@ class ConfigComponent {
|
|
|
1489
1487
|
}
|
|
1490
1488
|
handleMouseDown() {
|
|
1491
1489
|
this.dragDisabled = false;
|
|
1490
|
+
if (this.#dragRelease && !this.#dragRelease.closed)
|
|
1491
|
+
return;
|
|
1492
|
+
// why: chỉ cần bắt thả chuột ngoài handle trong lúc kéo; modal đóng không giữ listener toàn cục.
|
|
1493
|
+
this.#dragRelease = merge(fromEvent(window, 'mouseup'), fromEvent(window, 'blur'))
|
|
1494
|
+
.pipe(take(1))
|
|
1495
|
+
.subscribe(() => this.handleMouseUp());
|
|
1492
1496
|
}
|
|
1493
1497
|
handleMouseUp() {
|
|
1494
1498
|
this.dragDisabled = true;
|
|
1499
|
+
this.#dragRelease?.unsubscribe();
|
|
1500
|
+
this.#dragRelease = undefined;
|
|
1495
1501
|
}
|
|
1496
1502
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
1497
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ConfigComponent, isStandalone: true, selector: "config", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, tableOption: { classPropertyName: "tableOption", publicName: "tableOption", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changes: "changes" }, providers: [ConfigService], viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true, isSignal: true }, { propertyName: "table", first: true, predicate: MatTable, descendants: true, isSignal: true }], ngImport: i0, template: "@let _configuration = configuration();\n@let _autoId = autoId();\n\n<sd-modal width=\"sm\" [title]=\"'core.component.table.setup' | translate\" #modal>\n @if (_configuration) {\n <div class=\"c-container\">\n <div class=\"c-table\" style=\"max-height: 70vh\">\n <table\n mat-table\n [dataSource]=\"_configuration.columns!\"\n cdkDropList\n [cdkDropListData]=\"_configuration.columns\"\n (cdkDropListDropped)=\"dropTable($event)\">\n <ng-container matColumnDef=\"invisible\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 96px\">\n {{ 'core.component.form-builder.display' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <div class=\"d-flex\" style=\"gap: 8px\">\n <div class=\"c-handle\" (mousedown)=\"handleMouseDown()\" (mouseup)=\"handleMouseUp()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#5f6368\">\n <path d=\"M0 0h24v24H0V0z\" fill=\"none\" />\n <path\n d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\" />\n </svg>\n </div>\n <sd-switch [model]=\"!item.invisible\" (modelChange)=\"item.invisible = !item.invisible\" />\n </div>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"title\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef>\n {{ 'core.component.table.column-header' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input [clearable]=\"true\" class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.title\" [(model)]=\"item.title\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"width\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.width' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.width\" [(model)]=\"item.width\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"fixed\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.fixed' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.fixed\" />\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"truncate\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 100px\">\n {{ 'core.component.table.char-limit' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.truncate\" />\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"['invisible', 'title', 'width', 'fixed', 'truncate']; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: ['invisible', 'title', 'width', 'fixed', 'truncate']\"\n cdkDrag\n [cdkDragData]=\"row\"\n cdkDragLockAxis=\"y\"\n [cdkDragDisabled]=\"dragDisabled\">\n </tr>\n </table>\n </div>\n </div>\n }\n <ng-container sdFooter>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-skip' : null\" class=\"mr-8\" (click)=\"close()\" [title]=\"'core.component.table.skip' | translate\" color=\"secondary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-reset' : null\" class=\"mr-8\" (click)=\"onReset()\" [title]=\"'core.component.form-builder.default' | translate\" color=\"primary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-apply' : null\" (click)=\"onSave()\" [title]=\"'core.component.table.apply' | translate\" type=\"fill\" color=\"primary\"> </sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".c-table{position:relative;overflow:auto}.c-table table{width:100%}.c-table .c-th{font-weight:500;font-size:14px;line-height:20px;color:#000}.c-container{position:relative;min-height:200px}.c-handle{color:#ccc;cursor:move}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i6.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i6.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "ngmodule", type: CdkTableModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i11.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: i11.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: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdSwitch, selector: "sd-switch", inputs: ["autoId", "name", "form", "label", "color", "disabled", "viewed", "hideInlineError", "required", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1503
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ConfigComponent, isStandalone: true, selector: "config", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, tableOption: { classPropertyName: "tableOption", publicName: "tableOption", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { changes: "changes" }, providers: [ConfigService], viewQueries: [{ propertyName: "modal", first: true, predicate: SdModal, descendants: true, isSignal: true }, { propertyName: "table", first: true, predicate: MatTable, descendants: true, isSignal: true }], ngImport: i0, template: "@let _configuration = configuration();\n@let _autoId = autoId();\n\n<sd-modal width=\"sm\" [title]=\"'core.component.table.setup' | translate\" (sdClosed)=\"handleMouseUp()\" #modal>\n @if (_configuration) {\n <div class=\"c-container\">\n <div class=\"c-table\" style=\"max-height: 70vh\">\n <table\n mat-table\n [dataSource]=\"_configuration.columns!\"\n cdkDropList\n [cdkDropListData]=\"_configuration.columns\"\n (cdkDropListDropped)=\"dropTable($event)\">\n <ng-container matColumnDef=\"invisible\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 96px\">\n {{ 'core.component.form-builder.display' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <div class=\"d-flex\" style=\"gap: 8px\">\n <div class=\"c-handle\" (mousedown)=\"handleMouseDown()\" (mouseup)=\"handleMouseUp()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#5f6368\">\n <path d=\"M0 0h24v24H0V0z\" fill=\"none\" />\n <path\n d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\" />\n </svg>\n </div>\n <sd-switch [model]=\"!item.invisible\" (modelChange)=\"item.invisible = !item.invisible\" />\n </div>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"title\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef>\n {{ 'core.component.table.column-header' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input [clearable]=\"true\" class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.title\" [(model)]=\"item.title\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"width\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.width' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.width\" [(model)]=\"item.width\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"fixed\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.fixed' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.fixed\" />\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"truncate\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 100px\">\n {{ 'core.component.table.char-limit' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.truncate\" />\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"['invisible', 'title', 'width', 'fixed', 'truncate']; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: ['invisible', 'title', 'width', 'fixed', 'truncate']\"\n cdkDrag\n [cdkDragData]=\"row\"\n cdkDragLockAxis=\"y\"\n [cdkDragDisabled]=\"dragDisabled\">\n </tr>\n </table>\n </div>\n </div>\n }\n <ng-container sdFooter>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-skip' : null\" class=\"mr-8\" (click)=\"close()\" [title]=\"'core.component.table.skip' | translate\" color=\"secondary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-reset' : null\" class=\"mr-8\" (click)=\"onReset()\" [title]=\"'core.component.form-builder.default' | translate\" color=\"primary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-apply' : null\" (click)=\"onSave()\" [title]=\"'core.component.table.apply' | translate\" type=\"fill\" color=\"primary\"> </sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".c-table{position:relative;overflow:auto}.c-table table{width:100%}.c-table .c-th{font-weight:500;font-size:14px;line-height:20px;color:#000}.c-container{position:relative;min-height:200px}.c-handle{color:#ccc;cursor:move}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "ngmodule", type: FormsModule }, { kind: "ngmodule", type: MatTableModule }, { kind: "component", type: i6.MatTable, selector: "mat-table, table[mat-table]", exportAs: ["matTable"] }, { kind: "directive", type: i6.MatHeaderCellDef, selector: "[matHeaderCellDef]" }, { kind: "directive", type: i6.MatHeaderRowDef, selector: "[matHeaderRowDef]", inputs: ["matHeaderRowDef", "matHeaderRowDefSticky"] }, { kind: "directive", type: i6.MatColumnDef, selector: "[matColumnDef]", inputs: ["matColumnDef"] }, { kind: "directive", type: i6.MatCellDef, selector: "[matCellDef]" }, { kind: "directive", type: i6.MatRowDef, selector: "[matRowDef]", inputs: ["matRowDefColumns", "matRowDefWhen"] }, { kind: "directive", type: i6.MatHeaderCell, selector: "mat-header-cell, th[mat-header-cell]" }, { kind: "directive", type: i6.MatCell, selector: "mat-cell, td[mat-cell]" }, { kind: "component", type: i6.MatHeaderRow, selector: "mat-header-row, tr[mat-header-row]", exportAs: ["matHeaderRow"] }, { kind: "component", type: i6.MatRow, selector: "mat-row, tr[mat-row]", exportAs: ["matRow"] }, { kind: "ngmodule", type: MatSlideToggleModule }, { kind: "ngmodule", type: CdkTableModule }, { kind: "ngmodule", type: DragDropModule }, { kind: "directive", type: i11.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: i11.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: SdButton, selector: "sd-button", inputs: ["autoId", "type", "color", "size", "fontSet", "title", "width", "tooltip", "prefixIcon", "suffixIcon", "disabled", "loading", "block", "htmlType"], outputs: ["click"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "clearable", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdModal, selector: "sd-modal", inputs: ["autoId", "title", "color", "width", "height", "view", "modalClass", "lazyLoadContent", "hideClose", "disableBackdropClose"], outputs: ["sdClosed"] }, { kind: "component", type: SdSwitch, selector: "sd-switch", inputs: ["autoId", "name", "form", "label", "color", "disabled", "viewed", "hideInlineError", "required", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }] });
|
|
1498
1504
|
}
|
|
1499
1505
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ConfigComponent, decorators: [{
|
|
1500
1506
|
type: Component,
|
|
@@ -1510,7 +1516,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
1510
1516
|
SdModal,
|
|
1511
1517
|
SdSwitch,
|
|
1512
1518
|
TranslatePipe,
|
|
1513
|
-
], providers: [ConfigService], template: "@let _configuration = configuration();\n@let _autoId = autoId();\n\n<sd-modal width=\"sm\" [title]=\"'core.component.table.setup' | translate\" #modal>\n @if (_configuration) {\n <div class=\"c-container\">\n <div class=\"c-table\" style=\"max-height: 70vh\">\n <table\n mat-table\n [dataSource]=\"_configuration.columns!\"\n cdkDropList\n [cdkDropListData]=\"_configuration.columns\"\n (cdkDropListDropped)=\"dropTable($event)\">\n <ng-container matColumnDef=\"invisible\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 96px\">\n {{ 'core.component.form-builder.display' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <div class=\"d-flex\" style=\"gap: 8px\">\n <div class=\"c-handle\" (mousedown)=\"handleMouseDown()\" (mouseup)=\"handleMouseUp()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#5f6368\">\n <path d=\"M0 0h24v24H0V0z\" fill=\"none\" />\n <path\n d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\" />\n </svg>\n </div>\n <sd-switch [model]=\"!item.invisible\" (modelChange)=\"item.invisible = !item.invisible\" />\n </div>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"title\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef>\n {{ 'core.component.table.column-header' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input [clearable]=\"true\" class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.title\" [(model)]=\"item.title\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"width\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.width' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.width\" [(model)]=\"item.width\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"fixed\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.fixed' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.fixed\" />\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"truncate\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 100px\">\n {{ 'core.component.table.char-limit' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.truncate\" />\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"['invisible', 'title', 'width', 'fixed', 'truncate']; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: ['invisible', 'title', 'width', 'fixed', 'truncate']\"\n cdkDrag\n [cdkDragData]=\"row\"\n cdkDragLockAxis=\"y\"\n [cdkDragDisabled]=\"dragDisabled\">\n </tr>\n </table>\n </div>\n </div>\n }\n <ng-container sdFooter>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-skip' : null\" class=\"mr-8\" (click)=\"close()\" [title]=\"'core.component.table.skip' | translate\" color=\"secondary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-reset' : null\" class=\"mr-8\" (click)=\"onReset()\" [title]=\"'core.component.form-builder.default' | translate\" color=\"primary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-apply' : null\" (click)=\"onSave()\" [title]=\"'core.component.table.apply' | translate\" type=\"fill\" color=\"primary\"> </sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".c-table{position:relative;overflow:auto}.c-table table{width:100%}.c-table .c-th{font-weight:500;font-size:14px;line-height:20px;color:#000}.c-container{position:relative;min-height:200px}.c-handle{color:#ccc;cursor:move}\n"] }]
|
|
1519
|
+
], providers: [ConfigService], template: "@let _configuration = configuration();\n@let _autoId = autoId();\n\n<sd-modal width=\"sm\" [title]=\"'core.component.table.setup' | translate\" (sdClosed)=\"handleMouseUp()\" #modal>\n @if (_configuration) {\n <div class=\"c-container\">\n <div class=\"c-table\" style=\"max-height: 70vh\">\n <table\n mat-table\n [dataSource]=\"_configuration.columns!\"\n cdkDropList\n [cdkDropListData]=\"_configuration.columns\"\n (cdkDropListDropped)=\"dropTable($event)\">\n <ng-container matColumnDef=\"invisible\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 96px\">\n {{ 'core.component.form-builder.display' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <div class=\"d-flex\" style=\"gap: 8px\">\n <div class=\"c-handle\" (mousedown)=\"handleMouseDown()\" (mouseup)=\"handleMouseUp()\">\n <svg xmlns=\"http://www.w3.org/2000/svg\" height=\"24px\" viewBox=\"0 0 24 24\" width=\"24px\" fill=\"#5f6368\">\n <path d=\"M0 0h24v24H0V0z\" fill=\"none\" />\n <path\n d=\"M11 18c0 1.1-.9 2-2 2s-2-.9-2-2 .9-2 2-2 2 .9 2 2zm-2-8c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0-6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm6 4c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2zm0 2c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2zm0 6c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z\" />\n </svg>\n </div>\n <sd-switch [model]=\"!item.invisible\" (modelChange)=\"item.invisible = !item.invisible\" />\n </div>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"title\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef>\n {{ 'core.component.table.column-header' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input [clearable]=\"true\" class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.title\" [(model)]=\"item.title\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"width\">\n <th mat-header-cell class=\"c-th px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.width' | translate }}\n </th>\n <td class=\"px-4\" mat-cell *matCellDef=\"let item\">\n <sd-input class=\"py-4\" size=\"sm\" [placeholder]=\"item?.origin?.width\" [(model)]=\"item.width\" hideInlineError></sd-input>\n </td>\n </ng-container>\n <ng-container matColumnDef=\"fixed\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 80px\">\n {{ 'core.component.table.fixed' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.fixed\" />\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"truncate\">\n <th mat-header-cell class=\"c-th text-center px-4\" *matHeaderCellDef style=\"width: 100px\">\n {{ 'core.component.table.char-limit' | translate }}\n </th>\n <td class=\"text-center px-4\" mat-cell *matCellDef=\"let item\">\n <sd-switch [(model)]=\"item.truncate\" />\n </td>\n </ng-container>\n\n <tr mat-header-row *matHeaderRowDef=\"['invisible', 'title', 'width', 'fixed', 'truncate']; sticky: true\"></tr>\n <tr\n mat-row\n *matRowDef=\"let row; columns: ['invisible', 'title', 'width', 'fixed', 'truncate']\"\n cdkDrag\n [cdkDragData]=\"row\"\n cdkDragLockAxis=\"y\"\n [cdkDragDisabled]=\"dragDisabled\">\n </tr>\n </table>\n </div>\n </div>\n }\n <ng-container sdFooter>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-skip' : null\" class=\"mr-8\" (click)=\"close()\" [title]=\"'core.component.table.skip' | translate\" color=\"secondary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-reset' : null\" class=\"mr-8\" (click)=\"onReset()\" [title]=\"'core.component.form-builder.default' | translate\" color=\"primary\"> </sd-button>\n <sd-button [autoId]=\"_autoId ? _autoId + '-config-apply' : null\" (click)=\"onSave()\" [title]=\"'core.component.table.apply' | translate\" type=\"fill\" color=\"primary\"> </sd-button>\n </ng-container>\n</sd-modal>\n", styles: [".c-table{position:relative;overflow:auto}.c-table table{width:100%}.c-table .c-th{font-weight:500;font-size:14px;line-height:20px;color:#000}.c-container{position:relative;min-height:200px}.c-handle{color:#ccc;cursor:move}\n"] }]
|
|
1514
1520
|
}], ctorParameters: () => [] });
|
|
1515
1521
|
|
|
1516
1522
|
/** Segments that must never be traversed, matching the `@sdcorejs/utils` 1.2 safe-key rule. */
|
|
@@ -2207,25 +2213,29 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
|
|
|
2207
2213
|
class StickyShadowDirective {
|
|
2208
2214
|
#el = inject((ElementRef));
|
|
2209
2215
|
#destroyRef = inject(DestroyRef);
|
|
2216
|
+
#zone = inject(NgZone);
|
|
2210
2217
|
constructor() {
|
|
2211
|
-
afterNextRender(() => {
|
|
2218
|
+
afterNextRender(() => this.#zone.runOutsideAngular(() => {
|
|
2212
2219
|
const container = this.#el.nativeElement;
|
|
2213
2220
|
// Dùng MutationObserver để detect khi CDK render rows mới vào table
|
|
2214
2221
|
// (data load async, phân trang, ...) — lúc này td mới có đủ sticky classes
|
|
2215
|
-
|
|
2216
|
-
|
|
2217
|
-
|
|
2222
|
+
// why: gộp mutation và scroll vào cùng một lượt quét, không kích hoạt change detection của bảng.
|
|
2223
|
+
const mutations = new Subject();
|
|
2224
|
+
const mutationObserver = new MutationObserver(() => mutations.next());
|
|
2218
2225
|
mutationObserver.observe(container, {
|
|
2219
2226
|
childList: true, // Theo dõi thêm/bớt các node con
|
|
2220
2227
|
subtree: true, // Bao gồm tất cả các cấp con cháu
|
|
2221
2228
|
});
|
|
2222
2229
|
// Cập nhật shadow mỗi khi user scroll ngang
|
|
2223
|
-
fromEvent(container, 'scroll')
|
|
2230
|
+
merge(mutations, fromEvent(container, 'scroll'))
|
|
2224
2231
|
.pipe(debounceTime(10), takeUntilDestroyed(this.#destroyRef))
|
|
2225
2232
|
.subscribe(() => this.#updateShadow(container));
|
|
2226
2233
|
// Disconnect MutationObserver khi directive bị destroy
|
|
2227
|
-
this.#destroyRef.onDestroy(() =>
|
|
2228
|
-
|
|
2234
|
+
this.#destroyRef.onDestroy(() => {
|
|
2235
|
+
mutationObserver.disconnect();
|
|
2236
|
+
mutations.complete();
|
|
2237
|
+
});
|
|
2238
|
+
}));
|
|
2229
2239
|
}
|
|
2230
2240
|
/**
|
|
2231
2241
|
* Toggle class shadow lên tất cả th và td của một cột.
|
|
@@ -2707,9 +2717,7 @@ class TableExportService {
|
|
|
2707
2717
|
// theo catalog ngôn ngữ hiện tại.
|
|
2708
2718
|
exportTitle = computed(() => {
|
|
2709
2719
|
const percent = this.#exportPercent();
|
|
2710
|
-
return percent === null
|
|
2711
|
-
? this.#i18n.t('core.component.table.export')
|
|
2712
|
-
: this.#i18n.t('core.component.table.exporting', { percent });
|
|
2720
|
+
return percent === null ? this.#i18n.t('core.component.table.export') : this.#i18n.t('core.component.table.exporting', { percent });
|
|
2713
2721
|
});
|
|
2714
2722
|
/** Đặt tiến độ export (0-100), hoặc null để trả nút về nhãn mặc định. */
|
|
2715
2723
|
setExportProgress = (percent) => {
|
|
@@ -2826,13 +2834,13 @@ class TableExportService {
|
|
|
2826
2834
|
}
|
|
2827
2835
|
}
|
|
2828
2836
|
else if (column.type === 'date') {
|
|
2829
|
-
obj[fieldStr] = this.#dateTime.toFormat(itemValue, 'yyyy/MM/dd');
|
|
2837
|
+
obj[fieldStr] = isCSV ? this.#dateTime.toFormat(itemValue, 'yyyy/MM/dd') : this.#toExcelDate(itemValue, column.type);
|
|
2830
2838
|
}
|
|
2831
2839
|
else if (column.type === 'datetime') {
|
|
2832
|
-
obj[fieldStr] = this.#dateTime.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
|
|
2840
|
+
obj[fieldStr] = isCSV ? this.#dateTime.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss') : this.#toExcelDate(itemValue, column.type);
|
|
2833
2841
|
}
|
|
2834
2842
|
else if (column.type === 'time') {
|
|
2835
|
-
obj[fieldStr] = this.#dateTime.toFormat(itemValue, 'HH:mm:ss');
|
|
2843
|
+
obj[fieldStr] = isCSV ? this.#dateTime.toFormat(itemValue, 'HH:mm:ss') : this.#toExcelDate(itemValue, column.type);
|
|
2836
2844
|
}
|
|
2837
2845
|
else if (column.type === 'values' || column.type === 'lazy-values') {
|
|
2838
2846
|
const vals = (Array.isArray(itemValue) ? itemValue : [itemValue]).filter(e => !!e?.toString());
|
|
@@ -2880,8 +2888,21 @@ class TableExportService {
|
|
|
2880
2888
|
}
|
|
2881
2889
|
}
|
|
2882
2890
|
}
|
|
2891
|
+
const allColumns = this.#allColumns(option);
|
|
2892
|
+
const allExportedColumns = this.#allExportedColumns(option);
|
|
2893
|
+
const dateFormats = { date: 'dd/mm/yyyy', datetime: 'dd/mm/yyyy hh:mm:ss', time: 'hh:mm:ss' };
|
|
2894
|
+
const excelColumns = columns.map(exportColumn => {
|
|
2895
|
+
const column = allColumns.find(e => e.field === exportColumn.field);
|
|
2896
|
+
const exportedColumn = allExportedColumns.find(e => e.field === exportColumn.field);
|
|
2897
|
+
if (!column || column.transform || exportedColumn?.transform)
|
|
2898
|
+
return { ...exportColumn };
|
|
2899
|
+
if (column.type === 'date' || column.type === 'datetime' || column.type === 'time') {
|
|
2900
|
+
return { ...exportColumn, numFmt: dateFormats[column.type] };
|
|
2901
|
+
}
|
|
2902
|
+
return { ...exportColumn };
|
|
2903
|
+
});
|
|
2883
2904
|
await this.#excelService.export({
|
|
2884
|
-
columns,
|
|
2905
|
+
columns: excelColumns,
|
|
2885
2906
|
items,
|
|
2886
2907
|
fileName: option.export?.fileName,
|
|
2887
2908
|
sheets,
|
|
@@ -2892,6 +2913,26 @@ class TableExportService {
|
|
|
2892
2913
|
this.setExportProgress(null);
|
|
2893
2914
|
}
|
|
2894
2915
|
}
|
|
2916
|
+
/** Convert the existing formatter's wall clock to an Excel 1900 serial, never reparsing user text. */
|
|
2917
|
+
#toExcelDate(value, type) {
|
|
2918
|
+
const formatted = this.#dateTime.toFormat(value, type === 'time' ? 'HH:mm:ss' : 'yyyy/MM/dd HH:mm:ss');
|
|
2919
|
+
if (!formatted)
|
|
2920
|
+
return '';
|
|
2921
|
+
const [calendar, clock] = type === 'time' ? ['', formatted] : formatted.split(' ');
|
|
2922
|
+
const [hour, minute, second] = clock.split(':').map(Number);
|
|
2923
|
+
const fraction = (hour * 3600 + minute * 60 + second) / 86400;
|
|
2924
|
+
if (type === 'time')
|
|
2925
|
+
return fraction;
|
|
2926
|
+
const [year, month, day] = calendar.split('/').map(Number);
|
|
2927
|
+
if (year < 1900 || year > 9999) {
|
|
2928
|
+
return this.#dateTime.toFormat(value, type === 'date' ? 'dd/MM/yyyy' : 'dd/MM/yyyy HH:mm:ss');
|
|
2929
|
+
}
|
|
2930
|
+
// why: tính từ thành phần UTC để DST của máy không làm lệch wall clock đã được formatter xác định.
|
|
2931
|
+
const days = (Date.UTC(year, month - 1, day) - Date.UTC(1899, 11, 31)) / 86400000;
|
|
2932
|
+
// why: Excel giữ ngày 29/02/1900 không có thật ở serial 60; ngày thực từ 01/03/1900 phải cộng 1.
|
|
2933
|
+
const serial = days + (days >= 60 ? 1 : 0);
|
|
2934
|
+
return type === 'date' ? serial : serial + fraction;
|
|
2935
|
+
}
|
|
2895
2936
|
#getExportColumns(context, explicitColumns) {
|
|
2896
2937
|
const { option, configuration } = context;
|
|
2897
2938
|
let tableColumns = [];
|