@sdcorejs/angular 19.0.7 → 19.0.9

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.
Files changed (93) hide show
  1. package/README.md +8 -2
  2. package/assets/STYLE-GUIDE.md +13 -0
  3. package/components/autoid-inspector/index.d.ts +1 -7
  4. package/components/autoid-inspector/src/autoid-inspector.component.d.ts +22 -8
  5. package/components/autoid-inspector/src/models/autoid-element.model.d.ts +7 -7
  6. package/components/autoid-inspector/src/models/autoid-export-format.model.d.ts +17 -8
  7. package/components/autoid-inspector/src/models/autoid-inspector-config.model.d.ts +10 -5
  8. package/components/autoid-inspector/src/services/autoid-audit.service.d.ts +4 -4
  9. package/components/autoid-inspector/src/services/autoid-export.service.d.ts +7 -6
  10. package/components/autoid-inspector/src/services/autoid-scanner.service.d.ts +3 -3
  11. package/components/table/src/components/filter/column-filter/column-filter.component.d.ts +0 -1
  12. package/components/table/src/services/table-filter/table-filter.service.d.ts +1 -0
  13. package/components/table/src/services/table-local/table-local.util.d.ts +19 -0
  14. package/components/table/src/services/table-reorder/table-reorder.util.d.ts +29 -0
  15. package/components/table/src/services/table-selection/table-selection.util.d.ts +8 -0
  16. package/components/table/src/services/tree/tree.util.d.ts +10 -1
  17. package/components/table/src/table.component.d.ts +6 -10
  18. package/fesm2022/sdcorejs-angular-components-anchor.mjs +2 -3
  19. package/fesm2022/sdcorejs-angular-components-anchor.mjs.map +1 -1
  20. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs +195 -25
  21. package/fesm2022/sdcorejs-angular-components-autoid-inspector.mjs.map +1 -1
  22. package/fesm2022/sdcorejs-angular-components-badge.mjs +2 -2
  23. package/fesm2022/sdcorejs-angular-components-badge.mjs.map +1 -1
  24. package/fesm2022/sdcorejs-angular-components-button.mjs +2 -2
  25. package/fesm2022/sdcorejs-angular-components-button.mjs.map +1 -1
  26. package/fesm2022/sdcorejs-angular-components-document-builder.mjs +5 -6
  27. package/fesm2022/sdcorejs-angular-components-document-builder.mjs.map +1 -1
  28. package/fesm2022/sdcorejs-angular-components-editor.mjs +4 -4
  29. package/fesm2022/sdcorejs-angular-components-editor.mjs.map +1 -1
  30. package/fesm2022/sdcorejs-angular-components-form-generic.mjs +4 -4
  31. package/fesm2022/sdcorejs-angular-components-form-generic.mjs.map +1 -1
  32. package/fesm2022/sdcorejs-angular-components-import-excel.mjs.map +1 -1
  33. package/fesm2022/sdcorejs-angular-components-inform.mjs +2 -2
  34. package/fesm2022/sdcorejs-angular-components-inform.mjs.map +1 -1
  35. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs +2 -2
  36. package/fesm2022/sdcorejs-angular-components-modal-resizable.mjs.map +1 -1
  37. package/fesm2022/sdcorejs-angular-components-modal.mjs +2 -2
  38. package/fesm2022/sdcorejs-angular-components-modal.mjs.map +1 -1
  39. package/fesm2022/sdcorejs-angular-components-operator.mjs +2 -2
  40. package/fesm2022/sdcorejs-angular-components-operator.mjs.map +1 -1
  41. package/fesm2022/sdcorejs-angular-components-org-chart.mjs +2 -2
  42. package/fesm2022/sdcorejs-angular-components-org-chart.mjs.map +1 -1
  43. package/fesm2022/sdcorejs-angular-components-preview.mjs +3 -3
  44. package/fesm2022/sdcorejs-angular-components-preview.mjs.map +1 -1
  45. package/fesm2022/sdcorejs-angular-components-query-bar.mjs +8 -8
  46. package/fesm2022/sdcorejs-angular-components-query-bar.mjs.map +1 -1
  47. package/fesm2022/sdcorejs-angular-components-section.mjs +2 -2
  48. package/fesm2022/sdcorejs-angular-components-section.mjs.map +1 -1
  49. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs +6 -4
  50. package/fesm2022/sdcorejs-angular-components-side-drawer.mjs.map +1 -1
  51. package/fesm2022/sdcorejs-angular-components-table.mjs +772 -810
  52. package/fesm2022/sdcorejs-angular-components-table.mjs.map +1 -1
  53. package/fesm2022/sdcorejs-angular-components-tree.mjs +2 -2
  54. package/fesm2022/sdcorejs-angular-components-tree.mjs.map +1 -1
  55. package/fesm2022/sdcorejs-angular-components-upload-file.mjs +6 -7
  56. package/fesm2022/sdcorejs-angular-components-upload-file.mjs.map +1 -1
  57. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs +4 -5
  58. package/fesm2022/sdcorejs-angular-forms-autocomplete.mjs.map +1 -1
  59. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs +3 -3
  60. package/fesm2022/sdcorejs-angular-forms-checkbox.mjs.map +1 -1
  61. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs +4 -4
  62. package/fesm2022/sdcorejs-angular-forms-chip-calendar.mjs.map +1 -1
  63. package/fesm2022/sdcorejs-angular-forms-chip.mjs +4 -4
  64. package/fesm2022/sdcorejs-angular-forms-chip.mjs.map +1 -1
  65. package/fesm2022/sdcorejs-angular-forms-date-range.mjs +16 -9
  66. package/fesm2022/sdcorejs-angular-forms-date-range.mjs.map +1 -1
  67. package/fesm2022/sdcorejs-angular-forms-date.mjs +5 -6
  68. package/fesm2022/sdcorejs-angular-forms-date.mjs.map +1 -1
  69. package/fesm2022/sdcorejs-angular-forms-datetime.mjs +5 -6
  70. package/fesm2022/sdcorejs-angular-forms-datetime.mjs.map +1 -1
  71. package/fesm2022/sdcorejs-angular-forms-inline-text.mjs +2 -2
  72. package/fesm2022/sdcorejs-angular-forms-inline-text.mjs.map +1 -1
  73. package/fesm2022/sdcorejs-angular-forms-input-number.mjs +5 -6
  74. package/fesm2022/sdcorejs-angular-forms-input-number.mjs.map +1 -1
  75. package/fesm2022/sdcorejs-angular-forms-input.mjs +3 -3
  76. package/fesm2022/sdcorejs-angular-forms-input.mjs.map +1 -1
  77. package/fesm2022/sdcorejs-angular-forms-radio.mjs +3 -4
  78. package/fesm2022/sdcorejs-angular-forms-radio.mjs.map +1 -1
  79. package/fesm2022/sdcorejs-angular-forms-select.mjs +2 -2
  80. package/fesm2022/sdcorejs-angular-forms-select.mjs.map +1 -1
  81. package/fesm2022/sdcorejs-angular-forms-switch.mjs +3 -4
  82. package/fesm2022/sdcorejs-angular-forms-switch.mjs.map +1 -1
  83. package/fesm2022/sdcorejs-angular-forms-textarea.mjs +5 -7
  84. package/fesm2022/sdcorejs-angular-forms-textarea.mjs.map +1 -1
  85. package/fesm2022/sdcorejs-angular-modules-layout.mjs +2 -3
  86. package/fesm2022/sdcorejs-angular-modules-layout.mjs.map +1 -1
  87. package/fesm2022/sdcorejs-angular-services-api.mjs +1 -2
  88. package/fesm2022/sdcorejs-angular-services-api.mjs.map +1 -1
  89. package/fesm2022/sdcorejs-angular-services-confirm.mjs +2 -2
  90. package/fesm2022/sdcorejs-angular-services-confirm.mjs.map +1 -1
  91. package/forms/date-range/src/date-range.component.d.ts +2 -0
  92. package/forms/input-number/src/input-number.component.d.ts +0 -1
  93. package/package.json +1 -1
@@ -11,7 +11,7 @@ import * as i1$1 from '@sdcorejs/angular/services';
11
11
  import { SdExcelService, SdNotifyService } from '@sdcorejs/angular/services';
12
12
  import { Subject, fromEvent, Subscription, isObservable, firstValueFrom } from 'rxjs';
13
13
  import { debounceTime, startWith, map, switchMap } from 'rxjs/operators';
14
- import * as uuid from 'uuid';
14
+ import { DateUtilities, StringUtilities } from '@sdcorejs/angular/utilities';
15
15
  import { CdkTableModule, CdkColumnDef } from '@angular/cdk/table';
16
16
  import * as i11 from '@angular/cdk/drag-drop';
17
17
  import { moveItemInArray, DragDropModule } from '@angular/cdk/drag-drop';
@@ -34,7 +34,7 @@ import { SdQuickAction } from '@sdcorejs/angular/components/quick-action';
34
34
  import { SdTooltipDirective, SdScrollDirective, SdDesktopDirective, SdMobileDirective, SdHoverCopyDirective } from '@sdcorejs/angular/directives';
35
35
  import { SdSafeHtmlPipe, SdFormatNumberPipe } from '@sdcorejs/angular/pipes';
36
36
  import { BrowserUtilities, Utilities } from '@sdcorejs/utils/fns';
37
- import { DateUtilities, ArrayUtilities, NumberUtilities } from '@sdcorejs/angular/utilities/extensions';
37
+ import { DateUtilities as DateUtilities$1, ArrayUtilities, NumberUtilities } from '@sdcorejs/angular/utilities/extensions';
38
38
  import { SdBadge } from '@sdcorejs/angular/components/badge';
39
39
  import { SdOperator } from '@sdcorejs/angular/components/operator';
40
40
  import { SdInput, SdInputNumber, SdSelect, SdDate, SdDatetime, SdDateRange as SdDateRange$1, SdCheckbox, SdSwitch } from '@sdcorejs/angular/forms';
@@ -51,7 +51,6 @@ import * as i4 from '@angular/material/button';
51
51
  import { MatButtonModule } from '@angular/material/button';
52
52
  import * as i2$1 from '@angular/material/tooltip';
53
53
  import { MatTooltipModule } from '@angular/material/tooltip';
54
- import { StringUtilities, DateUtilities as DateUtilities$1 } from '@sdcorejs/angular/utilities';
55
54
  import * as i5$1 from '@angular/material/chips';
56
55
  import { MatChipsModule } from '@angular/material/chips';
57
56
 
@@ -163,6 +162,242 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
163
162
  }]
164
163
  }] });
165
164
 
165
+ const SdConvertToPagingReq = (filterRequest, args) => {
166
+ const { externalFilters, columns, fieldMapping } = args;
167
+ const req = {
168
+ filters: [],
169
+ orders: args?.orders || [],
170
+ pageNumber: filterRequest.pageNumber,
171
+ pageSize: filterRequest.pageSize,
172
+ };
173
+ const { filters } = req;
174
+ const { rawExternalFilter, rawColumnFilter, columnOperator, orderBy, orderDirection } = filterRequest;
175
+ // Xử lý external filter
176
+ for (const externalFilter of externalFilters || []) {
177
+ const value = rawExternalFilter?.[externalFilter.field];
178
+ const field = fieldMapping?.[externalFilter.field] || externalFilter.field;
179
+ // Nếu có giá trị thì mới xử lý filter
180
+ if (value !== undefined && value !== null && value !== '') {
181
+ if (externalFilter.type === 'string') {
182
+ if (externalFilter.defaultOperator === 'EQUAL' && typeof value === 'string' && value.includes(',')) {
183
+ filters.push({
184
+ field,
185
+ operator: 'IN',
186
+ data: value.split(',').map(val => val.trim()),
187
+ });
188
+ }
189
+ else {
190
+ filters.push({
191
+ field,
192
+ operator: externalFilter.defaultOperator || 'CONTAIN',
193
+ data: value,
194
+ });
195
+ }
196
+ }
197
+ else if (externalFilter.type === 'boolean') {
198
+ filters.push({
199
+ field,
200
+ operator: 'EQUAL',
201
+ data: value === true || value === 1 || value === 'true' || value === '1',
202
+ });
203
+ }
204
+ else if (externalFilter.type === 'daterange') {
205
+ if (typeof value === 'object' && 'from' in value && 'to' in value) {
206
+ if (value?.from) {
207
+ filters.push({
208
+ field,
209
+ operator: 'GREATER_OR_EQUAL',
210
+ data: DateUtilities.begin(value?.from).toISOString(),
211
+ });
212
+ }
213
+ if (value?.to) {
214
+ filters.push({
215
+ field,
216
+ operator: 'LESS_THAN',
217
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
218
+ });
219
+ }
220
+ }
221
+ }
222
+ else if (externalFilter.type === 'date' || externalFilter.type === 'datetime') {
223
+ if (typeof value === 'object' && 'from' in value && 'to' in value) {
224
+ if (value?.from) {
225
+ filters.push({
226
+ field,
227
+ operator: 'GREATER_OR_EQUAL',
228
+ data: DateUtilities.begin(value?.from).toISOString(),
229
+ });
230
+ }
231
+ if (value?.to) {
232
+ filters.push({
233
+ field,
234
+ operator: 'LESS_THAN',
235
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
236
+ });
237
+ }
238
+ }
239
+ else if (DateUtilities.isDate(value)) {
240
+ if (externalFilter.type === 'date') {
241
+ if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
242
+ filters.push({
243
+ field,
244
+ operator: 'GREATER_OR_EQUAL',
245
+ data: DateUtilities.begin(value).toISOString(),
246
+ });
247
+ }
248
+ if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
249
+ filters.push({
250
+ field,
251
+ operator: 'LESS_THAN',
252
+ data: DateUtilities.begin(DateUtilities.addDays(value, 1)).toISOString(),
253
+ });
254
+ }
255
+ }
256
+ else {
257
+ if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
258
+ filters.push({
259
+ field,
260
+ operator: 'GREATER_OR_EQUAL',
261
+ data: new Date(value).toISOString(),
262
+ });
263
+ }
264
+ if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
265
+ filters.push({
266
+ field,
267
+ operator: 'LESS_OR_EQUAL',
268
+ data: new Date(value).toISOString(),
269
+ });
270
+ }
271
+ }
272
+ }
273
+ }
274
+ else {
275
+ if (Array.isArray(value)) {
276
+ if (value.length) {
277
+ filters.push({
278
+ field,
279
+ operator: 'IN',
280
+ data: value,
281
+ });
282
+ }
283
+ }
284
+ else if (typeof value === 'object' && 'from' in value && 'to' in value) {
285
+ if (value?.from) {
286
+ filters.push({
287
+ field,
288
+ operator: 'GREATER_OR_EQUAL',
289
+ data: DateUtilities.begin(value?.from).toISOString(),
290
+ });
291
+ }
292
+ if (value?.to) {
293
+ filters.push({
294
+ field,
295
+ operator: 'LESS_THAN',
296
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
297
+ });
298
+ }
299
+ }
300
+ else {
301
+ filters.push({
302
+ field,
303
+ operator: externalFilter.defaultOperator || 'EQUAL',
304
+ data: value,
305
+ });
306
+ }
307
+ }
308
+ }
309
+ }
310
+ // Xử lý column filter
311
+ for (const column of columns || []) {
312
+ const value = rawColumnFilter?.[column.field];
313
+ const field = fieldMapping?.[column.field] || column.field;
314
+ const operator = columnOperator?.[column.field] || column.filter?.operator?.default;
315
+ // Nếu có giá trị thì mới xử lý filter
316
+ if (value !== undefined && value !== null && value !== '') {
317
+ if (column.type === 'string') {
318
+ filters.push({
319
+ field,
320
+ operator: operator || 'CONTAIN',
321
+ data: value,
322
+ });
323
+ }
324
+ else if (column.type === 'boolean') {
325
+ filters.push({
326
+ field,
327
+ operator: 'EQUAL',
328
+ data: value === true || value === 1 || value === 'true' || value === '1',
329
+ });
330
+ }
331
+ else if (column.type === 'date' || column.type === 'datetime') {
332
+ if (typeof value === 'object' && 'from' in value && 'to' in value) {
333
+ if (value?.from && value?.to) {
334
+ filters.push({
335
+ field,
336
+ operator: 'BETWEEN',
337
+ data: {
338
+ from: DateUtilities.begin(value?.from).toISOString(),
339
+ to: DateUtilities.end(value?.to).toISOString(),
340
+ },
341
+ });
342
+ }
343
+ else if (value?.from) {
344
+ filters.push({
345
+ field,
346
+ operator: 'GREATER_OR_EQUAL',
347
+ data: DateUtilities.begin(value?.from).toISOString(),
348
+ });
349
+ }
350
+ else if (value?.to) {
351
+ filters.push({
352
+ field,
353
+ operator: 'LESS_THAN',
354
+ data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
355
+ });
356
+ }
357
+ }
358
+ else {
359
+ if (DateUtilities.isDate(value)) {
360
+ filters.push({
361
+ field,
362
+ operator: 'BETWEEN',
363
+ data: {
364
+ from: DateUtilities.begin(value).toISOString(),
365
+ to: DateUtilities.end(value).toISOString(),
366
+ },
367
+ });
368
+ }
369
+ }
370
+ }
371
+ else {
372
+ if (Array.isArray(value)) {
373
+ if (value.length) {
374
+ filters.push({
375
+ field,
376
+ operator: 'IN',
377
+ data: value,
378
+ });
379
+ }
380
+ }
381
+ else {
382
+ filters.push({
383
+ field,
384
+ operator: operator || 'EQUAL',
385
+ data: value,
386
+ });
387
+ }
388
+ }
389
+ }
390
+ }
391
+ // Xử lý orders
392
+ if (orderBy && orderDirection) {
393
+ req.orders.push({
394
+ field: orderBy,
395
+ direction: orderDirection,
396
+ });
397
+ }
398
+ return req;
399
+ };
400
+
166
401
  /* eslint-disable @typescript-eslint/no-explicit-any */
167
402
  class ColumnTitleComponent {
168
403
  column = input.required();
@@ -235,8 +470,6 @@ class ColumnFilterComponent {
235
470
  });
236
471
  // Chỉ các Operator value cho phép — truyền vào <sd-operator [operators]>.
237
472
  operatorValues = computed(() => this.operators().map(o => o.value));
238
- // Margin wrapper operator: số + đang chọn operator → thêm mb-4 (canh baseline với sd-input-number).
239
- operatorWrapperClass = computed(() => (this.column()?.type === 'number' && this.operator() ? 'mb-4 mr-2' : 'mr-2'));
240
473
  // Items cho sd-select khi column type = values / lazy-values
241
474
  items = computed(() => {
242
475
  const col = this.column();
@@ -279,11 +512,11 @@ class ColumnFilterComponent {
279
512
  // Blur input: commit giá trị KHÔNG trigger reload.
280
513
  onFilterCommit = () => this.filterCommit.emit();
281
514
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ColumnFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
282
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ColumnFilterComponent, isStandalone: true, selector: "column-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, columnFilter: { classPropertyName: "columnFilter", publicName: "columnFilter", isSignal: true, isRequired: false, transformFunction: null }, cacheValues: { classPropertyName: "cacheValues", publicName: "cacheValues", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operator: "operatorChange", filterChange: "filterChange", filterCommit: "filterCommit" }, ngImport: i0, template: "@let _column = column();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-end\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <div class=\"d-flex align-items-center\" [ngClass]=\"operatorWrapperClass()\">\n <sd-operator [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n </div>\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container\n *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container\n *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdBadge, selector: "sd-badge", inputs: ["type", "color", "primary", "secondary", "success", "info", "warning", "error", "fontSet", "title", "description", "tooltip", "icon", "size"], outputs: ["click"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
515
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ColumnFilterComponent, isStandalone: true, selector: "column-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, column: { classPropertyName: "column", publicName: "column", isSignal: true, isRequired: true, transformFunction: null }, columnFilter: { classPropertyName: "columnFilter", publicName: "columnFilter", isSignal: true, isRequired: false, transformFunction: null }, cacheValues: { classPropertyName: "cacheValues", publicName: "cacheValues", isSignal: true, isRequired: false, transformFunction: null }, value: { classPropertyName: "value", publicName: "value", isSignal: true, isRequired: false, transformFunction: null }, operator: { classPropertyName: "operator", publicName: "operator", isSignal: true, isRequired: false, transformFunction: null }, isMobile: { classPropertyName: "isMobile", publicName: "isMobile", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { operator: "operatorChange", filterChange: "filterChange", filterCommit: "filterCommit" }, ngImport: i0, template: "@let _column = column();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-center\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <sd-operator class=\"mr-4\" [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "component", type: SdOperator, selector: "sd-operator", inputs: ["model", "operators", "disabled", "autoId"], outputs: ["modelChange"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "component", type: SdBadge, selector: "sd-badge", inputs: ["type", "color", "primary", "secondary", "success", "info", "warning", "error", "fontSet", "title", "description", "tooltip", "icon", "size"], outputs: ["click"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
283
516
  }
284
517
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ColumnFilterComponent, decorators: [{
285
518
  type: Component,
286
- args: [{ selector: 'column-filter', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, SdOperator, SdInput, SdInputNumber, SdSelect, SdDate, SdDateRange, SdBadge], template: "@let _column = column();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-end\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <div class=\"d-flex align-items-center\" [ngClass]=\"operatorWrapperClass()\">\n <sd-operator [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n </div>\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container\n *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container\n *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"] }]
519
+ args: [{ selector: 'column-filter', changeDetection: ChangeDetectionStrategy.OnPush, standalone: true, imports: [CommonModule, SdOperator, SdInput, SdInputNumber, SdSelect, SdDate, SdDateRange, SdBadge], template: "@let _column = column();\n@let _columnFilter = columnFilter();\n@let _autoId = autoId();\n@let _templateRef = templateRef();\n@let _operators = operators();\n@let _items = items();\n@let _label = label();\n@let _context = { columnFilter: _columnFilter, autoId: _autoId };\n\n<div class=\"d-flex c-inline-column align-items-center\" style=\"width: 100%; max-width: 100%\">\n @if (\n _column.type === 'string' ||\n _column.type === 'number' ||\n _column.type === 'boolean' ||\n _column.type === 'values' ||\n _column.type === 'lazy-values' ||\n _column.type === 'date' ||\n _column.type === 'datetime' ||\n _column.type === 'time'\n ) {\n <!-- Operator filter -->\n @if (_operators.length) {\n <sd-operator class=\"mr-4\" [(model)]=\"operator\" [operators]=\"operatorValues()\" />\n }\n\n <!-- N\u1EBFu filter nh\u1EADn v\u00E0o template -->\n @if (_templateRef) {\n <ng-container *ngTemplateOutlet=\"_templateRef; context: _context\"></ng-container>\n }\n <!-- M\u1EB7c \u0111\u1ECBnh theo type v\u00E0 gi\u00E1 tr\u1ECB values defined c\u1EE7a column -->\n @else {\n @if (_column.type === 'string') {\n <sd-input\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n type=\"text\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input>\n } @else if (_column.type === 'number') {\n @if (!_column.filter?.type) {\n <sd-input-number\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (keyupEnter)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-input-number>\n } @else if (_column.filter?.type === 'split-number') {\n <ng-container *ngTemplateOutlet=\"splitNumberTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n } @else if (_column.type === 'boolean') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"[\n { value: '1', display: _column.option?.displayOnTrue || 'True' },\n { value: '0', display: _column.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n <ng-template sdItemDef let-item=\"item\">\n @if (item.value === '1') {\n <sd-badge color=\"success\" [title]=\"_column.option?.displayOnTrue || 'True'\"> </sd-badge>\n } @else {\n <sd-badge color=\"error\" [title]=\"_column.option?.displayOnFalse || 'False'\"> </sd-badge>\n }\n </ng-template>\n </sd-select>\n } @else if (_column.type === 'values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'lazy-values') {\n <sd-select\n minWidthPanel=\"200px\"\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n [style.width]=\"'100%'\"\n size=\"sm\"\n [label]=\"_label\"\n [items]=\"_items\"\n [valueField]=\"_column.option.valueField\"\n [displayField]=\"_column.option.displayField\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n [multiple]=\"_column.option.selection === 'MULTIPLE'\"\n hideInlineError>\n </sd-select>\n } @else if (_column.type === 'date' || _column.type === 'datetime' || _column.type === 'time') {\n @if (!_column.filter?.type || _column.filter?.type === 'daterange') {\n <sd-date-range\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date-range>\n } @else if (_column.filter?.type === 'date') {\n <sd-date\n [autoId]=\"_autoId\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[_column.field]\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"_column.filter?.disabled\"\n hideInlineError>\n </sd-date>\n } @else if (_column.filter?.type === 'split-date') {\n <ng-container *ngTemplateOutlet=\"splitDateTpl; context: { field: _column.field, disabled: _column.filter?.disabled }\">\n </ng-container>\n }\n }\n }\n } @else {\n <sd-input [autoId]=\"_autoId\" style=\"flex: 1 1 0%; min-width: 0\" type=\"text\" size=\"sm\" disabled></sd-input>\n }\n</div>\n\n<!-- ================================================ -->\n<!-- Split from/to templates (\u0111\u01B0a xu\u1ED1ng cu\u1ED1i file) -->\n<!-- ================================================ -->\n<ng-template #splitNumberTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-input-number\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n <div class=\"mx-4\">-</div>\n <sd-input-number\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n (sdBlur)=\"onFilterCommit()\"\n (cleared)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-input-number>\n </div>\n</ng-template>\n\n<ng-template #splitDateTpl let-field=\"field\" let-disabled=\"disabled\">\n <div class=\"d-flex align-items-center\" style=\"flex: 1 1 0%; min-width: 0\">\n <sd-date\n [autoId]=\"_autoId + '-from'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [label]=\"_label\"\n [(model)]=\"_columnFilter[field].from\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n <div class=\"mx-4\">-</div>\n <sd-date\n [autoId]=\"_autoId + '-to'\"\n style=\"flex: 1 1 0%; min-width: 0\"\n type=\"date\"\n size=\"sm\"\n [(model)]=\"_columnFilter[field].to\"\n (sdChange)=\"onFilterChange()\"\n [disabled]=\"disabled\"\n hideInlineError>\n </sd-date>\n </div>\n</ng-template>\n", styles: [":host ::ng-deep .c-inline-column .mat-mdc-text-field-wrapper{background-color:#fff}\n"] }]
287
520
  }], ctorParameters: () => [] });
288
521
 
289
522
  // Đối với filter loại values, items có thể là 1 mảng, có thể là 1 hàm, do đó cần xử lý nếu là hàm có thể xử lý gọi API
@@ -336,7 +569,7 @@ const getChildrenFromData = (data, option) => {
336
569
  * mặc định nhánh chưa nạp).
337
570
  */
338
571
  const resolveDefaultExpanded = (level, option) => {
339
- const def = option?.loadType === 'static' ? option.defaultExpanded ?? false : false;
572
+ const def = option?.loadType === 'static' ? (option.defaultExpanded ?? false) : false;
340
573
  if (def === true)
341
574
  return true;
342
575
  if (def === false)
@@ -392,6 +625,48 @@ const subtreeMatches = (data, predicate, option, visited = new Set()) => {
392
625
  * bị mất khi từ khoá search đổi. Đây là bước "prune" của tính năng search-con.
393
626
  */
394
627
  const filterMatchingChildren = (children, predicate, option) => children.filter(child => subtreeMatches(child, predicate, option));
628
+ const getVisibleChildrenData = (data, option, predicate) => {
629
+ const children = getChildrenFromData(data, option);
630
+ return predicate ? filterMatchingChildren(children, predicate, option) : children;
631
+ };
632
+ const saveTreeExpandState = (rows, expandState) => {
633
+ for (const row of rows) {
634
+ if (row.meta.tree?.isExpanded) {
635
+ expandState.set(row.meta.id, true);
636
+ }
637
+ const children = row.meta.tree?.childItems;
638
+ if (children?.length) {
639
+ saveTreeExpandState(children, expandState);
640
+ }
641
+ }
642
+ };
643
+ const clearTreeChildCache = (rows) => {
644
+ for (const row of rows) {
645
+ const children = row.meta.tree?.childItems;
646
+ if (children?.length) {
647
+ clearTreeChildCache(children);
648
+ row.meta.tree.childItems = undefined;
649
+ }
650
+ }
651
+ };
652
+ const initTreeMeta = (rows, option, args = {}) => {
653
+ const { expandState, treeSearchPredicate, level = 0, parentId } = args;
654
+ const searchActive = !!treeSearchPredicate;
655
+ for (const row of rows) {
656
+ const saved = expandState?.get(row.meta.id);
657
+ const hasChildren = searchActive
658
+ ? getVisibleChildrenData(row.data, option, treeSearchPredicate).length > 0
659
+ : resolveHasChildren(row, option);
660
+ row.meta.tree = {
661
+ ...row.meta.tree,
662
+ level,
663
+ parentId,
664
+ hasChildren,
665
+ isExpanded: searchActive ? hasChildren : (saved ?? resolveDefaultExpanded(level, option)),
666
+ isExpanding: false,
667
+ };
668
+ }
669
+ };
395
670
  const flattenTree = (roots, option, visited = new Set()) => {
396
671
  if (!option)
397
672
  return roots;
@@ -618,7 +893,7 @@ class ExternalFilterComponent {
618
893
  });
619
894
  };
620
895
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ExternalFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
621
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ExternalFilterComponent, isStandalone: true, selector: "external-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, externalFilters: { classPropertyName: "externalFilters", publicName: "externalFilters", isSignal: true, isRequired: false, transformFunction: null }, filterRegister: { classPropertyName: "filterRegister", publicName: "filterRegister", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let _filter = filter();\n@let _autoId = autoId();\n@let _items = filterItems();\n@let _externalFilter = externalFilter();\n@let _inlineExternal = inlineExternal();\n@let _filtered = filtered();\n@let _col = col();\n\n@if (!_filter?.disabled) {\n @if (!isMobileOrTablet && _items.length) {\n <sd-section\n [hideHeader]=\"_filter?.hideExternalFilterToolbar\"\n icon=\"filter_alt\"\n iconColor=\"secondary\"\n [title]=\"'core.component.table.filter' | translate\"\n [collapsible]=\"_filter?.collapsible ?? _filter?.collapsable ?? true\">\n <div class=\"d-flex mr-4\" sdHeaderRight>\n <sd-button\n [autoId]=\"_autoId + 'clear'\"\n [disabled]=\"!_filtered\"\n type=\"link\"\n prefixIcon=\"cleaning_services\"\n [tooltip]=\"'core.component.table.clear-selection' | translate\"\n (click)=\"$event.stopPropagation(); clearFilter($event)\"></sd-button>\n <sd-button\n [autoId]=\"_autoId + 'setting'\"\n type=\"link\"\n prefixIcon=\"settings\"\n [tooltip]=\"'core.component.table.customize' | translate\"\n [matMenuTriggerFor]=\"menu\"\n (click)=\"$event.stopPropagation()\"></sd-button>\n <mat-menu #menu=\"matMenu\" class=\"sd-custom-panel-filter-configuration\" [attr.data-autoid]=\"_autoId + 'setting-menu'\">\n @for (externalFilter of _items; track externalFilter.field) {\n <button\n [attr.data-autoid]=\"_autoId + 'setting-' + externalFilter.field\"\n mat-menu-item\n (click)=\"onCheckboxChange($event, externalFilter)\"\n [disabled]=\"externalFilter.required\">\n <sd-checkbox\n [autoId]=\"_autoId + 'setting-' + externalFilter.field + '-checkbox'\"\n [label]=\"externalFilter.title\"\n [model]=\"_inlineExternal[externalFilter.field]\"\n [disabled]=\"externalFilter.required\" />\n </button>\n }\n </mat-menu>\n </div>\n <div class=\"row mx-0\" style=\"row-gap: 16px\">\n @for (item of _items; track item.field) {\n @if (_inlineExternal[item.field]) {\n <div class=\"col-md-3 col-sm-6 px-8\" [class.col-lg-2]=\"_col === 2\" [class.col-lg-3]=\"_col === 3\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'AUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'MULTIPLEAUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'lazy-values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { form: form, externalFilter: _externalFilter, autoId: _autoId + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n }\n </div>\n @if (_filter?.manualFilter) {\n <div class=\"d-flex justify-content-end mt-16 px-8\">\n <sd-button\n [autoId]=\"_autoId + 'submit'\"\n [disabled]=\"form.invalid\"\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.table.search' | translate\"\n prefixIcon=\"filter_alt\"\n (click)=\"onSubmit()\">\n </sd-button>\n </div>\n }\n </sd-section>\n }\n}\n", styles: [":host{display:block;padding-left:0;padding-right:0}::ng-deep .sd-custom-panel-filter-configuration{width:240px;max-height:550px;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsible", "collapsable", "hideHeader", "noPaddingBody"], outputs: ["collapsedChange"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange$1, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { 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: "pipe", type: FilterValuesPipe, name: "filterValues" }, { kind: "component", type: SdCheckbox, selector: "sd-checkbox", inputs: ["autoId", "name", "form", "label", "color", "disabled", "viewed", "inlineError", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
896
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: ExternalFilterComponent, isStandalone: true, selector: "external-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, externalFilters: { classPropertyName: "externalFilters", publicName: "externalFilters", isSignal: true, isRequired: false, transformFunction: null }, filterRegister: { classPropertyName: "filterRegister", publicName: "filterRegister", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "@let _filter = filter();\n@let _autoId = autoId();\n@let _filterItems = filterItems();\n@let _externalFilter = externalFilter();\n@let _inlineExternal = inlineExternal();\n@let _filtered = filtered();\n@let _col = col();\n\n@if (!_filter?.disabled) {\n @if (!isMobileOrTablet && _filterItems.length) {\n <sd-section\n [hideHeader]=\"_filter?.hideExternalFilterToolbar\"\n icon=\"filter_alt\"\n iconColor=\"secondary\"\n [title]=\"'core.component.table.filter' | translate\"\n [collapsible]=\"_filter?.collapsible ?? _filter?.collapsable ?? true\">\n <div class=\"d-flex mr-4\" sdHeaderRight>\n <sd-button\n [autoId]=\"_autoId + 'clear'\"\n [disabled]=\"!_filtered\"\n type=\"link\"\n prefixIcon=\"cleaning_services\"\n [tooltip]=\"'core.component.table.clear-selection' | translate\"\n (click)=\"$event.stopPropagation(); clearFilter($event)\"></sd-button>\n <sd-button\n [autoId]=\"_autoId + 'setting'\"\n type=\"link\"\n prefixIcon=\"settings\"\n [tooltip]=\"'core.component.table.customize' | translate\"\n [matMenuTriggerFor]=\"menu\"\n (click)=\"$event.stopPropagation()\"></sd-button>\n <mat-menu #menu=\"matMenu\" class=\"sd-custom-panel-filter-configuration\" [attr.data-autoid]=\"_autoId + 'setting-menu'\">\n @for (externalFilter of _filterItems; track externalFilter.field) {\n <button\n [attr.data-autoid]=\"_autoId + 'setting-' + externalFilter.field\"\n mat-menu-item\n (click)=\"onCheckboxChange($event, externalFilter)\"\n [disabled]=\"externalFilter.required\">\n <sd-checkbox\n [autoId]=\"_autoId + 'setting-' + externalFilter.field + '-checkbox'\"\n [label]=\"externalFilter.title\"\n [model]=\"_inlineExternal[externalFilter.field]\"\n [disabled]=\"externalFilter.required\" />\n </button>\n }\n </mat-menu>\n </div>\n <div class=\"row mx-0\" style=\"row-gap: 16px\">\n @for (item of _filterItems; track item.field) {\n @if (_inlineExternal[item.field]) {\n <div class=\"col-md-3 col-sm-6 px-8\" [class.col-lg-2]=\"_col === 2\" [class.col-lg-3]=\"_col === 3\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'AUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'MULTIPLEAUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'lazy-values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { form: form, externalFilter: _externalFilter, autoId: _autoId + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n }\n </div>\n @if (_filter?.manualFilter) {\n <div class=\"d-flex justify-content-end mt-16 px-8\">\n <sd-button\n [autoId]=\"_autoId + 'submit'\"\n [disabled]=\"form.invalid\"\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.table.search' | translate\"\n prefixIcon=\"filter_alt\"\n (click)=\"onSubmit()\">\n </sd-button>\n </div>\n }\n </sd-section>\n }\n}\n", styles: [":host{display:block;padding-left:0;padding-right:0}::ng-deep .sd-custom-panel-filter-configuration{width:240px;max-height:550px;overflow-y:auto}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { kind: "ngmodule", type: MatCheckboxModule }, { kind: "component", type: SdSection, selector: "sd-section", inputs: ["title", "subTitle", "icon", "iconColor", "collapsed", "collapsible", "collapsable", "hideHeader", "noPaddingBody"], outputs: ["collapsedChange"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange$1, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { 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: "pipe", type: FilterValuesPipe, name: "filterValues" }, { kind: "component", type: SdCheckbox, selector: "sd-checkbox", inputs: ["autoId", "name", "form", "label", "color", "disabled", "viewed", "inlineError", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
622
897
  }
623
898
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ExternalFilterComponent, decorators: [{
624
899
  type: Component,
@@ -637,7 +912,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
637
912
  FilterValuesPipe,
638
913
  SdCheckbox,
639
914
  TranslatePipe,
640
- ], template: "@let _filter = filter();\n@let _autoId = autoId();\n@let _items = filterItems();\n@let _externalFilter = externalFilter();\n@let _inlineExternal = inlineExternal();\n@let _filtered = filtered();\n@let _col = col();\n\n@if (!_filter?.disabled) {\n @if (!isMobileOrTablet && _items.length) {\n <sd-section\n [hideHeader]=\"_filter?.hideExternalFilterToolbar\"\n icon=\"filter_alt\"\n iconColor=\"secondary\"\n [title]=\"'core.component.table.filter' | translate\"\n [collapsible]=\"_filter?.collapsible ?? _filter?.collapsable ?? true\">\n <div class=\"d-flex mr-4\" sdHeaderRight>\n <sd-button\n [autoId]=\"_autoId + 'clear'\"\n [disabled]=\"!_filtered\"\n type=\"link\"\n prefixIcon=\"cleaning_services\"\n [tooltip]=\"'core.component.table.clear-selection' | translate\"\n (click)=\"$event.stopPropagation(); clearFilter($event)\"></sd-button>\n <sd-button\n [autoId]=\"_autoId + 'setting'\"\n type=\"link\"\n prefixIcon=\"settings\"\n [tooltip]=\"'core.component.table.customize' | translate\"\n [matMenuTriggerFor]=\"menu\"\n (click)=\"$event.stopPropagation()\"></sd-button>\n <mat-menu #menu=\"matMenu\" class=\"sd-custom-panel-filter-configuration\" [attr.data-autoid]=\"_autoId + 'setting-menu'\">\n @for (externalFilter of _items; track externalFilter.field) {\n <button\n [attr.data-autoid]=\"_autoId + 'setting-' + externalFilter.field\"\n mat-menu-item\n (click)=\"onCheckboxChange($event, externalFilter)\"\n [disabled]=\"externalFilter.required\">\n <sd-checkbox\n [autoId]=\"_autoId + 'setting-' + externalFilter.field + '-checkbox'\"\n [label]=\"externalFilter.title\"\n [model]=\"_inlineExternal[externalFilter.field]\"\n [disabled]=\"externalFilter.required\" />\n </button>\n }\n </mat-menu>\n </div>\n <div class=\"row mx-0\" style=\"row-gap: 16px\">\n @for (item of _items; track item.field) {\n @if (_inlineExternal[item.field]) {\n <div class=\"col-md-3 col-sm-6 px-8\" [class.col-lg-2]=\"_col === 2\" [class.col-lg-3]=\"_col === 3\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'AUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'MULTIPLEAUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'lazy-values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { form: form, externalFilter: _externalFilter, autoId: _autoId + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n }\n </div>\n @if (_filter?.manualFilter) {\n <div class=\"d-flex justify-content-end mt-16 px-8\">\n <sd-button\n [autoId]=\"_autoId + 'submit'\"\n [disabled]=\"form.invalid\"\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.table.search' | translate\"\n prefixIcon=\"filter_alt\"\n (click)=\"onSubmit()\">\n </sd-button>\n </div>\n }\n </sd-section>\n }\n}\n", styles: [":host{display:block;padding-left:0;padding-right:0}::ng-deep .sd-custom-panel-filter-configuration{width:240px;max-height:550px;overflow-y:auto}\n"] }]
915
+ ], template: "@let _filter = filter();\n@let _autoId = autoId();\n@let _filterItems = filterItems();\n@let _externalFilter = externalFilter();\n@let _inlineExternal = inlineExternal();\n@let _filtered = filtered();\n@let _col = col();\n\n@if (!_filter?.disabled) {\n @if (!isMobileOrTablet && _filterItems.length) {\n <sd-section\n [hideHeader]=\"_filter?.hideExternalFilterToolbar\"\n icon=\"filter_alt\"\n iconColor=\"secondary\"\n [title]=\"'core.component.table.filter' | translate\"\n [collapsible]=\"_filter?.collapsible ?? _filter?.collapsable ?? true\">\n <div class=\"d-flex mr-4\" sdHeaderRight>\n <sd-button\n [autoId]=\"_autoId + 'clear'\"\n [disabled]=\"!_filtered\"\n type=\"link\"\n prefixIcon=\"cleaning_services\"\n [tooltip]=\"'core.component.table.clear-selection' | translate\"\n (click)=\"$event.stopPropagation(); clearFilter($event)\"></sd-button>\n <sd-button\n [autoId]=\"_autoId + 'setting'\"\n type=\"link\"\n prefixIcon=\"settings\"\n [tooltip]=\"'core.component.table.customize' | translate\"\n [matMenuTriggerFor]=\"menu\"\n (click)=\"$event.stopPropagation()\"></sd-button>\n <mat-menu #menu=\"matMenu\" class=\"sd-custom-panel-filter-configuration\" [attr.data-autoid]=\"_autoId + 'setting-menu'\">\n @for (externalFilter of _filterItems; track externalFilter.field) {\n <button\n [attr.data-autoid]=\"_autoId + 'setting-' + externalFilter.field\"\n mat-menu-item\n (click)=\"onCheckboxChange($event, externalFilter)\"\n [disabled]=\"externalFilter.required\">\n <sd-checkbox\n [autoId]=\"_autoId + 'setting-' + externalFilter.field + '-checkbox'\"\n [label]=\"externalFilter.title\"\n [model]=\"_inlineExternal[externalFilter.field]\"\n [disabled]=\"externalFilter.required\" />\n </button>\n }\n </mat-menu>\n </div>\n <div class=\"row mx-0\" style=\"row-gap: 16px\">\n @for (item of _filterItems; track item.field) {\n @if (_inlineExternal[item.field]) {\n <div class=\"col-md-3 col-sm-6 px-8\" [class.col-lg-2]=\"_col === 2\" [class.col-lg-3]=\"_col === 3\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (keyupEnter)=\"onKeyupEnter()\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n [form]=\"form\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'AUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.option.selection === 'MULTIPLEAUTOCOMPLETE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'lazy-values') {\n @if (item.option.selection === 'MULTIPLE') {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n multiple\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else {\n <sd-select\n [autoId]=\"_autoId + item.field\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_externalFilter[item.field]\"\n (sdChange)=\"onFilter(item)\"\n appearance=\"outline\"\n [form]=\"form\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n }\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { form: form, externalFilter: _externalFilter, autoId: _autoId + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n }\n </div>\n @if (_filter?.manualFilter) {\n <div class=\"d-flex justify-content-end mt-16 px-8\">\n <sd-button\n [autoId]=\"_autoId + 'submit'\"\n [disabled]=\"form.invalid\"\n type=\"fill\"\n color=\"primary\"\n [title]=\"'core.component.table.search' | translate\"\n prefixIcon=\"filter_alt\"\n (click)=\"onSubmit()\">\n </sd-button>\n </div>\n }\n </sd-section>\n }\n}\n", styles: [":host{display:block;padding-left:0;padding-right:0}::ng-deep .sd-custom-panel-filter-configuration{width:240px;max-height:550px;overflow-y:auto}\n"] }]
641
916
  }], ctorParameters: () => [] });
642
917
 
643
918
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -714,7 +989,7 @@ class MobileFilterComponent {
714
989
  this.drawer().close();
715
990
  };
716
991
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: MobileFilterComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
717
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: MobileFilterComponent, isStandalone: true, selector: "mobile-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, externalFilters: { classPropertyName: "externalFilters", publicName: "externalFilters", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, filterDefs: { classPropertyName: "filterDefs", publicName: "filterDefs", isSignal: true, isRequired: false, transformFunction: null }, filterRegister: { classPropertyName: "filterRegister", publicName: "filterRegister", isSignal: true, isRequired: false, transformFunction: null }, cacheValues: { classPropertyName: "cacheValues", publicName: "cacheValues", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "drawer", first: true, predicate: SdSideDrawer, descendants: true, isSignal: true }], ngImport: i0, template: "@let _autoId = autoId();\n@let _externalFilters = externalFilterItems();\n@let _columns = columnItems();\n@let _filterDefs = filterDefItems();\n@let _workingColumnFilter = workingColumnFilter();\n@let _workingColumnOperator = workingColumnOperator();\n@let _workingExternalFilter = workingExternalFilter();\n@let _cacheValues = cacheValues();\n\n<sd-side-drawer\n #drawer\n [title]=\"'core.component.table.filter' | translate\"\n width=\"90vw\">\n <div class=\"mobile-filter-body\">\n <!-- ============================ -->\n <!-- 1. External filters (inline) -->\n <!-- ============================ -->\n @if (_externalFilters.length) {\n <section class=\"mobile-filter-section\">\n @for (item of _externalFilters; track item.field) {\n <div class=\"mobile-filter-row\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE' || item.option.selection === 'MULTIPLEAUTOCOMPLETE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'lazy-values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { externalFilter: _workingExternalFilter, autoId: _autoId + 'ext-' + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 2. Custom filterDef templates -->\n <!-- ============================ -->\n @if (_filterDefs.length) {\n <section class=\"mobile-filter-section\">\n @for (def of _filterDefs; track def.sdTableFilterDef()) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'def-' + def.sdTableFilterDef()\">\n <ng-container\n *ngTemplateOutlet=\"def.templateRef!; context: { externalFilter: _workingExternalFilter }\">\n </ng-container>\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 3. Column filters -->\n <!-- ============================ -->\n @if (_columns.length) {\n <section class=\"mobile-filter-section\">\n @for (column of _columns; track column.field) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'col-' + column.field\">\n <column-filter\n [autoId]=\"_autoId + 'col'\"\n [column]=\"column\"\n [columnFilter]=\"_workingColumnFilter\"\n [operator]=\"$any(_workingColumnOperator[column.field])\"\n (operatorChange)=\"_workingColumnOperator[column.field] = $event!\"\n [cacheValues]=\"_cacheValues\"\n isMobile>\n </column-filter>\n </div>\n }\n </section>\n }\n </div>\n\n <ng-container sdFooter>\n <div class=\"mobile-filter-footer\">\n <sd-button\n [autoId]=\"_autoId + 'cancel'\"\n class=\"mobile-filter-action\"\n (click)=\"onCancel()\"\n [title]=\"'core.confirm.no' | translate\"\n type=\"link\"\n color=\"secondary\">\n </sd-button>\n <sd-button\n [autoId]=\"_autoId + 'apply'\"\n class=\"mobile-filter-action\"\n (click)=\"onApply()\"\n [title]=\"'core.confirm.yes' | translate\"\n type=\"fill\"\n color=\"primary\">\n </sd-button>\n </div>\n </ng-container>\n</sd-side-drawer>\n", styles: [".mobile-filter-body{display:flex;flex-direction:column;gap:16px;padding:8px 0}.mobile-filter-section{display:flex;flex-direction:column;gap:12px}.mobile-filter-row{display:flex;flex-direction:column}.mobile-filter-footer{display:flex;gap:8px;width:100%}.mobile-filter-action{flex:1 1 50%}.mobile-filter-action ::ng-deep button{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: SdSideDrawer, selector: "sd-side-drawer", inputs: ["title", "width", "hideClose", "disableBackdropClose", "drawerClass", "autoId"], outputs: ["sdClosed"] }, { 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: ColumnFilterComponent, selector: "column-filter", inputs: ["autoId", "column", "columnFilter", "cacheValues", "value", "operator", "isMobile"], outputs: ["operatorChange", "filterChange", "filterCommit"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange$1, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: FilterValuesPipe, name: "filterValues" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
992
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: MobileFilterComponent, isStandalone: true, selector: "mobile-filter", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, filter: { classPropertyName: "filter", publicName: "filter", isSignal: true, isRequired: false, transformFunction: null }, externalFilters: { classPropertyName: "externalFilters", publicName: "externalFilters", isSignal: true, isRequired: false, transformFunction: null }, columns: { classPropertyName: "columns", publicName: "columns", isSignal: true, isRequired: false, transformFunction: null }, filterDefs: { classPropertyName: "filterDefs", publicName: "filterDefs", isSignal: true, isRequired: false, transformFunction: null }, filterRegister: { classPropertyName: "filterRegister", publicName: "filterRegister", isSignal: true, isRequired: false, transformFunction: null }, cacheValues: { classPropertyName: "cacheValues", publicName: "cacheValues", isSignal: true, isRequired: false, transformFunction: null } }, viewQueries: [{ propertyName: "drawer", first: true, predicate: SdSideDrawer, descendants: true, isSignal: true }], ngImport: i0, template: "@let _autoId = autoId();\n@let _externalFilterItems = externalFilterItems();\n@let _columnItems = columnItems();\n@let _filterDefItems = filterDefItems();\n@let _workingColumnFilter = workingColumnFilter();\n@let _workingColumnOperator = workingColumnOperator();\n@let _workingExternalFilter = workingExternalFilter();\n@let _cacheValues = cacheValues();\n\n<sd-side-drawer\n #drawer\n [title]=\"'core.component.table.filter' | translate\"\n width=\"90vw\">\n <div class=\"mobile-filter-body\">\n <!-- ============================ -->\n <!-- 1. External filters (inline) -->\n <!-- ============================ -->\n @if (_externalFilterItems.length) {\n <section class=\"mobile-filter-section\">\n @for (item of _externalFilterItems; track item.field) {\n <div class=\"mobile-filter-row\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE' || item.option.selection === 'MULTIPLEAUTOCOMPLETE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'lazy-values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { externalFilter: _workingExternalFilter, autoId: _autoId + 'ext-' + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 2. Custom filterDef templates -->\n <!-- ============================ -->\n @if (_filterDefItems.length) {\n <section class=\"mobile-filter-section\">\n @for (def of _filterDefItems; track def.sdTableFilterDef()) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'def-' + def.sdTableFilterDef()\">\n <ng-container\n *ngTemplateOutlet=\"def.templateRef!; context: { externalFilter: _workingExternalFilter }\">\n </ng-container>\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 3. Column filters -->\n <!-- ============================ -->\n @if (_columnItems.length) {\n <section class=\"mobile-filter-section\">\n @for (column of _columnItems; track column.field) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'col-' + column.field\">\n <column-filter\n [autoId]=\"_autoId + 'col'\"\n [column]=\"column\"\n [columnFilter]=\"_workingColumnFilter\"\n [operator]=\"$any(_workingColumnOperator[column.field])\"\n (operatorChange)=\"_workingColumnOperator[column.field] = $event!\"\n [cacheValues]=\"_cacheValues\"\n isMobile>\n </column-filter>\n </div>\n }\n </section>\n }\n </div>\n\n <ng-container sdFooter>\n <div class=\"mobile-filter-footer\">\n <sd-button\n [autoId]=\"_autoId + 'cancel'\"\n class=\"mobile-filter-action\"\n (click)=\"onCancel()\"\n [title]=\"'core.confirm.no' | translate\"\n type=\"link\"\n color=\"secondary\">\n </sd-button>\n <sd-button\n [autoId]=\"_autoId + 'apply'\"\n class=\"mobile-filter-action\"\n (click)=\"onApply()\"\n [title]=\"'core.confirm.yes' | translate\"\n type=\"fill\"\n color=\"primary\">\n </sd-button>\n </div>\n </ng-container>\n</sd-side-drawer>\n", styles: [".mobile-filter-body{display:flex;flex-direction:column;gap:16px;padding:8px 0}.mobile-filter-section{display:flex;flex-direction:column;gap:12px}.mobile-filter-row{display:flex;flex-direction:column}.mobile-filter-footer{display:flex;gap:8px;width:100%}.mobile-filter-action{flex:1 1 50%}.mobile-filter-action ::ng-deep button{width:100%}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "pipe", type: i1.AsyncPipe, name: "async" }, { kind: "component", type: SdSideDrawer, selector: "sd-side-drawer", inputs: ["title", "width", "hideClose", "disableBackdropClose", "drawerClass", "autoId"], outputs: ["sdClosed"] }, { 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: ColumnFilterComponent, selector: "column-filter", inputs: ["autoId", "column", "columnFilter", "cacheValues", "value", "operator", "isMobile"], outputs: ["operatorChange", "filterChange", "filterCommit"] }, { kind: "component", type: SdInput, selector: "sd-input", inputs: ["autoId", "name", "appearance", "floatLabel", "size", "form", "label", "helperText", "placeholder", "type", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "minlength", "maxlength", "pattern", "patternErrorMessage", "validator", "inlineError", "hyperlink", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdInputNumber, selector: "sd-input-number", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "blurOnEnter", "required", "readonly", "disabled", "viewed", "type", "precision", "format", "min", "max", "validator", "inlineError", "hyperlink", "appearance", "floatLabel", "model"], outputs: ["modelChange", "sdChange", "sdFocus", "sdBlur", "keyupEnter", "cleared", "sdFocusForceBlur"] }, { kind: "component", type: SdSelect, selector: "sd-select", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "valueField", "displayField", "disabledField", "cacheChecksum", "limit", "hyperlink", "minWidthPanel", "hideInlineError", "required", "disabled", "viewed", "multiple", "clearable", "validator", "inlineError", "appearance", "floatLabel", "items", "model"], outputs: ["modelChange", "sdChange", "sdSelection"] }, { kind: "component", type: SdDate, selector: "sd-date", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDatetime, selector: "sd-datetime", inputs: ["autoId", "name", "size", "form", "label", "helperText", "placeholder", "hideInlineError", "required", "disabled", "viewed", "showSeconds", "clearable", "inlineError", "hyperlink", "appearance", "floatLabel", "min", "minDate", "max", "maxDate", "model"], outputs: ["modelChange", "sdChange", "sdFocus"] }, { kind: "component", type: SdDateRange$1, selector: "sd-date-range", inputs: ["autoId", "name", "size", "form", "label", "helperText", "hideInlineError", "required", "disabled", "viewed", "clearable", "appearance", "floatLabel", "min", "max", "model"], outputs: ["modelChange", "sdChange"] }, { kind: "pipe", type: FilterValuesPipe, name: "filterValues" }, { kind: "pipe", type: TranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
718
993
  }
719
994
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: MobileFilterComponent, decorators: [{
720
995
  type: Component,
@@ -731,7 +1006,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
731
1006
  SdDateRange$1,
732
1007
  FilterValuesPipe,
733
1008
  TranslatePipe,
734
- ], template: "@let _autoId = autoId();\n@let _externalFilters = externalFilterItems();\n@let _columns = columnItems();\n@let _filterDefs = filterDefItems();\n@let _workingColumnFilter = workingColumnFilter();\n@let _workingColumnOperator = workingColumnOperator();\n@let _workingExternalFilter = workingExternalFilter();\n@let _cacheValues = cacheValues();\n\n<sd-side-drawer\n #drawer\n [title]=\"'core.component.table.filter' | translate\"\n width=\"90vw\">\n <div class=\"mobile-filter-body\">\n <!-- ============================ -->\n <!-- 1. External filters (inline) -->\n <!-- ============================ -->\n @if (_externalFilters.length) {\n <section class=\"mobile-filter-section\">\n @for (item of _externalFilters; track item.field) {\n <div class=\"mobile-filter-row\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE' || item.option.selection === 'MULTIPLEAUTOCOMPLETE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'lazy-values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { externalFilter: _workingExternalFilter, autoId: _autoId + 'ext-' + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 2. Custom filterDef templates -->\n <!-- ============================ -->\n @if (_filterDefs.length) {\n <section class=\"mobile-filter-section\">\n @for (def of _filterDefs; track def.sdTableFilterDef()) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'def-' + def.sdTableFilterDef()\">\n <ng-container\n *ngTemplateOutlet=\"def.templateRef!; context: { externalFilter: _workingExternalFilter }\">\n </ng-container>\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 3. Column filters -->\n <!-- ============================ -->\n @if (_columns.length) {\n <section class=\"mobile-filter-section\">\n @for (column of _columns; track column.field) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'col-' + column.field\">\n <column-filter\n [autoId]=\"_autoId + 'col'\"\n [column]=\"column\"\n [columnFilter]=\"_workingColumnFilter\"\n [operator]=\"$any(_workingColumnOperator[column.field])\"\n (operatorChange)=\"_workingColumnOperator[column.field] = $event!\"\n [cacheValues]=\"_cacheValues\"\n isMobile>\n </column-filter>\n </div>\n }\n </section>\n }\n </div>\n\n <ng-container sdFooter>\n <div class=\"mobile-filter-footer\">\n <sd-button\n [autoId]=\"_autoId + 'cancel'\"\n class=\"mobile-filter-action\"\n (click)=\"onCancel()\"\n [title]=\"'core.confirm.no' | translate\"\n type=\"link\"\n color=\"secondary\">\n </sd-button>\n <sd-button\n [autoId]=\"_autoId + 'apply'\"\n class=\"mobile-filter-action\"\n (click)=\"onApply()\"\n [title]=\"'core.confirm.yes' | translate\"\n type=\"fill\"\n color=\"primary\">\n </sd-button>\n </div>\n </ng-container>\n</sd-side-drawer>\n", styles: [".mobile-filter-body{display:flex;flex-direction:column;gap:16px;padding:8px 0}.mobile-filter-section{display:flex;flex-direction:column;gap:12px}.mobile-filter-row{display:flex;flex-direction:column}.mobile-filter-footer{display:flex;gap:8px;width:100%}.mobile-filter-action{flex:1 1 50%}.mobile-filter-action ::ng-deep button{width:100%}\n"] }]
1009
+ ], template: "@let _autoId = autoId();\n@let _externalFilterItems = externalFilterItems();\n@let _columnItems = columnItems();\n@let _filterDefItems = filterDefItems();\n@let _workingColumnFilter = workingColumnFilter();\n@let _workingColumnOperator = workingColumnOperator();\n@let _workingExternalFilter = workingExternalFilter();\n@let _cacheValues = cacheValues();\n\n<sd-side-drawer\n #drawer\n [title]=\"'core.component.table.filter' | translate\"\n width=\"90vw\">\n <div class=\"mobile-filter-body\">\n <!-- ============================ -->\n <!-- 1. External filters (inline) -->\n <!-- ============================ -->\n @if (_externalFilterItems.length) {\n <section class=\"mobile-filter-section\">\n @for (item of _externalFilterItems; track item.field) {\n <div class=\"mobile-filter-row\">\n @if (item.type === 'string') {\n <sd-input\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n type=\"text\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input>\n } @else if (item.type === 'number') {\n <sd-input-number\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-input-number>\n } @else if (item.type === 'boolean') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"[\n { value: true, display: item.option?.displayOnTrue || 'True' },\n { value: false, display: item.option?.displayOnFalse || 'False' },\n ]\"\n valueField=\"value\"\n displayField=\"display\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'date') {\n <sd-date\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-date>\n } @else if (item.type === 'datetime') {\n <sd-datetime\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n hideInlineError>\n </sd-datetime>\n } @else if (item.type === 'daterange') {\n <sd-date-range\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n [required]=\"item.required\"\n [min]=\"item.minDate\"\n [max]=\"item.maxDate\"\n appearance=\"outline\"\n hideInlineError>\n </sd-date-range>\n } @else if (item.type === 'values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items | filterValues | async\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE' || item.option.selection === 'MULTIPLEAUTOCOMPLETE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'lazy-values') {\n <sd-select\n [autoId]=\"_autoId + 'ext-' + item.field\"\n size=\"sm\"\n [label]=\"item.title\"\n [items]=\"item.option.items\"\n [valueField]=\"item.option.valueField\"\n [displayField]=\"item.option.displayField\"\n [(model)]=\"_workingExternalFilter[item.field]\"\n appearance=\"outline\"\n [multiple]=\"item.option.selection === 'MULTIPLE'\"\n [required]=\"item.required\"\n hideInlineError>\n </sd-select>\n } @else if (item.type === 'custom' && item.filterDef) {\n <ng-container\n *ngTemplateOutlet=\"\n item.filterDef;\n context: { externalFilter: _workingExternalFilter, autoId: _autoId + 'ext-' + item.field }\n \">\n </ng-container>\n }\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 2. Custom filterDef templates -->\n <!-- ============================ -->\n @if (_filterDefItems.length) {\n <section class=\"mobile-filter-section\">\n @for (def of _filterDefItems; track def.sdTableFilterDef()) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'def-' + def.sdTableFilterDef()\">\n <ng-container\n *ngTemplateOutlet=\"def.templateRef!; context: { externalFilter: _workingExternalFilter }\">\n </ng-container>\n </div>\n }\n </section>\n }\n\n <!-- ============================ -->\n <!-- 3. Column filters -->\n <!-- ============================ -->\n @if (_columnItems.length) {\n <section class=\"mobile-filter-section\">\n @for (column of _columnItems; track column.field) {\n <div class=\"mobile-filter-row\" [attr.data-autoid]=\"_autoId + 'col-' + column.field\">\n <column-filter\n [autoId]=\"_autoId + 'col'\"\n [column]=\"column\"\n [columnFilter]=\"_workingColumnFilter\"\n [operator]=\"$any(_workingColumnOperator[column.field])\"\n (operatorChange)=\"_workingColumnOperator[column.field] = $event!\"\n [cacheValues]=\"_cacheValues\"\n isMobile>\n </column-filter>\n </div>\n }\n </section>\n }\n </div>\n\n <ng-container sdFooter>\n <div class=\"mobile-filter-footer\">\n <sd-button\n [autoId]=\"_autoId + 'cancel'\"\n class=\"mobile-filter-action\"\n (click)=\"onCancel()\"\n [title]=\"'core.confirm.no' | translate\"\n type=\"link\"\n color=\"secondary\">\n </sd-button>\n <sd-button\n [autoId]=\"_autoId + 'apply'\"\n class=\"mobile-filter-action\"\n (click)=\"onApply()\"\n [title]=\"'core.confirm.yes' | translate\"\n type=\"fill\"\n color=\"primary\">\n </sd-button>\n </div>\n </ng-container>\n</sd-side-drawer>\n", styles: [".mobile-filter-body{display:flex;flex-direction:column;gap:16px;padding:8px 0}.mobile-filter-section{display:flex;flex-direction:column;gap:12px}.mobile-filter-row{display:flex;flex-direction:column}.mobile-filter-footer{display:flex;gap:8px;width:100%}.mobile-filter-action{flex:1 1 50%}.mobile-filter-action ::ng-deep button{width:100%}\n"] }]
735
1010
  }] });
736
1011
 
737
1012
  class ActionFilterPipe {
@@ -842,11 +1117,11 @@ class SelectorActionComponent {
842
1117
  }
843
1118
  };
844
1119
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SelectorActionComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
845
- static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SelectorActionComponent, isStandalone: true, selector: "selector-action", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, tableOption: { classPropertyName: "tableOption", publicName: "tableOption", isSignal: true, isRequired: false, transformFunction: null }, selectedTableItems: { classPropertyName: "selectedTableItems", publicName: "selectedTableItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clear: "clear" }, ngImport: i0, template: "@let _actions = selectedTableItems() | actionFilter: tableOption()?.selector?.actions;\n@let _selected = selectedTableItems();\n@let _autoId = autoId();\n\n<sd-quick-action [opened]=\"opened()\">\n <div class=\"d-flex align-items-center\" sdMessage>\n <div class=\"c-bg-length\">\n <span class=\"c-length\">{{ _selected?.length }}</span>\n </div>\n <div class=\"c-message\">\n {{ message() }}\n </div>\n </div>\n\n <div class=\"d-flex align-items-center mr-8\" sdAction>\n @for (action of _actions; track $index) {\n @if (action.variant === 'normal') {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + $index : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon\"\n [color]=\"action.color\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet\"\n [title]=\"action.title\"\n (click)=\"onClickAction(action)\"></sd-button>\n } @else if (action.variant === 'children') {\n @let _actionIndex = $index;\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + _actionIndex : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon || 'more_vert'\"\n [color]=\"action.color || 'secondary'\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet || 'material-icons-outlined'\"\n [title]=\"action.title\"\n [matMenuTriggerFor]=\"menu\">\n </sd-button>\n <mat-menu #menu=\"matMenu\">\n <span>\n @for (child of action.children; track $index) {\n <button\n mat-menu-item\n [attr.data-autoid]=\"_autoId ? _autoId + '-action-' + _actionIndex + '-' + $index : null\"\n (click)=\"onClickAction(child)\">\n @if (child.icon) {\n <mat-icon [fontSet]=\"child.fontSet || 'material-icons-outlined'\" class=\"c-icon\">{{ child.icon }}</mat-icon>\n }\n <span> {{ child.title }}</span>\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n <!-- N\u00FAt close lu\u00F4n hi\u1EC7n k\u1EC3 c\u1EA3 kh\u00F4ng c\u00F3 action \u2014 \u0111\u1EA3m b\u1EA3o user c\u00F3 l\u1ED1i tho\u00E1t clear selection -->\n <sd-button [autoId]=\"_autoId ? _autoId + '-clear-selection' : null\" class=\"ml-4\" prefixIcon=\"close\" color=\"secondary\" type=\"outline\" (click)=\"onClear()\" width=\"35px\"></sd-button>\n </div>\n</sd-quick-action>\n", styles: [".c-bg-length{display:flex;flex-direction:column;align-items:flex-start;padding:12px 8px;position:static;width:auto;min-width:48px;height:48px;left:0;top:0;background:var(--sd-primary);border-radius:4px 0 0 4px}.c-bg-length .c-length{position:static;width:auto;min-width:32px;height:24px;left:8px;top:12px;font-family:Roboto;font-style:normal;font-weight:500;font-size:16px;line-height:24px;text-align:center;color:#fff}.c-message{position:static;min-width:200px;height:20px;left:0;top:6px;font-family:Roboto;font-style:normal;font-weight:400;font-size:14px;line-height:20px;color:#000;margin:0 0 0 8px}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { 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: SdQuickAction, selector: "sd-quick-action", inputs: ["opened"] }, { kind: "pipe", type: ActionFilterPipe, name: "actionFilter" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
1120
+ static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SelectorActionComponent, isStandalone: true, selector: "selector-action", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, tableOption: { classPropertyName: "tableOption", publicName: "tableOption", isSignal: true, isRequired: false, transformFunction: null }, selectedTableItems: { classPropertyName: "selectedTableItems", publicName: "selectedTableItems", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { clear: "clear" }, ngImport: i0, template: "@let _actions = selectedTableItems() | actionFilter: tableOption()?.selector?.actions;\n@let _selectedTableItems = selectedTableItems();\n@let _autoId = autoId();\n\n<sd-quick-action [opened]=\"opened()\">\n <div class=\"d-flex align-items-center\" sdMessage>\n <div class=\"c-bg-length\">\n <span class=\"c-length\">{{ _selectedTableItems?.length }}</span>\n </div>\n <div class=\"c-message\">\n {{ message() }}\n </div>\n </div>\n\n <div class=\"d-flex align-items-center mr-8\" sdAction>\n @for (action of _actions; track $index) {\n @if (action.variant === 'normal') {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + $index : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon\"\n [color]=\"action.color\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet\"\n [title]=\"action.title\"\n (click)=\"onClickAction(action)\"></sd-button>\n } @else if (action.variant === 'children') {\n @let _actionIndex = $index;\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + _actionIndex : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon || 'more_vert'\"\n [color]=\"action.color || 'secondary'\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet || 'material-icons-outlined'\"\n [title]=\"action.title\"\n [matMenuTriggerFor]=\"menu\">\n </sd-button>\n <mat-menu #menu=\"matMenu\">\n <span>\n @for (child of action.children; track $index) {\n <button\n mat-menu-item\n [attr.data-autoid]=\"_autoId ? _autoId + '-action-' + _actionIndex + '-' + $index : null\"\n (click)=\"onClickAction(child)\">\n @if (child.icon) {\n <mat-icon [fontSet]=\"child.fontSet || 'material-icons-outlined'\" class=\"c-icon\">{{ child.icon }}</mat-icon>\n }\n <span> {{ child.title }}</span>\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n <!-- N\u00FAt close lu\u00F4n hi\u1EC7n k\u1EC3 c\u1EA3 kh\u00F4ng c\u00F3 action \u2014 \u0111\u1EA3m b\u1EA3o user c\u00F3 l\u1ED1i tho\u00E1t clear selection -->\n <sd-button [autoId]=\"_autoId ? _autoId + '-clear-selection' : null\" class=\"ml-4\" prefixIcon=\"close\" color=\"secondary\" type=\"outline\" (click)=\"onClear()\" width=\"35px\"></sd-button>\n </div>\n</sd-quick-action>\n", styles: [".c-bg-length{display:flex;flex-direction:column;align-items:flex-start;padding:12px 8px;position:static;width:auto;min-width:48px;height:48px;left:0;top:0;background:var(--sd-primary);border-radius:4px 0 0 4px}.c-bg-length .c-length{position:static;width:auto;min-width:32px;height:24px;left:8px;top:12px;font-family:Roboto;font-style:normal;font-weight:500;font-size:16px;line-height:24px;text-align:center;color:#fff}.c-message{position:static;min-width:200px;height:20px;left:0;top:6px;font-family:Roboto;font-style:normal;font-weight:400;font-size:14px;line-height:20px;color:#000;margin:0 0 0 8px}\n"], dependencies: [{ kind: "ngmodule", type: MatIconModule }, { kind: "component", type: i5.MatIcon, selector: "mat-icon", inputs: ["color", "inline", "svgIcon", "fontSet", "fontIcon"], exportAs: ["matIcon"] }, { kind: "ngmodule", type: MatMenuModule }, { kind: "component", type: i2.MatMenu, selector: "mat-menu", inputs: ["backdropClass", "aria-label", "aria-labelledby", "aria-describedby", "xPosition", "yPosition", "overlapTrigger", "hasBackdrop", "class", "classList"], outputs: ["closed", "close"], exportAs: ["matMenu"] }, { kind: "component", type: i2.MatMenuItem, selector: "[mat-menu-item]", inputs: ["role", "disabled", "disableRipple"], exportAs: ["matMenuItem"] }, { kind: "directive", type: i2.MatMenuTrigger, selector: "[mat-menu-trigger-for], [matMenuTriggerFor]", inputs: ["mat-menu-trigger-for", "matMenuTriggerFor", "matMenuTriggerData", "matMenuTriggerRestoreFocus"], outputs: ["menuOpened", "onMenuOpen", "menuClosed", "onMenuClose"], exportAs: ["matMenuTrigger"] }, { 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: SdQuickAction, selector: "sd-quick-action", inputs: ["opened"] }, { kind: "pipe", type: ActionFilterPipe, name: "actionFilter" }], changeDetection: i0.ChangeDetectionStrategy.OnPush });
846
1121
  }
847
1122
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SelectorActionComponent, decorators: [{
848
1123
  type: Component,
849
- args: [{ selector: 'selector-action', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconModule, MatMenuModule, SdButton, SdQuickAction, ActionFilterPipe], template: "@let _actions = selectedTableItems() | actionFilter: tableOption()?.selector?.actions;\n@let _selected = selectedTableItems();\n@let _autoId = autoId();\n\n<sd-quick-action [opened]=\"opened()\">\n <div class=\"d-flex align-items-center\" sdMessage>\n <div class=\"c-bg-length\">\n <span class=\"c-length\">{{ _selected?.length }}</span>\n </div>\n <div class=\"c-message\">\n {{ message() }}\n </div>\n </div>\n\n <div class=\"d-flex align-items-center mr-8\" sdAction>\n @for (action of _actions; track $index) {\n @if (action.variant === 'normal') {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + $index : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon\"\n [color]=\"action.color\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet\"\n [title]=\"action.title\"\n (click)=\"onClickAction(action)\"></sd-button>\n } @else if (action.variant === 'children') {\n @let _actionIndex = $index;\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + _actionIndex : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon || 'more_vert'\"\n [color]=\"action.color || 'secondary'\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet || 'material-icons-outlined'\"\n [title]=\"action.title\"\n [matMenuTriggerFor]=\"menu\">\n </sd-button>\n <mat-menu #menu=\"matMenu\">\n <span>\n @for (child of action.children; track $index) {\n <button\n mat-menu-item\n [attr.data-autoid]=\"_autoId ? _autoId + '-action-' + _actionIndex + '-' + $index : null\"\n (click)=\"onClickAction(child)\">\n @if (child.icon) {\n <mat-icon [fontSet]=\"child.fontSet || 'material-icons-outlined'\" class=\"c-icon\">{{ child.icon }}</mat-icon>\n }\n <span> {{ child.title }}</span>\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n <!-- N\u00FAt close lu\u00F4n hi\u1EC7n k\u1EC3 c\u1EA3 kh\u00F4ng c\u00F3 action \u2014 \u0111\u1EA3m b\u1EA3o user c\u00F3 l\u1ED1i tho\u00E1t clear selection -->\n <sd-button [autoId]=\"_autoId ? _autoId + '-clear-selection' : null\" class=\"ml-4\" prefixIcon=\"close\" color=\"secondary\" type=\"outline\" (click)=\"onClear()\" width=\"35px\"></sd-button>\n </div>\n</sd-quick-action>\n", styles: [".c-bg-length{display:flex;flex-direction:column;align-items:flex-start;padding:12px 8px;position:static;width:auto;min-width:48px;height:48px;left:0;top:0;background:var(--sd-primary);border-radius:4px 0 0 4px}.c-bg-length .c-length{position:static;width:auto;min-width:32px;height:24px;left:8px;top:12px;font-family:Roboto;font-style:normal;font-weight:500;font-size:16px;line-height:24px;text-align:center;color:#fff}.c-message{position:static;min-width:200px;height:20px;left:0;top:6px;font-family:Roboto;font-style:normal;font-weight:400;font-size:14px;line-height:20px;color:#000;margin:0 0 0 8px}\n"] }]
1124
+ args: [{ selector: 'selector-action', changeDetection: ChangeDetectionStrategy.OnPush, imports: [MatIconModule, MatMenuModule, SdButton, SdQuickAction, ActionFilterPipe], template: "@let _actions = selectedTableItems() | actionFilter: tableOption()?.selector?.actions;\n@let _selectedTableItems = selectedTableItems();\n@let _autoId = autoId();\n\n<sd-quick-action [opened]=\"opened()\">\n <div class=\"d-flex align-items-center\" sdMessage>\n <div class=\"c-bg-length\">\n <span class=\"c-length\">{{ _selectedTableItems?.length }}</span>\n </div>\n <div class=\"c-message\">\n {{ message() }}\n </div>\n </div>\n\n <div class=\"d-flex align-items-center mr-8\" sdAction>\n @for (action of _actions; track $index) {\n @if (action.variant === 'normal') {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + $index : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon\"\n [color]=\"action.color\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet\"\n [title]=\"action.title\"\n (click)=\"onClickAction(action)\"></sd-button>\n } @else if (action.variant === 'children') {\n @let _actionIndex = $index;\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-action-' + _actionIndex : null\"\n class=\"ml-4\"\n [tooltip]=\"action.tooltip\"\n [prefixIcon]=\"action.icon || 'more_vert'\"\n [color]=\"action.color || 'secondary'\"\n [type]=\"action.type || 'light'\"\n [fontSet]=\"action.fontSet || 'material-icons-outlined'\"\n [title]=\"action.title\"\n [matMenuTriggerFor]=\"menu\">\n </sd-button>\n <mat-menu #menu=\"matMenu\">\n <span>\n @for (child of action.children; track $index) {\n <button\n mat-menu-item\n [attr.data-autoid]=\"_autoId ? _autoId + '-action-' + _actionIndex + '-' + $index : null\"\n (click)=\"onClickAction(child)\">\n @if (child.icon) {\n <mat-icon [fontSet]=\"child.fontSet || 'material-icons-outlined'\" class=\"c-icon\">{{ child.icon }}</mat-icon>\n }\n <span> {{ child.title }}</span>\n </button>\n }\n </span>\n </mat-menu>\n }\n }\n <!-- N\u00FAt close lu\u00F4n hi\u1EC7n k\u1EC3 c\u1EA3 kh\u00F4ng c\u00F3 action \u2014 \u0111\u1EA3m b\u1EA3o user c\u00F3 l\u1ED1i tho\u00E1t clear selection -->\n <sd-button [autoId]=\"_autoId ? _autoId + '-clear-selection' : null\" class=\"ml-4\" prefixIcon=\"close\" color=\"secondary\" type=\"outline\" (click)=\"onClear()\" width=\"35px\"></sd-button>\n </div>\n</sd-quick-action>\n", styles: [".c-bg-length{display:flex;flex-direction:column;align-items:flex-start;padding:12px 8px;position:static;width:auto;min-width:48px;height:48px;left:0;top:0;background:var(--sd-primary);border-radius:4px 0 0 4px}.c-bg-length .c-length{position:static;width:auto;min-width:32px;height:24px;left:8px;top:12px;font-family:Roboto;font-style:normal;font-weight:500;font-size:16px;line-height:24px;text-align:center;color:#fff}.c-message{position:static;min-width:200px;height:20px;left:0;top:6px;font-family:Roboto;font-style:normal;font-weight:400;font-size:14px;line-height:20px;color:#000;margin:0 0 0 8px}\n"] }]
850
1125
  }] });
851
1126
 
852
1127
  const DEFAULT_TABLE_CONFIG = {
@@ -2411,13 +2686,13 @@ class TableExportService {
2411
2686
  }
2412
2687
  }
2413
2688
  else if (column.type === 'date') {
2414
- obj[fieldStr] = DateUtilities.toFormat(itemValue, 'yyyy/MM/dd');
2689
+ obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'yyyy/MM/dd');
2415
2690
  }
2416
2691
  else if (column.type === 'datetime') {
2417
- obj[fieldStr] = DateUtilities.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
2692
+ obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'yyyy/MM/dd HH:mm:ss');
2418
2693
  }
2419
2694
  else if (column.type === 'time') {
2420
- obj[fieldStr] = DateUtilities.toFormat(itemValue, 'HH:mm:ss');
2695
+ obj[fieldStr] = DateUtilities$1.toFormat(itemValue, 'HH:mm:ss');
2421
2696
  }
2422
2697
  else if (column.type === 'values' || column.type === 'lazy-values') {
2423
2698
  const vals = (Array.isArray(itemValue) ? itemValue : [itemValue]).filter(e => !!e?.toString());
@@ -2712,8 +2987,8 @@ class TableFormatService {
2712
2987
  // PRIVATE HELPERS
2713
2988
  // ==========================================
2714
2989
  #formatDateDisplay(value, type) {
2715
- const date = DateUtilities.toFormat(value, 'dd/MM/yyyy');
2716
- const time = DateUtilities.toFormat(value, 'HH:mm:ss');
2990
+ const date = DateUtilities$1.toFormat(value, 'dd/MM/yyyy');
2991
+ const time = DateUtilities$1.toFormat(value, 'HH:mm:ss');
2717
2992
  if (type === 'datetime') {
2718
2993
  return time && date ? `<div class="T14R">${date}<span class="T14R text-black400 ml-4">${time}</span></div>` : '';
2719
2994
  }
@@ -2774,44 +3049,305 @@ const buildColumnWidthMap = (column) => {
2774
3049
  return result;
2775
3050
  };
2776
3051
 
2777
- class SdTableFilterService {
2778
- storageService;
2779
- #filterConfiguration = 'GRID-FILTER-CONFIGURATION';
2780
- #filterValue = 'GRID-FILTER-VALUE';
2781
- #cache = {};
2782
- constructor(storageService) {
2783
- this.storageService = storageService;
2784
- }
2785
- register = (filter, args) => {
2786
- let cacheSession = false;
2787
- const { id, columns, externalFilters } = args;
2788
- const tempKey = Utilities.hash({
2789
- id,
2790
- columns: columns?.map(e => e.field).filter(field => !!field) || [],
2791
- externalFilters: externalFilters?.map(e => e.field).filter(field => !!field) || [],
2792
- });
2793
- const key = filter?.key || tempKey;
2794
- if (!filter?.key) {
2795
- cacheSession = true; // Nếu không key thì chỉ lưu theo session
2796
- }
2797
- if (!this.#cache[key]) {
2798
- // Setting của filter configuration
2799
- const filterConfiguration = this.storageService.create({
2800
- prefix: this.#filterConfiguration,
2801
- key,
2802
- }, {
2803
- default: this.#defaultConfiguration(args),
2804
- type: cacheSession ? 'session' : undefined,
2805
- });
2806
- // Lấy giá trị configuration merge với giá trị defaultShowing của args nếu như args có thay đổi
2807
- filterConfiguration.set(this.#initConfiguration(args, filterConfiguration.get()));
2808
- // Setting của filter value
2809
- const filterValue = this.storageService.create({
2810
- prefix: this.#filterValue,
2811
- key: !filter?.cacheable ? tempKey : key,
2812
- }, {
2813
- default: this.#defaultValue(args),
2814
- type: cacheSession || !filter?.cacheable ? 'session' : undefined,
3052
+ /* eslint-disable @typescript-eslint/no-explicit-any */
3053
+ const isTableItem = (item) => !!item && typeof item === 'object' && 'data' in item && 'meta' in item;
3054
+ const resolveItemData = (item) => (isTableItem(item) ? item.data : item);
3055
+ const hasActiveColumnFilter = (rawColumnFilter = {}, columns = []) => columns.some(col => {
3056
+ const v = rawColumnFilter[col.field];
3057
+ if (Array.isArray(v))
3058
+ return v.length > 0;
3059
+ if (v && typeof v === 'object')
3060
+ return !!v.from || !!v.to;
3061
+ return v !== undefined && v !== null && v !== '';
3062
+ });
3063
+ const matchesColumnFilter = (data, columns = [], rawColumnFilter = {}) => {
3064
+ for (const column of columns) {
3065
+ const { field, type } = column;
3066
+ const filterValue = (rawColumnFilter[field] || '').toString().trim().toLowerCase();
3067
+ const rawColVal = Utilities.getNestedValue(data, field);
3068
+ const columnValue = (rawColVal || '').toString().trim().toLowerCase();
3069
+ if (filterValue) {
3070
+ if (!columnValue && type !== 'datetime' && type !== 'date' && type !== 'time') {
3071
+ return false;
3072
+ }
3073
+ if (type === 'string') {
3074
+ if (columnValue.indexOf(filterValue) === -1) {
3075
+ return false;
3076
+ }
3077
+ }
3078
+ else if (type === 'values' || type === 'lazy-values') {
3079
+ const columnType = column;
3080
+ const isMultiple = columnType.option.selection === 'MULTIPLE';
3081
+ if (isMultiple && Array.isArray(rawColVal)) {
3082
+ const columnValues = rawColVal.map((i) => (Utilities.getNestedValue(i, columnType.option.valueField) ?? '').toString().trim().toLowerCase()) ??
3083
+ [];
3084
+ const filterValues = rawColumnFilter[field]?.map((v) => (v ?? '').toString().trim().toLowerCase());
3085
+ if (filterValues?.length && filterValues.every(fv => !columnValues.includes(fv))) {
3086
+ return false;
3087
+ }
3088
+ }
3089
+ else {
3090
+ if (columnValue !== filterValue) {
3091
+ return false;
3092
+ }
3093
+ }
3094
+ }
3095
+ else if (type === 'number') {
3096
+ const fValue = +filterValue.replace('>=', '').replace('<=', '').replace('>', '').replace('<', '');
3097
+ const cValue = +columnValue;
3098
+ if (fValue || fValue === 0) {
3099
+ if (!cValue && cValue !== 0) {
3100
+ return false;
3101
+ }
3102
+ if (filterValue.indexOf('>=') > -1 && cValue < fValue) {
3103
+ return false;
3104
+ }
3105
+ else if (filterValue.indexOf('<=') > -1 && cValue > fValue) {
3106
+ return false;
3107
+ }
3108
+ else if (filterValue.indexOf('<') > -1 && cValue >= fValue) {
3109
+ return false;
3110
+ }
3111
+ else if (filterValue.indexOf('>') > -1 && cValue <= fValue) {
3112
+ return false;
3113
+ }
3114
+ else if (cValue !== fValue) {
3115
+ return false;
3116
+ }
3117
+ }
3118
+ }
3119
+ else if (type === 'boolean') {
3120
+ if ((filterValue === '1' || filterValue === 'true') && columnValue !== '1' && columnValue !== 'true') {
3121
+ return false;
3122
+ }
3123
+ else if ((filterValue === '0' || filterValue === 'false') && columnValue !== '0' && columnValue !== 'false') {
3124
+ return false;
3125
+ }
3126
+ }
3127
+ else if (type === 'datetime' || type === 'date' || type === 'time') {
3128
+ const from = rawColumnFilter[field]?.from ?? rawColumnFilter[field];
3129
+ const to = rawColumnFilter[field]?.to ?? rawColumnFilter[field];
3130
+ const fromDate = DateUtilities$1.begin(from);
3131
+ const toDate = DateUtilities$1.end(to);
3132
+ if (fromDate || toDate) {
3133
+ if (!columnValue) {
3134
+ return false;
3135
+ }
3136
+ const columnTime = new Date(columnValue).getTime();
3137
+ const fromDateTime = fromDate?.getTime() || null;
3138
+ const toDateTime = toDate?.getTime() || null;
3139
+ if (fromDateTime && fromDateTime > columnTime) {
3140
+ return false;
3141
+ }
3142
+ if (toDateTime && columnTime > toDateTime) {
3143
+ return false;
3144
+ }
3145
+ }
3146
+ }
3147
+ }
3148
+ }
3149
+ return true;
3150
+ };
3151
+ const filterLocalItems = (localItems, option, filterInfo) => {
3152
+ const { columns } = option;
3153
+ const { rawColumnFilter, orderBy, orderDirection, pageSize, pageNumber } = filterInfo;
3154
+ const matchesData = (data) => matchesColumnFilter(data, columns, rawColumnFilter);
3155
+ const treeOpt = option.tree;
3156
+ const treeSearch = option.type === 'local' && treeOpt?.loadType === 'static' && hasActiveColumnFilter(rawColumnFilter, columns);
3157
+ const treeSearchPredicate = treeSearch ? matchesData : undefined;
3158
+ const items = treeSearch
3159
+ ? localItems.filter(item => subtreeMatches(resolveItemData(item), matchesData, treeOpt))
3160
+ : localItems.filter(item => matchesData(resolveItemData(item)));
3161
+ if (orderBy && orderDirection) {
3162
+ const column = columns.find(e => e.field === orderBy);
3163
+ if (column) {
3164
+ const { type, field } = column;
3165
+ items.sort((tableItemCurrent, tableItemNext) => {
3166
+ const dataVal = Utilities.getNestedValue(resolveItemData(tableItemCurrent), field);
3167
+ const nextVal = Utilities.getNestedValue(resolveItemData(tableItemNext), field);
3168
+ if (type === 'number') {
3169
+ return (dataVal || 0) - (nextVal || 0);
3170
+ }
3171
+ if (type === 'date' || type === 'datetime' || type === 'time') {
3172
+ const d1 = new Date(dataVal || '').getTime();
3173
+ const d2 = new Date(nextVal || '').getTime();
3174
+ return d1 - d2;
3175
+ }
3176
+ const s1 = (dataVal || '').toString();
3177
+ const s2 = (nextVal || '').toString();
3178
+ if (s1 > s2) {
3179
+ return 1;
3180
+ }
3181
+ if (s1 < s2) {
3182
+ return -1;
3183
+ }
3184
+ return 0;
3185
+ });
3186
+ if (orderDirection === 'DESC') {
3187
+ items.reverse();
3188
+ }
3189
+ }
3190
+ }
3191
+ return {
3192
+ items: items.filter((item, index) => index >= pageNumber * pageSize && index < (pageNumber + 1) * pageSize),
3193
+ total: items.length,
3194
+ treeSearchPredicate,
3195
+ };
3196
+ };
3197
+
3198
+ const isGroupHeader = (item) => !!item?.meta?.group?.items?.length;
3199
+ const isChildRow = (item) => (item?.meta.tree?.level ?? 0) > 0;
3200
+ const isRootReorderRow = (item) => !!item && !isGroupHeader(item) && !isChildRow(item);
3201
+ const moveItem = (items, fromIndex, toIndex) => {
3202
+ const next = [...items];
3203
+ const [moved] = next.splice(fromIndex, 1);
3204
+ if (!moved)
3205
+ return next;
3206
+ next.splice(toIndex, 0, moved);
3207
+ return next;
3208
+ };
3209
+ const isReorderDisabled = (item, option, index = -1) => {
3210
+ if (isChildRow(item))
3211
+ return true;
3212
+ if (!option?.disabled || isGroupHeader(item))
3213
+ return false;
3214
+ return option.disabled(item.data, index);
3215
+ };
3216
+ const sameReorderGroup = (a, b, allItems) => {
3217
+ const groupOf = (item) => {
3218
+ let lastGroupIdx = -1;
3219
+ for (let i = 0; i < allItems.length; i++) {
3220
+ if (isGroupHeader(allItems[i]))
3221
+ lastGroupIdx = i;
3222
+ if (allItems[i] === item)
3223
+ return lastGroupIdx;
3224
+ }
3225
+ return -1;
3226
+ };
3227
+ return groupOf(a) === groupOf(b);
3228
+ };
3229
+ const canSortReorder = ({ enabled, index, dragItem, allItems = [], hasGroup }) => {
3230
+ if (!enabled)
3231
+ return false;
3232
+ const targetItem = allItems[index];
3233
+ if (!isRootReorderRow(targetItem))
3234
+ return false;
3235
+ if (isChildRow(dragItem))
3236
+ return false;
3237
+ if (hasGroup) {
3238
+ return sameReorderGroup(dragItem, targetItem, allItems);
3239
+ }
3240
+ return true;
3241
+ };
3242
+ const toReorderItemsIndex = (renderedItems, renderedIndex) => {
3243
+ let count = 0;
3244
+ for (let i = 0; i < renderedIndex; i++) {
3245
+ if (isRootReorderRow(renderedItems[i]))
3246
+ count++;
3247
+ }
3248
+ return count;
3249
+ };
3250
+ const reorderTableItems = ({ items, renderedItems, previousRenderedIndex, currentRenderedIndex, localItems, }) => {
3251
+ const fromIndex = toReorderItemsIndex(renderedItems, previousRenderedIndex);
3252
+ const toIndex = toReorderItemsIndex(renderedItems, currentRenderedIndex);
3253
+ const nextItems = moveItem(items, fromIndex, toIndex);
3254
+ let nextLocalItems = localItems;
3255
+ if (localItems) {
3256
+ const localPositions = items.map(item => localItems.indexOf(item));
3257
+ if (localPositions.every(p => p >= 0)) {
3258
+ nextLocalItems = [...localItems];
3259
+ nextItems.forEach((item, i) => {
3260
+ nextLocalItems[localPositions[i]] = item;
3261
+ });
3262
+ }
3263
+ }
3264
+ return {
3265
+ items: nextItems,
3266
+ localItems: nextLocalItems,
3267
+ fromIndex,
3268
+ toIndex,
3269
+ };
3270
+ };
3271
+
3272
+ const getSelectionRows = (roots, treeOpt) => treeOpt ? flattenTree(roots, treeOpt) : roots;
3273
+ const getSelectedRowData = (rows) => rows.filter(row => row.meta.selector.isSelected).map(row => row.data);
3274
+ const applyDefaultSelected = (roots, treeOpt, defaultSelected) => {
3275
+ if (!defaultSelected)
3276
+ return;
3277
+ const rows = treeOpt ? collectFormattedTreeRows(roots) : roots;
3278
+ rows.forEach(item => {
3279
+ item.meta.selector.isSelected = defaultSelected(item.data);
3280
+ });
3281
+ };
3282
+ const resolveSelectAllState = (visibleRows) => visibleRows.length > 0 && visibleRows.every(row => row.meta.selector?.isSelected);
3283
+ const restorePreservedSelection = (visibleRows, preservedSelectedMap) => {
3284
+ visibleRows.forEach(item => {
3285
+ if (preservedSelectedMap.has(item.meta.id)) {
3286
+ item.meta.selector.isSelected = true;
3287
+ preservedSelectedMap.set(item.meta.id, item);
3288
+ }
3289
+ });
3290
+ };
3291
+ const syncPreservedSelection = (visibleRows, preservedSelectedMap) => {
3292
+ visibleRows.forEach(item => {
3293
+ const id = item.meta.id;
3294
+ if (item.meta.selector.isSelected) {
3295
+ preservedSelectedMap.set(id, item);
3296
+ }
3297
+ else {
3298
+ preservedSelectedMap.delete(id);
3299
+ }
3300
+ });
3301
+ return Array.from(preservedSelectedMap.values());
3302
+ };
3303
+
3304
+ class SdTableFilterService {
3305
+ storageService;
3306
+ #filterConfiguration = 'GRID-FILTER-CONFIGURATION';
3307
+ #filterValue = 'GRID-FILTER-VALUE';
3308
+ #cache = {};
3309
+ constructor(storageService) {
3310
+ this.storageService = storageService;
3311
+ }
3312
+ #hasOwnValue = (values, field) => {
3313
+ return !!values && Object.prototype.hasOwnProperty.call(values, field);
3314
+ };
3315
+ #resolveKey = (filter, args) => {
3316
+ const { id, columns, externalFilters } = args;
3317
+ const tempKey = Utilities.hash({
3318
+ id,
3319
+ columns: columns?.map(e => e.field).filter(field => !!field) || [],
3320
+ externalFilters: externalFilters?.map(e => e.field).filter(field => !!field) || [],
3321
+ });
3322
+ return {
3323
+ tempKey,
3324
+ key: filter?.key || tempKey,
3325
+ cacheSession: !filter?.key,
3326
+ };
3327
+ };
3328
+ register = (filter, args) => {
3329
+ const { key, tempKey, cacheSession } = this.#resolveKey(filter, args);
3330
+ if (args.force) {
3331
+ delete this.#cache[key];
3332
+ }
3333
+ if (!this.#cache[key]) {
3334
+ // Setting của filter configuration
3335
+ const filterConfiguration = this.storageService.create({
3336
+ prefix: this.#filterConfiguration,
3337
+ key,
3338
+ }, {
3339
+ default: this.#defaultConfiguration(args),
3340
+ type: cacheSession ? 'session' : undefined,
3341
+ });
3342
+ // Lấy giá trị configuration merge với giá trị defaultShowing của args nếu như args có thay đổi
3343
+ filterConfiguration.set(this.#initConfiguration(args, filterConfiguration.get()));
3344
+ // Setting của filter value
3345
+ const filterValue = this.storageService.create({
3346
+ prefix: this.#filterValue,
3347
+ key: !filter?.cacheable ? tempKey : key,
3348
+ }, {
3349
+ default: this.#defaultValue(args),
3350
+ type: cacheSession || !filter?.cacheable ? 'session' : undefined,
2815
3351
  });
2816
3352
  // Lấy giá trị value merge với giá trị default của args nếu như args có thay đổi
2817
3353
  filterValue.set(this.#initValue(args, filterValue.get()));
@@ -2959,21 +3495,25 @@ class SdTableFilterService {
2959
3495
  const { columns, externalFilters } = args;
2960
3496
  // Filter column
2961
3497
  for (const item of columns || []) {
2962
- columnFilter[item.field] = value?.columnFilter?.[item.field] ?? item?.filter?.default;
3498
+ // why: null is a deliberate cached clear from SD controls; only a missing key should fall back to default.
3499
+ columnFilter[item.field] = this.#hasOwnValue(value?.columnFilter, item.field)
3500
+ ? value?.columnFilter?.[item.field]
3501
+ : item?.filter?.default;
2963
3502
  if (item?.filter?.operator?.enable && item?.filter?.operator?.default) {
2964
3503
  columnOperator[item.field] = item.filter.operator.default;
2965
3504
  }
2966
3505
  }
2967
3506
  // Filter external
2968
3507
  for (const item of externalFilters || []) {
3508
+ const hasExternalValue = this.#hasOwnValue(value?.externalFilter, item.field);
2969
3509
  if (item.type === 'daterange') {
2970
3510
  externalFilter[item.field] = {
2971
- from: value?.externalFilter?.[item.field]?.from ?? item.default?.from,
2972
- to: value?.externalFilter?.[item.field]?.to ?? item.default?.to,
3511
+ from: hasExternalValue ? value?.externalFilter?.[item.field]?.from : item.default?.from,
3512
+ to: hasExternalValue ? value?.externalFilter?.[item.field]?.to : item.default?.to,
2973
3513
  };
2974
3514
  }
2975
3515
  else {
2976
- externalFilter[item.field] = value?.externalFilter?.[item.field] ?? item?.default;
3516
+ externalFilter[item.field] = hasExternalValue ? value?.externalFilter?.[item.field] : item?.default;
2977
3517
  }
2978
3518
  }
2979
3519
  return {
@@ -3083,13 +3623,17 @@ class SdTable {
3083
3623
  isSelectAll = signal(false);
3084
3624
  isFiltered = signal(false);
3085
3625
  requireFiltered = signal(false);
3086
- #tableId = uuid.v4();
3626
+ #tableId = Utilities.generateUuid();
3087
3627
  filterRegister;
3088
- key = uuid.v4();
3628
+ key = Utilities.generateUuid();
3089
3629
  columnOperator = {};
3090
3630
  columnFilter = {};
3091
3631
  #localItems = [];
3092
3632
  #subscription = new Subscription();
3633
+ #configurationSubscription;
3634
+ #filterRegisterSubscription;
3635
+ #optionInstance;
3636
+ #optionRevision = 0;
3093
3637
  #reload = new Subject();
3094
3638
  #loadCompleted = false;
3095
3639
  cacheValues = {};
@@ -3099,7 +3643,7 @@ class SdTable {
3099
3643
  treeRevision = signal(0);
3100
3644
  // Search ở cấp con (static tree + type 'local'): predicate khớp 1 dòng theo
3101
3645
  // column filter hiện hành. Set ở #filterLocal khi có filter active, đọc lại ở
3102
- // #render/#initTreeMeta/#ensureChildItemsFormatted để prune + auto-expand các
3646
+ // #render/#ensureChildItemsFormatted dùng predicate này để prune + auto-expand các
3103
3647
  // nhánh có node con khớp. undefined = không search → cây render bình thường.
3104
3648
  #treeSearchPredicate;
3105
3649
  // Lần render trước có đang ở chế độ search hay không (để xử lý chuyển trạng thái).
@@ -3122,27 +3666,40 @@ class SdTable {
3122
3666
  const option = this.option();
3123
3667
  if (option) {
3124
3668
  untracked(() => {
3669
+ if (this.#optionInstance !== option) {
3670
+ this.#optionInstance = option;
3671
+ this.#resetStateForNewOption();
3672
+ }
3673
+ const optionRevision = this.#optionRevision;
3125
3674
  const initOpt = this.#initConfiguration({ ...option });
3126
3675
  this.tableOption.set(initOpt);
3127
3676
  this.#loadCompleted = false;
3128
3677
  const storage = this.#configService.init(initOpt);
3129
- this.#subscription.add(storage.observer.pipe(startWith(storage.subject.getValue())).subscribe(() => {
3678
+ this.#configurationSubscription?.unsubscribe();
3679
+ this.#configurationSubscription = storage.observer.pipe(startWith(storage.subject.getValue())).subscribe(() => {
3680
+ if (optionRevision !== this.#optionRevision)
3681
+ return;
3130
3682
  const configurationResult = this.#configService.loadConfigurationResult(initOpt, storage.get());
3131
3683
  const displayColumns = configurationResult.displayedColumns || [];
3132
3684
  this.#ref.detectChanges();
3133
3685
  this.#tableFormatService
3134
3686
  .loadValues(initOpt.columns.filter(column => displayColumns.includes(column.field)), this.cacheValues, this.#cacheObjValues)
3135
3687
  .then(() => {
3688
+ if (optionRevision !== this.#optionRevision)
3689
+ return;
3136
3690
  this.configuration.set(configurationResult);
3137
3691
  this.#loadFilterRegister();
3138
3692
  if (this.filterRegister) {
3139
- this.#reload.next({ force: true });
3693
+ this.#requestReload(true);
3140
3694
  }
3141
3695
  })
3142
3696
  .finally(() => {
3697
+ if (optionRevision !== this.#optionRevision)
3698
+ return;
3143
3699
  this.#ref.detectChanges();
3144
3700
  });
3145
- }));
3701
+ });
3702
+ this.#subscription.add(this.#configurationSubscription);
3146
3703
  });
3147
3704
  }
3148
3705
  });
@@ -3150,7 +3707,7 @@ class SdTable {
3150
3707
  const paginator = this.paginator();
3151
3708
  if (paginator) {
3152
3709
  untracked(() => {
3153
- this.#subscription.add(paginator.page.subscribe(() => this.#reload.next({ force: false })));
3710
+ this.#subscription.add(paginator.page.subscribe(() => this.#requestReload(false)));
3154
3711
  });
3155
3712
  }
3156
3713
  });
@@ -3158,7 +3715,7 @@ class SdTable {
3158
3715
  const sort = this.sort();
3159
3716
  if (sort) {
3160
3717
  untracked(() => {
3161
- this.#subscription.add(sort.sortChange.subscribe(() => this.#reload.next({ force: false })));
3718
+ this.#subscription.add(sort.sortChange.subscribe(() => this.#requestReload(false)));
3162
3719
  });
3163
3720
  }
3164
3721
  });
@@ -3172,7 +3729,7 @@ class SdTable {
3172
3729
  const conf = this.configuration();
3173
3730
  if (!conf)
3174
3731
  return;
3175
- const firstColumns = conf.firstColumns.map(c => c.field === field ? { ...c, width } : c);
3732
+ const firstColumns = conf.firstColumns.map(c => (c.field === field ? { ...c, width } : c));
3176
3733
  const column = { ...conf.column };
3177
3734
  if (column[field]) {
3178
3735
  column[field] = { ...column[field], width };
@@ -3184,16 +3741,26 @@ class SdTable {
3184
3741
  this.configuration.set({ ...conf, firstColumns, column, fixedColumn });
3185
3742
  }));
3186
3743
  }
3187
- ngOnInit() { }
3188
3744
  ngAfterViewInit() {
3189
3745
  this.#subscription.add(this.#reload
3190
3746
  .pipe(debounceTime(200), switchMap(async (data) => {
3747
+ if (data.revision !== this.#optionRevision || !this.filterRegister)
3748
+ return undefined;
3191
3749
  const filterInfo = this.getFilterRequest();
3192
3750
  const result = await this.#load(filterInfo, !this.#loadCompleted || data.force);
3751
+ if (data.revision !== this.#optionRevision)
3752
+ return undefined;
3193
3753
  this.#loadCompleted = true;
3194
- return result;
3754
+ return {
3755
+ result,
3756
+ revision: data.revision,
3757
+ };
3195
3758
  }))
3196
- .subscribe(this.#render));
3759
+ .subscribe(loadResult => {
3760
+ if (!loadResult || loadResult.revision !== this.#optionRevision)
3761
+ return;
3762
+ this.#render(loadResult.result);
3763
+ }));
3197
3764
  }
3198
3765
  ngOnDestroy() {
3199
3766
  this.#subscription.unsubscribe();
@@ -3212,6 +3779,49 @@ class SdTable {
3212
3779
  isExported: true,
3213
3780
  };
3214
3781
  };
3782
+ #requestReload = (force) => {
3783
+ this.#reload.next({ force, revision: this.#optionRevision });
3784
+ };
3785
+ #resetStateForNewOption = () => {
3786
+ this.#optionRevision += 1;
3787
+ this.#configurationSubscription?.unsubscribe();
3788
+ this.#configurationSubscription = undefined;
3789
+ this.#filterRegisterSubscription?.unsubscribe();
3790
+ this.#filterRegisterSubscription = undefined;
3791
+ this.filterRegister = undefined;
3792
+ this.#tableId = Utilities.generateUuid();
3793
+ this.key = Utilities.generateUuid();
3794
+ this.#loadCompleted = false;
3795
+ this.tableOption.set(undefined);
3796
+ this.configuration.set(undefined);
3797
+ this.items.set([]);
3798
+ this.selectedTableItems.set([]);
3799
+ this.total.set(undefined);
3800
+ this.loading.set(false);
3801
+ this.isSelectAll.set(false);
3802
+ this.isFiltered.set(false);
3803
+ this.requireFiltered.set(false);
3804
+ this.columnOperator = {};
3805
+ this.#syncColumnFilterInPlace({});
3806
+ this.#localItems = [];
3807
+ this.cacheValues = {};
3808
+ this.#cacheObjValues = {};
3809
+ this.#itemIndexMap = new WeakMap();
3810
+ this.#treeExpandState.clear();
3811
+ this.#treeSearchPredicate = undefined;
3812
+ this.#treeSearchActive = false;
3813
+ this.groupExpandState.clear();
3814
+ this.#preservedSelectedMap.clear();
3815
+ this.treeRevision.update(n => n + 1);
3816
+ if (this.paginator()) {
3817
+ this.paginator().pageIndex = 0;
3818
+ }
3819
+ const sort = this.sort();
3820
+ if (sort) {
3821
+ sort.active = '';
3822
+ sort.direction = '';
3823
+ }
3824
+ };
3215
3825
  #initConfiguration = (option) => {
3216
3826
  option.paginate = {
3217
3827
  hidden: option?.paginate?.hidden,
@@ -3231,7 +3841,10 @@ class SdTable {
3231
3841
  if (!column.filter?.operator?.list?.length) {
3232
3842
  column.filter.operator.list = this.tableConfiguration?.filter?.operator?.list?.[column.type] || [];
3233
3843
  }
3234
- this.columnOperator[column?.field] = this.tableConfiguration?.filter?.operator?.default?.[column.type];
3844
+ const defaultOperator = this.tableConfiguration?.filter?.operator?.default?.[column.type];
3845
+ if (defaultOperator) {
3846
+ this.columnOperator[column.field] = defaultOperator;
3847
+ }
3235
3848
  if (column.filter.operator.default && column.filter.operator.list?.some(el => el === column.filter?.operator?.default)) {
3236
3849
  this.columnOperator[column.field] = column.filter.operator.default;
3237
3850
  }
@@ -3241,33 +3854,39 @@ class SdTable {
3241
3854
  };
3242
3855
  #loadFilterRegister = () => {
3243
3856
  const opt = this.tableOption();
3244
- if (opt && !this.filterRegister) {
3245
- this.filterRegister = this.#gridFilterService.register(opt?.filter, {
3246
- id: this.#tableId,
3247
- columns: opt?.columns,
3248
- externalFilters: opt?.filter?.externalFilters,
3249
- filterDefs: [...this.sdFilterDefs()],
3250
- columnOperator: this.columnOperator,
3251
- });
3252
- this.#subscription.add(this.filterRegister.value.observer
3253
- .pipe(debounceTime(500), map(filterValue => {
3254
- const { columnOperator, columnFilter, notReload } = filterValue;
3255
- this.columnOperator = columnOperator || {};
3256
- // Sync IN PLACE — không gán object clone mới. column-filter chia sẻ
3257
- // reference this.columnFilter qua [columnFilter] input; nếu gán clone
3258
- // mới, cf giữ ref cũ (do OnPush + reload async lag) → ghi clear vào
3259
- // object orphan → giá trị cũ persist. Giữ ref ổn định để cf + table
3260
- // luôn trỏ cùng 1 object.
3261
- this.#syncColumnFilterInPlace(columnFilter || {});
3262
- if (!notReload) {
3263
- if (this.paginator()) {
3264
- this.paginator().pageIndex = 0;
3265
- }
3266
- this.#reload.next({ force: false });
3857
+ if (!opt || this.filterRegister)
3858
+ return;
3859
+ this.filterRegister = this.#gridFilterService.register(opt?.filter, {
3860
+ id: this.#tableId,
3861
+ columns: opt?.columns,
3862
+ externalFilters: opt?.filter?.externalFilters,
3863
+ filterDefs: [...this.sdFilterDefs()],
3864
+ columnOperator: this.columnOperator,
3865
+ force: true,
3866
+ });
3867
+ const { columnOperator, columnFilter } = this.filterRegister.value.get();
3868
+ this.columnOperator = columnOperator || {};
3869
+ this.#syncColumnFilterInPlace(columnFilter || {});
3870
+ this.#filterRegisterSubscription?.unsubscribe();
3871
+ this.#filterRegisterSubscription = this.filterRegister.value.observer
3872
+ .pipe(debounceTime(500), map(filterValue => {
3873
+ const { columnOperator, columnFilter, notReload } = filterValue;
3874
+ this.columnOperator = columnOperator || {};
3875
+ // Sync IN PLACE — không gán object clone mới. column-filter chia sẻ
3876
+ // reference this.columnFilter qua [columnFilter] input; nếu gán clone
3877
+ // mới, cf giữ ref cũ (do OnPush + reload async lag) ghi clear vào
3878
+ // object orphan → giá trị cũ persist. Giữ ref ổn định để cf + table
3879
+ // luôn trỏ cùng 1 object.
3880
+ this.#syncColumnFilterInPlace(columnFilter || {});
3881
+ if (!notReload) {
3882
+ if (this.paginator()) {
3883
+ this.paginator().pageIndex = 0;
3267
3884
  }
3268
- }))
3269
- .subscribe());
3270
- }
3885
+ this.#requestReload(false);
3886
+ }
3887
+ }))
3888
+ .subscribe();
3889
+ this.#subscription.add(this.#filterRegisterSubscription);
3271
3890
  };
3272
3891
  // Đồng bộ this.columnFilter với `next` mà GIỮ NGUYÊN reference object.
3273
3892
  // column-filter chia sẻ ref này qua [columnFilter] — reassign clone mới sẽ
@@ -3282,158 +3901,12 @@ class SdTable {
3282
3901
  }
3283
3902
  Object.assign(cur, next);
3284
3903
  };
3285
- // True nếu column filter hiện có ít nhất một giá trị đang lọc (để bật search-con).
3286
- #hasActiveColumnFilter = (rawColumnFilter, columns) => columns.some(col => {
3287
- const v = rawColumnFilter[col.field];
3288
- if (Array.isArray(v))
3289
- return v.length > 0;
3290
- if (v && typeof v === 'object')
3291
- return !!v.from || !!v.to;
3292
- return v !== undefined && v !== null && v !== '';
3293
- });
3294
- // Predicate khớp MỘT dòng (raw data) theo column filter. Tách riêng để dùng lại
3295
- // cho cả lọc root lẫn search-con (subtreeMatches/filterMatchingChildren).
3296
- #matchesColumnFilter = (data, columns, rawColumnFilter) => {
3297
- for (const column of columns) {
3298
- const { field, type } = column;
3299
- const filterValue = (rawColumnFilter[field] || '').toString().trim().toLowerCase();
3300
- // SỬA: Dùng getNestedValue để hỗ trợ nested field trong filterLocal
3301
- const rawColVal = Utilities.getNestedValue(data, field);
3302
- const columnValue = (rawColVal || '').toString().trim().toLowerCase();
3303
- if (filterValue) {
3304
- if (!columnValue && type !== 'datetime' && type !== 'date' && type !== 'time') {
3305
- return false;
3306
- }
3307
- if (type === 'string') {
3308
- if (columnValue.indexOf(filterValue) === -1) {
3309
- return false;
3310
- }
3311
- }
3312
- else if (type === 'values' || type === 'lazy-values') {
3313
- const columnType = column;
3314
- const isMultiple = columnType.option.selection === 'MULTIPLE';
3315
- if (isMultiple && Array.isArray(rawColVal)) {
3316
- const columnValues = rawColVal.map((i) => (Utilities.getNestedValue(i, columnType.option.valueField) ?? '').toString().trim().toLowerCase()) ?? [];
3317
- const filterValues = rawColumnFilter[field]?.map((v) => (v ?? '').toString().trim().toLowerCase());
3318
- if (filterValues?.length && filterValues.every(fv => !columnValues.includes(fv))) {
3319
- return false;
3320
- }
3321
- }
3322
- else {
3323
- if (columnValue !== filterValue) {
3324
- return false;
3325
- }
3326
- }
3327
- }
3328
- else if (type === 'number') {
3329
- const fValue = +filterValue.replace('>=', '').replace('<=', '').replace('>', '').replace('<', '');
3330
- const cValue = +columnValue;
3331
- if (fValue || fValue === 0) {
3332
- if (!cValue && cValue !== 0) {
3333
- return false;
3334
- }
3335
- if (filterValue.indexOf('>=') > -1 && cValue < fValue) {
3336
- return false;
3337
- }
3338
- else if (filterValue.indexOf('<=') > -1 && cValue > fValue) {
3339
- return false;
3340
- }
3341
- else if (filterValue.indexOf('<') > -1 && cValue >= fValue) {
3342
- return false;
3343
- }
3344
- else if (filterValue.indexOf('>') > -1 && cValue <= fValue) {
3345
- return false;
3346
- }
3347
- else if (cValue !== fValue) {
3348
- return false;
3349
- }
3350
- }
3351
- }
3352
- else if (type === 'boolean') {
3353
- if ((filterValue === '1' || filterValue === 'true') && columnValue !== '1' && columnValue !== 'true') {
3354
- return false;
3355
- }
3356
- else if ((filterValue === '0' || filterValue === 'false') && columnValue !== '0' && columnValue !== 'false') {
3357
- return false;
3358
- }
3359
- }
3360
- else if (type === 'datetime' || type === 'date' || type === 'time') {
3361
- const from = rawColumnFilter[field]?.from ?? rawColumnFilter[field];
3362
- const to = rawColumnFilter[field]?.to ?? rawColumnFilter[field];
3363
- const fromDate = DateUtilities.begin(from);
3364
- const toDate = DateUtilities.end(to);
3365
- if (fromDate || toDate) {
3366
- if (!columnValue) {
3367
- return false;
3368
- }
3369
- const columnTime = new Date(columnValue).getTime();
3370
- const fromDateTime = fromDate?.getTime() || null;
3371
- const toDateTime = toDate?.getTime() || null;
3372
- if (fromDateTime && fromDateTime > columnTime) {
3373
- return false;
3374
- }
3375
- if (toDateTime && columnTime > toDateTime) {
3376
- return false;
3377
- }
3378
- }
3379
- }
3380
- }
3381
- }
3382
- return true;
3383
- };
3384
3904
  #filterLocal = (localItems, filterInfo) => {
3385
- const opt = this.tableOption();
3386
- const { columns } = opt;
3387
- const { rawColumnFilter, orderBy, orderDirection, pageSize, pageNumber } = filterInfo;
3388
- const matchesData = (data) => this.#matchesColumnFilter(data, columns, rawColumnFilter);
3389
- // Search ở cấp con: chỉ bật cho table local + static tree + đang có filter.
3390
- // Khi bật, một root được giữ nếu CHÍNH NÓ hoặc bất kỳ hậu duệ nào khớp; và
3391
- // lưu predicate để #render prune + auto-expand nhánh khớp.
3392
- const treeOpt = opt.tree;
3393
- const treeSearch = opt.type === 'local' && treeOpt?.loadType === 'static' && this.#hasActiveColumnFilter(rawColumnFilter, columns);
3394
- this.#treeSearchPredicate = treeSearch ? matchesData : undefined;
3395
- const items = treeSearch
3396
- ? localItems.filter(tableItem => subtreeMatches(tableItem.data, matchesData, treeOpt))
3397
- : localItems.filter(tableItem => matchesData(tableItem.data));
3398
- // Sort
3399
- if (orderBy && orderDirection) {
3400
- const column = columns.find(e => e.field === orderBy);
3401
- if (column) {
3402
- const { type, field } = column;
3403
- items.sort((tableItemCurrent, tableItemNext) => {
3404
- const data = tableItemCurrent.data;
3405
- const next = tableItemNext.data;
3406
- // SỬA: Dùng getNestedValue cho sorting
3407
- const dataVal = Utilities.getNestedValue(data, field);
3408
- const nextVal = Utilities.getNestedValue(next, field);
3409
- if (type === 'number') {
3410
- return (dataVal || 0) - (nextVal || 0);
3411
- }
3412
- if (type === 'date' || type === 'datetime' || type === 'time') {
3413
- const d1 = new Date(dataVal || '').getTime();
3414
- const d2 = new Date(nextVal || '').getTime();
3415
- return d1 - d2;
3416
- }
3417
- const s1 = (dataVal || '').toString();
3418
- const s2 = (nextVal || '').toString();
3419
- if (s1 > s2) {
3420
- return 1;
3421
- }
3422
- if (s1 < s2) {
3423
- return -1;
3424
- }
3425
- return 0;
3426
- });
3427
- if (orderDirection === 'DESC') {
3428
- items.reverse();
3429
- }
3430
- }
3431
- }
3905
+ const result = filterLocalItems(localItems, this.tableOption(), filterInfo);
3906
+ this.#treeSearchPredicate = result.treeSearchPredicate;
3432
3907
  return {
3433
- items: items.filter((item, index) => {
3434
- return index >= pageNumber * pageSize && index < (pageNumber + 1) * pageSize;
3435
- }),
3436
- total: items.length,
3908
+ items: result.items,
3909
+ total: result.total,
3437
3910
  };
3438
3911
  };
3439
3912
  getFilterRequest = () => {
@@ -3517,7 +3990,10 @@ class SdTable {
3517
3990
  total: 0,
3518
3991
  };
3519
3992
  }
3520
- const pagingReq = this.#convertPagingReq(filterReq);
3993
+ const pagingReq = SdConvertToPagingReq(filterReq, {
3994
+ columns: opt.columns,
3995
+ externalFilters: opt.filter?.externalFilters,
3996
+ });
3521
3997
  const data = await items(filterReq, pagingReq).catch(err => {
3522
3998
  console.error(err);
3523
3999
  return {
@@ -3567,18 +4043,21 @@ class SdTable {
3567
4043
  // why: KHÔNG persist trạng thái bung bị ÉP trong lúc search (tránh nhánh
3568
4044
  // tự bung do search bị ghi nhầm thành lựa chọn của user sau khi clear).
3569
4045
  if (!this.#treeSearchActive) {
3570
- this.#saveTreeExpandState(this.items());
4046
+ saveTreeExpandState(this.items(), this.#treeExpandState);
3571
4047
  }
3572
4048
  // why: search vừa tắt → childItems đang là tập đã prune theo từ khoá cũ;
3573
4049
  // xoá cache để dựng lại đầy đủ children ở chế độ thường.
3574
4050
  if (this.#treeSearchActive && !searchNow) {
3575
- this.#clearTreeChildCache(this.#localItems);
4051
+ clearTreeChildCache(this.#localItems);
3576
4052
  }
3577
4053
  }
3578
4054
  this.items.set(args?.items || []);
3579
4055
  this.total.set(args?.total || 0);
3580
4056
  if (treeOpt) {
3581
- this.#initTreeMeta(this.items(), treeOpt);
4057
+ initTreeMeta(this.items(), treeOpt, {
4058
+ expandState: this.#treeExpandState,
4059
+ treeSearchPredicate: this.#treeSearchPredicate,
4060
+ });
3582
4061
  await this.#expandDefaultBranches(this.items(), treeOpt);
3583
4062
  this.treeRevision.update(n => n + 1);
3584
4063
  }
@@ -3625,7 +4104,10 @@ class SdTable {
3625
4104
  else {
3626
4105
  const filterReq = this.#filterExportInfo(pageNumber, pageSize);
3627
4106
  if (opt.type === 'server') {
3628
- const pagingReq = this.#convertPagingReq(filterReq);
4107
+ const pagingReq = SdConvertToPagingReq(filterReq, {
4108
+ columns: opt.columns,
4109
+ externalFilters: opt.filter?.externalFilters,
4110
+ });
3629
4111
  const result = opt.items(filterReq, pagingReq);
3630
4112
  return await result;
3631
4113
  }
@@ -3670,51 +4152,6 @@ class SdTable {
3670
4152
  exportCustom = () => {
3671
4153
  this.#tableExportService.exportCustom(this.#createExportContext());
3672
4154
  };
3673
- #saveTreeExpandState = (rows) => {
3674
- for (const row of rows) {
3675
- if (row.meta.tree?.isExpanded) {
3676
- this.#treeExpandState.set(row.meta.id, true);
3677
- }
3678
- for (const child of row.meta.tree?.childItems ?? []) {
3679
- this.#saveTreeExpandState([child]);
3680
- }
3681
- }
3682
- };
3683
- // Children "nhìn thấy được" của một row: bình thường = toàn bộ embedded;
3684
- // khi search-con = chỉ giữ child có subtree khớp từ khoá (prune).
3685
- #visibleChildrenData = (data, treeOpt) => {
3686
- const all = getChildrenFromData(data, treeOpt);
3687
- const predicate = this.#treeSearchPredicate;
3688
- return predicate ? filterMatchingChildren(all, predicate, treeOpt) : all;
3689
- };
3690
- // Xoá cache childItems trên toàn cây — dùng khi tắt search để dựng lại đầy đủ.
3691
- #clearTreeChildCache = (rows) => {
3692
- for (const row of rows) {
3693
- const children = row.meta.tree?.childItems;
3694
- if (children?.length) {
3695
- this.#clearTreeChildCache(children);
3696
- row.meta.tree.childItems = undefined;
3697
- }
3698
- }
3699
- };
3700
- #initTreeMeta = (rows, option, level = 0, parentId) => {
3701
- const searchActive = !!this.#treeSearchPredicate;
3702
- for (const row of rows) {
3703
- const saved = this.#treeExpandState.get(row.meta.id);
3704
- // Khi search: hasChildren tính theo tập con đã prune; auto-expand nhánh còn con.
3705
- const hasChildren = searchActive
3706
- ? this.#visibleChildrenData(row.data, option).length > 0
3707
- : resolveHasChildren(row, option);
3708
- row.meta.tree = {
3709
- ...row.meta.tree,
3710
- level,
3711
- parentId,
3712
- hasChildren,
3713
- isExpanded: searchActive ? hasChildren : (saved ?? resolveDefaultExpanded(level, option)),
3714
- isExpanding: false,
3715
- };
3716
- }
3717
- };
3718
4155
  #ensureChildItemsFormatted = async (row) => {
3719
4156
  const opt = this.tableOption();
3720
4157
  const treeOpt = opt.tree;
@@ -3723,7 +4160,7 @@ class SdTable {
3723
4160
  // vì tập này phụ thuộc từ khoá (đổi mỗi lần gõ) nên không thể cache.
3724
4161
  if (!searchActive && row.meta.tree?.childItems?.length)
3725
4162
  return;
3726
- const raw = this.#visibleChildrenData(row.data, treeOpt);
4163
+ const raw = getVisibleChildrenData(row.data, treeOpt, this.#treeSearchPredicate);
3727
4164
  if (!raw.length) {
3728
4165
  if (searchActive && row.meta.tree)
3729
4166
  row.meta.tree.childItems = [];
@@ -3731,20 +4168,12 @@ class SdTable {
3731
4168
  }
3732
4169
  const formatted = await this.#tableFormatService.format(raw, opt.columns, this.cacheValues, this.#cacheObjValues);
3733
4170
  row.meta.tree.childItems = formatted;
3734
- const childLevel = (row.meta.tree.level ?? 0) + 1;
3735
- for (const child of formatted) {
3736
- const saved = this.#treeExpandState.get(child.meta.id);
3737
- const childHasChildren = searchActive
3738
- ? this.#visibleChildrenData(child.data, treeOpt).length > 0
3739
- : resolveHasChildren(child, treeOpt);
3740
- child.meta.tree = {
3741
- level: childLevel,
3742
- parentId: row.meta.id,
3743
- hasChildren: childHasChildren,
3744
- isExpanded: searchActive ? childHasChildren : (saved ?? resolveDefaultExpanded(childLevel, treeOpt)),
3745
- isExpanding: false,
3746
- };
3747
- }
4171
+ initTreeMeta(formatted, treeOpt, {
4172
+ level: (row.meta.tree.level ?? 0) + 1,
4173
+ parentId: row.meta.id,
4174
+ expandState: this.#treeExpandState,
4175
+ treeSearchPredicate: this.#treeSearchPredicate,
4176
+ });
3748
4177
  };
3749
4178
  #expandDefaultBranches = async (rows, option) => {
3750
4179
  const maxDepth = option.maxDepth;
@@ -3903,28 +4332,14 @@ class SdTable {
3903
4332
  this.#updateSelectedItems();
3904
4333
  };
3905
4334
  #getSelectionRows = () => {
3906
- const roots = this.items();
3907
- const treeOpt = this.tableOption()?.tree;
3908
- if (!treeOpt)
3909
- return roots;
3910
- return flattenTree(roots, treeOpt);
4335
+ return getSelectionRows(this.items(), this.tableOption()?.tree);
3911
4336
  };
3912
- #getSelectedRowData = () => this.#getSelectionRows()
3913
- .filter(e => e.meta.selector.isSelected)
3914
- .map(e => e.data);
4337
+ #getSelectedRowData = () => getSelectedRowData(this.#getSelectionRows());
3915
4338
  #applyDefaultSelected = () => {
3916
- const defaultSelected = this.tableOption()?.selector?.defaultSelected;
3917
- if (!defaultSelected)
3918
- return;
3919
- const treeOpt = this.tableOption()?.tree;
3920
- const rows = treeOpt ? collectFormattedTreeRows(this.items()) : this.items();
3921
- rows.forEach(item => {
3922
- item.meta.selector.isSelected = defaultSelected(item.data);
3923
- });
4339
+ applyDefaultSelected(this.items(), this.tableOption()?.tree, this.tableOption()?.selector?.defaultSelected);
3924
4340
  };
3925
4341
  #syncSelectAllState = () => {
3926
- const visible = this.#getSelectionRows();
3927
- this.isSelectAll.set(visible.length > 0 && visible.every(e => e.meta.selector?.isSelected));
4342
+ this.isSelectAll.set(resolveSelectAllState(this.#getSelectionRows()));
3928
4343
  };
3929
4344
  // ==========================================
3930
4345
  // GROUP HELPERS — sync selection + expand state cho group header rows
@@ -3967,9 +4382,7 @@ class SdTable {
3967
4382
  return;
3968
4383
  const key = header.meta.group.key;
3969
4384
  const defaultExpanded = !this.tableOption()?.group?.defaultCollapsed;
3970
- const current = this.groupExpandState.has(key)
3971
- ? !!this.groupExpandState.get(key)
3972
- : defaultExpanded;
4385
+ const current = this.groupExpandState.has(key) ? !!this.groupExpandState.get(key) : defaultExpanded;
3973
4386
  this.groupExpandState.set(key, !current);
3974
4387
  header.meta.group.isExpanded = !current;
3975
4388
  // why: trigger pipe re-eval — Map mutation không thay đổi reference items() nên cần update.
@@ -3986,7 +4399,7 @@ class SdTable {
3986
4399
  * Colspan cho cell sdGroupHeader trên group row = displayedColumns.length.
3987
4400
  * Span TOÀN BỘ width data row vì group row chỉ có 1 cell.
3988
4401
  */
3989
- sdGroupColspan = computed(() => (this.configuration()?.displayedColumns?.length || 1));
4402
+ sdGroupColspan = computed(() => this.configuration()?.displayedColumns?.length || 1);
3990
4403
  /** Build context object truyền vào SdTableGroupDefDirective template. */
3991
4404
  groupContext = (header) => {
3992
4405
  const g = header.meta.group;
@@ -4008,9 +4421,7 @@ class SdTable {
4008
4421
  if (!fn)
4009
4422
  return null;
4010
4423
  const tree = row.meta?.tree;
4011
- const ctx = this.tableOption()?.tree && tree
4012
- ? { level: tree.level, hasChildren: tree.hasChildren, isExpanded: tree.isExpanded }
4013
- : undefined;
4424
+ const ctx = this.tableOption()?.tree && tree ? { level: tree.level, hasChildren: tree.hasChildren, isExpanded: tree.isExpanded } : undefined;
4014
4425
  return fn(row.data, index, ctx);
4015
4426
  };
4016
4427
  // why: khi preserveSelection bật, giữ map nội bộ id → SdTableItem để selection
@@ -4024,29 +4435,14 @@ class SdTable {
4024
4435
  #restorePreservedSelection = () => {
4025
4436
  if (!this.#preserveEnabled())
4026
4437
  return;
4027
- const rows = this.#getSelectionRows();
4028
- rows.forEach(item => {
4029
- if (this.#preservedSelectedMap.has(item.meta.id)) {
4030
- item.meta.selector.isSelected = true;
4031
- this.#preservedSelectedMap.set(item.meta.id, item);
4032
- }
4033
- });
4438
+ restorePreservedSelection(this.#getSelectionRows(), this.#preservedSelectedMap);
4034
4439
  };
4035
4440
  #updateSelectedItems = () => {
4036
4441
  const rows = this.#getSelectionRows();
4037
4442
  if (this.#preserveEnabled()) {
4038
4443
  // Sync map theo state visible: add nếu selected, remove nếu deselected.
4039
4444
  // Off-page items giữ nguyên trong map (không bị visit ở đây).
4040
- rows.forEach(item => {
4041
- const id = item.meta.id;
4042
- if (item.meta.selector.isSelected) {
4043
- this.#preservedSelectedMap.set(id, item);
4044
- }
4045
- else {
4046
- this.#preservedSelectedMap.delete(id);
4047
- }
4048
- });
4049
- this.selectedTableItems.set(Array.from(this.#preservedSelectedMap.values()));
4445
+ this.selectedTableItems.set(syncPreservedSelection(rows, this.#preservedSelectedMap));
4050
4446
  }
4051
4447
  else {
4052
4448
  this.selectedTableItems.set(rows.filter(item => item.meta.selector.isSelected));
@@ -4098,295 +4494,38 @@ class SdTable {
4098
4494
  return (p?.pageIndex ?? 0) * (p?.pageSize ?? 0);
4099
4495
  });
4100
4496
  isReorderDisabled(item) {
4101
- if ((item.meta.tree?.level ?? 0) > 0)
4102
- return true;
4103
4497
  const opt = this.tableOption()?.rowReorder;
4104
- if (!opt?.disabled || item.meta?.group?.items?.length)
4105
- return false;
4106
4498
  const idx = this.#itemIndexMap.get(item) ?? -1;
4107
- return opt.disabled(item.data, idx);
4108
- }
4109
- #sameGroup(a, b, allItems) {
4110
- const groupOf = (item) => {
4111
- let lastGroupIdx = -1;
4112
- for (let i = 0; i < allItems.length; i++) {
4113
- if (allItems[i].meta?.group?.items?.length)
4114
- lastGroupIdx = i;
4115
- if (allItems[i] === item)
4116
- return lastGroupIdx;
4117
- }
4118
- return -1;
4119
- };
4120
- return groupOf(a) === groupOf(b);
4499
+ return isReorderDisabled(item, opt, idx);
4121
4500
  }
4122
4501
  reorderSortPredicate = (index, drag, drop) => {
4123
4502
  const opt = this.tableOption()?.rowReorder;
4124
- if (!opt?.enabled)
4125
- return false;
4126
- const allItems = drop.data;
4127
- const targetItem = allItems?.[index];
4128
- if (!targetItem)
4129
- return false;
4130
- if ((targetItem.meta.tree?.level ?? 0) > 0)
4131
- return false;
4132
- if ((drag.data.meta.tree?.level ?? 0) > 0)
4133
- return false;
4134
- if (targetItem.meta?.group?.items?.length)
4135
- return false;
4136
- if (this.tableOption()?.group) {
4137
- return this.#sameGroup(drag.data, targetItem, allItems);
4138
- }
4139
- return true;
4503
+ return canSortReorder({
4504
+ enabled: opt?.enabled,
4505
+ index,
4506
+ dragItem: drag.data,
4507
+ allItems: drop.data,
4508
+ hasGroup: !!this.tableOption()?.group,
4509
+ });
4140
4510
  };
4141
4511
  onReorderDrop(event) {
4142
4512
  const { previousIndex, currentIndex } = event;
4143
4513
  if (previousIndex === currentIndex)
4144
4514
  return;
4145
- const groupedItems = event.container.data;
4146
- const toItemsIndex = (domIdx) => {
4147
- let count = 0;
4148
- for (let i = 0; i < domIdx; i++) {
4149
- if (!groupedItems[i]?.meta?.group?.items?.length && (groupedItems[i]?.meta?.tree?.level ?? 0) === 0)
4150
- count++;
4151
- }
4152
- return count;
4153
- };
4154
- const fromIdx = toItemsIndex(previousIndex);
4155
- const toIdx = toItemsIndex(currentIndex);
4156
- const current = [...this.items()];
4157
- const localPositions = current.map(item => this.#localItems.indexOf(item));
4158
- moveItemInArray(current, fromIdx, toIdx);
4159
- this.items.set(current);
4160
- if (this.tableOption()?.type === 'local' && localPositions.every(p => p >= 0)) {
4161
- const newLocal = [...this.#localItems];
4162
- current.forEach((item, i) => {
4163
- newLocal[localPositions[i]] = item;
4164
- });
4165
- this.#localItems = newLocal;
4515
+ const result = reorderTableItems({
4516
+ items: this.items(),
4517
+ renderedItems: event.container.data,
4518
+ previousRenderedIndex: previousIndex,
4519
+ currentRenderedIndex: currentIndex,
4520
+ localItems: this.tableOption()?.type === 'local' ? this.#localItems : undefined,
4521
+ });
4522
+ this.items.set(result.items);
4523
+ if (this.tableOption()?.type === 'local' && result.localItems) {
4524
+ this.#localItems = result.localItems;
4166
4525
  }
4167
4526
  this.table()?.renderRows();
4168
- this.tableOption()?.rowReorder?.onChange?.(current.map(i => i.data), event.item.data.data, fromIdx, toIdx);
4527
+ this.tableOption()?.rowReorder?.onChange?.(result.items.map(i => i.data), event.item.data.data, result.fromIndex, result.toIndex);
4169
4528
  }
4170
- #convertPagingReq = (filterReq) => {
4171
- const opt = this.tableOption();
4172
- const { columns, filter } = opt;
4173
- const externalFilters = filter?.externalFilters || [];
4174
- const req = {
4175
- filters: [],
4176
- orders: [],
4177
- pageNumber: filterReq.pageNumber,
4178
- pageSize: filterReq.pageSize,
4179
- };
4180
- const { filters, orders } = req;
4181
- const { rawColumnFilter, columnOperator, rawExternalFilter, orderBy, orderDirection } = filterReq;
4182
- for (const externalFilter of externalFilters || []) {
4183
- const { field } = externalFilter;
4184
- const value = rawExternalFilter?.[field];
4185
- if (value !== undefined && value !== null && value !== '') {
4186
- if (externalFilter.type === 'string') {
4187
- if (externalFilter.defaultOperator === 'EQUAL' && value?.includes(',')) {
4188
- filters.push({
4189
- field,
4190
- operator: 'IN',
4191
- data: value.split(',').map(val => val.trim()),
4192
- });
4193
- }
4194
- else {
4195
- filters.push({
4196
- field,
4197
- operator: externalFilter.defaultOperator || 'CONTAIN',
4198
- data: value,
4199
- });
4200
- }
4201
- }
4202
- else if (externalFilter.type === 'boolean') {
4203
- filters.push({
4204
- field,
4205
- operator: 'EQUAL',
4206
- data: value === true || value === 1 || value === 'true' || value === '1',
4207
- });
4208
- }
4209
- else if (externalFilter.type === 'daterange') {
4210
- if (typeof value === 'object' && 'from' in value && 'to' in value) {
4211
- if (value?.from) {
4212
- filters.push({
4213
- field,
4214
- operator: 'GREATER_OR_EQUAL',
4215
- data: DateUtilities.begin(value?.from).toISOString(),
4216
- });
4217
- }
4218
- if (value?.to) {
4219
- filters.push({
4220
- field,
4221
- operator: 'LESS_THAN',
4222
- data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
4223
- });
4224
- }
4225
- }
4226
- }
4227
- else if (externalFilter.type === 'date' || externalFilter.type === 'datetime') {
4228
- if (DateUtilities.isDate(value)) {
4229
- if (externalFilter.type === 'date') {
4230
- if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
4231
- filters.push({
4232
- field,
4233
- operator: 'GREATER_OR_EQUAL',
4234
- data: DateUtilities.begin(value).toISOString(),
4235
- });
4236
- }
4237
- if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
4238
- filters.push({
4239
- field,
4240
- operator: 'LESS_THAN',
4241
- data: DateUtilities.begin(DateUtilities.addDays(value, 1)).toISOString(),
4242
- });
4243
- }
4244
- }
4245
- else {
4246
- if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
4247
- filters.push({
4248
- field,
4249
- operator: 'GREATER_OR_EQUAL',
4250
- data: new Date(value).toISOString(),
4251
- });
4252
- }
4253
- if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
4254
- filters.push({
4255
- field,
4256
- operator: 'LESS_OR_EQUAL',
4257
- data: new Date(value).toISOString(),
4258
- });
4259
- }
4260
- }
4261
- }
4262
- }
4263
- else {
4264
- if (Array.isArray(value)) {
4265
- if (value.length) {
4266
- filters.push({
4267
- field,
4268
- operator: 'IN',
4269
- data: value,
4270
- });
4271
- }
4272
- }
4273
- else if (typeof value === 'object' && 'from' in value && 'to' in value) {
4274
- if (value?.from) {
4275
- filters.push({
4276
- field,
4277
- operator: 'GREATER_OR_EQUAL',
4278
- data: DateUtilities.begin(value?.from).toISOString(),
4279
- });
4280
- }
4281
- if (value?.to) {
4282
- filters.push({
4283
- field,
4284
- operator: 'LESS_THAN',
4285
- data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
4286
- });
4287
- }
4288
- }
4289
- else {
4290
- filters.push({
4291
- field,
4292
- operator: externalFilter.defaultOperator || 'EQUAL',
4293
- data: value,
4294
- });
4295
- }
4296
- }
4297
- }
4298
- }
4299
- for (const column of columns || []) {
4300
- const { field } = column;
4301
- const value = rawColumnFilter?.[field];
4302
- const operator = columnOperator?.[field] || column.filter?.operator?.default;
4303
- if (value !== undefined && value !== null && value !== '') {
4304
- if (column.type === 'string') {
4305
- // `operator` is the wide `Operator` union; in this data-bearing path it is a
4306
- // single-value comparison operator, hence the cast to the `Filter` contract.
4307
- filters.push({
4308
- field: field,
4309
- operator: operator || 'CONTAIN',
4310
- data: value,
4311
- });
4312
- }
4313
- else if (column.type === 'boolean') {
4314
- filters.push({
4315
- field: field,
4316
- operator: 'EQUAL',
4317
- data: value === true || value === 1 || value === 'true' || value === '1',
4318
- });
4319
- }
4320
- else if (column.type === 'date' || column.type === 'datetime') {
4321
- if (value && typeof value === 'object' && 'from' in value && 'to' in value) {
4322
- if (value?.from && value?.to) {
4323
- filters.push({
4324
- field: field,
4325
- operator: 'BETWEEN',
4326
- data: {
4327
- from: DateUtilities.begin(value?.from).toISOString(),
4328
- to: DateUtilities.end(value?.to).toISOString(),
4329
- },
4330
- });
4331
- }
4332
- else if (value?.from) {
4333
- filters.push({
4334
- field: field,
4335
- operator: 'GREATER_OR_EQUAL',
4336
- data: DateUtilities.begin(value?.from).toISOString(),
4337
- });
4338
- }
4339
- else if (value?.to) {
4340
- filters.push({
4341
- field: field,
4342
- operator: 'LESS_THAN',
4343
- data: DateUtilities.begin(DateUtilities.addDays(value?.to, 1)).toISOString(),
4344
- });
4345
- }
4346
- }
4347
- else {
4348
- if (DateUtilities.isDate(value)) {
4349
- filters.push({
4350
- field: field,
4351
- operator: 'BETWEEN',
4352
- data: {
4353
- from: DateUtilities.begin(value).toISOString(),
4354
- to: DateUtilities.end(value).toISOString(),
4355
- },
4356
- });
4357
- }
4358
- }
4359
- }
4360
- else {
4361
- if (Array.isArray(value)) {
4362
- if (value.length) {
4363
- filters.push({
4364
- field: field,
4365
- operator: 'IN',
4366
- data: value,
4367
- });
4368
- }
4369
- }
4370
- else {
4371
- // `operator` is the wide `Operator` union; this data-bearing path uses a
4372
- // single-value comparison operator, hence the cast to the `Filter` contract.
4373
- filters.push({
4374
- field: field,
4375
- operator: operator || 'EQUAL',
4376
- data: value,
4377
- });
4378
- }
4379
- }
4380
- }
4381
- }
4382
- if (orderBy && orderDirection) {
4383
- orders.push({
4384
- field: orderBy,
4385
- direction: orderDirection,
4386
- });
4387
- }
4388
- return req;
4389
- };
4390
4529
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: SdTable, deps: [], target: i0.ɵɵFactoryTarget.Component });
4391
4530
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "19.2.21", type: SdTable, isStandalone: true, selector: "sd-table", inputs: { autoIdInput: { classPropertyName: "autoIdInput", publicName: "autoId", isSignal: true, isRequired: false, transformFunction: null }, option: { classPropertyName: "option", publicName: "option", isSignal: true, isRequired: true, transformFunction: null } }, host: { properties: { "attr.data-autoid": "autoId()", "attr.data-loading": "loading() ? \"true\" : \"false\"" } }, providers: [
4392
4531
  DatePipe,
@@ -4468,183 +4607,6 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
4468
4607
  ], template: "@let _configuration = configuration();\n@let _tableOption = tableOption()!;\n@let _loading = loading();\n@let _items = items();\n@let _total = total();\n@let _isFiltered = isFiltered();\n@let _requireFiltered = requireFiltered();\n@let _export = _tableOption?.export;\n@let _exporting = exporting();\n@let _titleDef = titleDef();\n@let _autoId = autoId();\n@let _exportTitle = exportTitle();\n@let _cellDef = cellDef();\n@let _footerDef = footerDef();\n@let _sdSubInformation = sdSubInformation();\n@let _configComponent = configComponent();\n@let _mobileFilter = mobileFilter();\n@let _selectedTableItems = selectedTableItems();\n<!-- why: tree kh\u00F4ng c\u00F2n c\u1ED9t ri\u00EAng \u2014 khi KH\u00D4NG c\u00F3 c\u1ED9t Index, icon expand nh\u00FAng v\u00E0o\n c\u1ED9t data \u0111\u1EA7u ti\u00EAn. \u0110\u00E2y l\u00E0 field c\u1EE7a c\u1ED9t \u0111\u00F3 (null n\u1EBFu c\u00F3 Index ho\u1EB7c kh\u00F4ng tree). -->\n@let _treeFirstField = _tableOption?.tree && !_tableOption.index?.enabled ? _configuration?.firstColumns?.[0]?.field : null;\n\n@if (_configuration) {\n @if (!_tableOption.filter?.disabled && !!_tableOption.filter?.externalFilters?.length && filterRegister) {\n <external-filter\n class=\"mb-16\"\n [autoId]=\"_autoId\"\n [filterRegister]=\"filterRegister\"\n [filter]=\"_tableOption.filter!\"\n [externalFilters]=\"_tableOption.filter?.externalFilters!\">\n </external-filter>\n }\n @let groupedItems = _items | sdTree: _tableOption.tree : treeRevision() | sdGroup: _tableOption : groupExpandState;\n\n <!-- Tree toggle d\u00F9ng chung: indent (theo c\u1EA5p) + slot chevron/spinner. Nh\u00FAng v\u00E0o\n c\u1ED9t Index ho\u1EB7c c\u1ED9t data \u0111\u1EA7u qua *ngTemplateOutlet (context: { row }). -->\n <ng-template #sdTreeToggle let-row=\"row\">\n @let _tree = row?.meta?.tree;\n @if (_tree) {\n <span class=\"sd-tree-indent\" [style.width.px]=\"(_tree.level ?? 0) * (_tableOption.tree?.indentSize ?? 20)\"></span>\n <span class=\"sd-tree-toggle-slot\">\n @if (_tree.hasChildren) {\n @if (_tree.isExpanding) {\n <span class=\"lds-ring sd-tree-spinner\">\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n </span>\n } @else {\n @let _aid = _autoId;\n <button\n type=\"button\"\n class=\"sd-tree-toggle-btn\"\n [attr.data-autoid]=\"_aid ? _aid + '-tree-toggle-' + row.meta.id : null\"\n (click)=\"onTreeToggle(row); $event.stopPropagation()\">\n <mat-icon>{{ _tree.isExpanded ? 'expand_more' : 'chevron_right' }}</mat-icon>\n </button>\n }\n }\n </span>\n }\n </ng-template>\n\n <ng-content select=\"[sdTableTop]\"></ng-content>\n <div class=\"c-container\">\n @if (_loading) {\n <div class=\"c-loading\">\n <mat-spinner></mat-spinner>\n </div>\n }\n <div\n class=\"c-table\"\n sdScroll\n stickyShadow\n [style.max-height]=\"_tableOption.style?.maxHeight\"\n [style.min-height]=\"_tableOption.style?.minHeight\">\n <table\n mat-table\n [dataSource]=\"groupedItems\"\n [trackBy]=\"trackBy\"\n matSort\n [matSortDisabled]=\"!_tableOption.sort?.enable\"\n multiTemplateDataRows\n cdkDropList\n [cdkDropListData]=\"groupedItems\"\n [cdkDropListDisabled]=\"!_tableOption.rowReorder?.enabled\"\n [cdkDropListSortPredicate]=\"reorderSortPredicate\"\n (cdkDropListDropped)=\"onReorderDrop($event)\">\n @if (_tableOption.rowReorder?.enabled) {\n <ng-container matColumnDef=\"sdReorder\">\n <th mat-header-cell *matHeaderCellDef class=\"sd-reorder-header\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td mat-cell *matCellDef=\"let row\" class=\"sd-reorder-cell\">\n @if (!row.meta?.group?.items?.length) {\n <mat-icon cdkDragHandle [class.sd-reorder-disabled]=\"isReorderDisabled(row)\">\n {{ _tableOption.rowReorder?.icon || 'drag_indicator' }}\n </mat-icon>\n }\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n }\n\n <ng-container matColumnDef=\"sdSubInformation\" sticky>\n <td class=\"p-0\" mat-cell *matCellDef=\"let item\" [attr.colspan]=\"_configuration.displayedColumns.length\">\n @if (_sdSubInformation?.templateRef) {\n @if (_tableOption.expand?.always) {\n <ng-container *ngTemplateOutlet=\"_sdSubInformation?.templateRef!; context: { item: item }\"> </ng-container>\n } @else {\n <div [@detailExpand]=\"item.isExpanded ? 'expanded' : 'collapsed'\">\n @if (item.isExpanded) {\n <ng-container *ngTemplateOutlet=\"_sdSubInformation?.templateRef!; context: { item: item }\"> </ng-container>\n }\n </div>\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n\n <ng-container matColumnDef=\"sdSubInformationAction\" stickyEnd>\n <th class=\"p-0\" mat-header-cell *matHeaderCellDef style=\"width: 1px\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td mat-cell *matCellDef=\"let element\">\n @if (!element.isExpanding && !_tableOption.expand?.always) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-expand-' + element.meta.id : null\"\n type=\"link\"\n [prefixIcon]=\"element.isExpanded ? 'expand_less' : 'expand_more'\"\n (click)=\"onExpand(element)\"\n width=\"35px\">\n </sd-button>\n }\n @if (element.isExpanding) {\n <div class=\"lds-ring\">\n <div></div>\n <div></div>\n <div></div>\n <div></div>\n </div>\n }\n </td>\n </ng-container>\n\n <ng-container matColumnDef=\"sdSelection\" sticky>\n <th class=\"sd-selection-cell p-0\" mat-header-cell *matHeaderCellDef [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\">\n @let visibleSelectAll = _items | selectionVisibleSelectAll: _tableOption.selector | async;\n @if (visibleSelectAll) {\n <mat-checkbox\n class=\"c-selection px-8\"\n color=\"primary\"\n [ngModel]=\"isSelectAll()\"\n (ngModelChange)=\"isSelectAll.set($event)\"\n (change)=\"onSelectAll()\">\n </mat-checkbox>\n }\n </th>\n\n <td class=\"sd-selection-cell p-0\" mat-cell *matCellDef=\"let item\">\n @let visible = item | selectionVisible: _tableOption.selector;\n @if (visible) {\n @if (_tableOption.selector?.single) {\n <mat-radio-button\n class=\"c-selection px-8\"\n color=\"primary\"\n [checked]=\"item.meta.selector.isSelected\"\n (change)=\"item.meta.selector.isSelected = true; onSelect(item)\"\n [disabled]=\"_selectedTableItems | selectionDisabled: item : _tableOption.selector!\">\n </mat-radio-button>\n } @else {\n <mat-checkbox\n class=\"c-selection px-8\"\n color=\"primary\"\n [(ngModel)]=\"item.meta.selector.isSelected\"\n (change)=\"onSelect(item)\"\n [disabled]=\"_selectedTableItems | selectionDisabled: item : _tableOption.selector!\">\n </mat-checkbox>\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n @if (_tableOption.index?.enabled) {\n @let _pageOffset = pageOffset();\n @let _indexWidth = _tableOption.index?.width || '50px';\n <!-- why: khi tree, c\u1ED9t Index \"\u00F4m\" th\u00EAm chevron + indent. D\u00F9ng width:1px +\n nowrap (.sd-tree-index-cell) \u0111\u1EC3 c\u1ED9t co S\u00C1T n\u1ED9i dung, kh\u00F4ng chi\u1EBFm ch\u1ED7\n th\u1EEBa; header \"#\" canh tr\u00E1i l\u00F9i v\u00E0o (.sd-tree-index-header) cho th\u1EB3ng s\u1ED1. -->\n @let _treeIndex = !!_tableOption.tree;\n <ng-container matColumnDef=\"sdIndex\" sticky>\n <th\n [class.p-0]=\"!_treeIndex\"\n [class.text-center]=\"!_treeIndex\"\n [class.sd-tree-index-cell]=\"_treeIndex\"\n [class.sd-tree-index-header]=\"_treeIndex\"\n mat-header-cell\n *matHeaderCellDef\n [style.width]=\"_treeIndex ? '1px' : _indexWidth\"\n [style.min-width]=\"_treeIndex ? null : _indexWidth\"\n [style.max-width]=\"_treeIndex ? null : _indexWidth\"\n [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\">\n {{ _tableOption.index?.title || '#' }}\n </th>\n <td\n [class.px-4]=\"!_treeIndex\"\n [class.text-center]=\"!_treeIndex\"\n [class.sd-tree-index-cell]=\"_treeIndex\"\n mat-cell\n *matCellDef=\"let item; let i = dataIndex\"\n [style.width]=\"_treeIndex ? '1px' : null\"\n [style.min-width]=\"_treeIndex ? null : _indexWidth\"\n [style.max-width]=\"_treeIndex ? null : _indexWidth\">\n @if (!item?.meta?.group?.isGroupHeader) {\n @if (_treeIndex) {\n <!-- why: tree \u2192 chevron + indent + STT ph\u00E2n c\u1EA5p (1, 1.2, 1.2.1). T\u00F4 \u0111\u1EADm root (level 0). -->\n <span class=\"sd-tree-line\">\n <ng-container *ngTemplateOutlet=\"sdTreeToggle; context: { row: item }\"></ng-container>\n @if (item?.meta?.tree?.indexPath?.length) {\n <span class=\"sd-tree-stt\" [class.sd-stt-root]=\"(item.meta.tree.level ?? 0) === 0\">\n {{ item.meta.tree.indexPath.join('.') }}\n </span>\n }\n </span>\n } @else {\n {{ _pageOffset + i + 1 }}\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef [style.width]=\"_treeIndex ? '1px' : null\" [style.min-width]=\"_treeIndex ? null : _indexWidth\" [style.max-width]=\"_treeIndex ? null : _indexWidth\"></td>\n </ng-container>\n }\n <ng-container\n matColumnDef=\"sdCommand\"\n [sticky]=\"_tableOption.command?.align !== 'right'\"\n [stickyEnd]=\"_tableOption.command?.align === 'right'\">\n <th class=\"p-0\" mat-header-cell *matHeaderCellDef style=\"width: 50px\" [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td class=\"px-8\" mat-cell *matCellDef=\"let item\">\n <desktop-command\n [autoId]=\"_autoId\"\n [commands]=\"_tableOption.command?.commands || _tableOption.commands || []\"\n [item]=\"item\"\n [itemIndex]=\"groupedItems.indexOf(item)\"></desktop-command>\n </td>\n <td mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n <!-- why: group header row d\u00F9ng matRowDef RI\u00CANG v\u1EDBi column list ['sdGroupHeader']\n qua predicate when:isGroupHeaderRow. Cell colspan = displayedColumns.length \u0111\u1EC3\n span TO\u00C0N B\u1ED8 width data row. Tr\u00E1nh colspan trick overflow row khi c\u00E1c TD kh\u00E1c render. -->\n <ng-container matColumnDef=\"sdGroupHeader\">\n <td\n class=\"p-0 sd-group-header-cell\"\n mat-cell\n *matCellDef=\"let item\"\n [attr.colspan]=\"sdGroupColspan()\">\n <div class=\"sd-group-row d-flex align-items-center py-8\">\n <!-- why: slot 42px kh\u1EDBp width v\u1EDBi sd-selection-cell data row \u2192 checkbox group c\u0103n TR\u1EE4C d\u1ECDc \u0111\u00FAng v\u1EDBi checkbox data. -->\n @if (_tableOption.selector?.visible && !_tableOption.selector?.single) {\n <div class=\"sd-group-selection-slot d-flex align-items-center justify-content-center\">\n <mat-checkbox\n class=\"c-selection\"\n color=\"primary\"\n [checked]=\"isGroupAllSelected(item)\"\n [indeterminate]=\"isGroupIndeterminate(item)\"\n (change)=\"onSelectGroup(item, $event.checked)\">\n </mat-checkbox>\n </div>\n } @else {\n <div class=\"sd-group-selection-slot\"></div>\n }\n @if (_tableOption.group?.collapsible) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-group-toggle-' + (item.meta.group?.key || item.meta.id) : null\"\n type=\"link\"\n [prefixIcon]=\"item.meta.group.isExpanded ? 'expand_more' : 'chevron_right'\"\n width=\"32px\"\n (click)=\"toggleGroupExpand(item)\">\n </sd-button>\n }\n @if (sdGroupDef()?.templateRef; as tpl) {\n <ng-container *ngTemplateOutlet=\"tpl; context: groupContext(item)\"></ng-container>\n } @else {\n <span class=\"T14R\">{{ item.meta.group.values | json }}</span>\n }\n </div>\n </td>\n </ng-container>\n <!-- why: filler column \u1EDF cu\u1ED1i h\u1EA5p th\u1EE5 leftover space khi t\u1ED5ng width c\u00E1c c\u1ED9t data < container \u2014 gi\u1EEF cho selection/STT/command kh\u00F4ng b\u1ECB d\u00E3n. -->\n <ng-container matColumnDef=\"sdFiller\">\n <th class=\"sd-filler-cell p-0\" mat-header-cell *matHeaderCellDef [attr.rowspan]=\"_configuration.multipleHeader ? 2 : 1\"></th>\n <td class=\"sd-filler-cell p-0\" mat-cell *matCellDef=\"let item\"></td>\n <td class=\"sd-filler-cell p-0\" mat-footer-cell *matFooterCellDef></td>\n </ng-container>\n @for (column of _configuration.firstColumns; track column.field) {\n <ng-container [matColumnDef]=\"column.field\" [sticky]=\"_configuration.fixedColumn[column.field]\">\n <th\n mat-header-cell\n *matHeaderCellDef\n class=\"px-8 py-8 c-th\"\n [sdColumnResize]=\"!!_tableOption.config?.resizable && column.type !== 'children'\"\n [minWidth]=\"column.minWidth\"\n [maxWidth]=\"column.maxWidth\"\n (resizeEnd)=\"onColumnResize(column.field, $event)\"\n [style.width]=\"column.width\"\n [style.min-width]=\"column.minWidth || column.width\"\n [style.max-width]=\"column.maxWidth\"\n [attr.rowspan]=\"_configuration.multipleHeader && column.type !== 'children' ? 2 : 1\"\n [attr.colspan]=\"column.type === 'children' ? column.children.length : 1\">\n <div>\n @if (column.type === 'children') {\n <div\n aria-hidden=\"true\"\n class=\"c-header-title\"\n [class.justify-content-end]=\"column.align === 'right'\"\n [class.text-right]=\"column.align === 'right'\">\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\n </div>\n } @else {\n <div\n aria-hidden=\"true\"\n mat-sort-header\n class=\"c-header-title\"\n [class.justify-content-end]=\"column.align === 'right'\"\n [class.text-right]=\"column.align === 'right'\"\n [disabled]=\"!column.sortable\">\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\n </div>\n @let hideInlineFilter =\n _tableOption.filter?.disabled ||\n _tableOption.filter?.hideInlineFilter === true ||\n (_tableOption.filter?.hideInlineFilter === 'auto' && !_isFiltered && _total! <= 10);\n @if (!hideInlineFilter) {\n <ng-container *sdDesktop>\n <column-filter\n [autoId]=\"_autoId\"\n [value]=\"columnFilter?.[column.field!]!\"\n [operator]=\"columnOperator[column.field]\"\n (operatorChange)=\"columnOperator[column.field] = $event!; onOperatorChange(column, $event)\"\n [columnFilter]=\"columnFilter!\"\n [cacheValues]=\"cacheValues\"\n [column]=\"column\"\n (filterChange)=\"onFilterChange()\"\n (filterCommit)=\"onFilterCommit()\">\n </column-filter>\n </ng-container>\n }\n }\n </div>\n </th>\n <td\n class=\"c-td px-0\"\n [class.d-none]=\"column.type === 'children'\"\n mat-cell\n *matCellDef=\"let item\"\n [sdHoverCopy]=\"(item.data && item.data[column.field]) || ''\"\n [sdHoverCopyDisabled]=\"!column.cell?.copiable\">\n @if (column.type !== 'children' && !item?.meta?.group?.isGroupHeader) {\n @if (_treeFirstField && column.field === _treeFirstField) {\n <!-- why: kh\u00F4ng c\u00F3 c\u1ED9t Index \u2192 chevron + indent nh\u00FAng v\u00E0o c\u1ED9t data \u0111\u1EA7u (nh\u01B0 file explorer). -->\n <span class=\"sd-tree-line px-8\">\n <ng-container *ngTemplateOutlet=\"sdTreeToggle; context: { row: item }\"></ng-container>\n <desktop-cell class=\"d-block\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"></desktop-cell>\n </span>\n } @else {\n <desktop-cell class=\"d-block px-8\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"> </desktop-cell>\n }\n }\n </td>\n <td mat-footer-cell *matFooterCellDef>\n <ng-container *ngIf=\"_footerDef[column.field]\">\n <ng-container *ngTemplateOutlet=\"_footerDef[column.field].templateRef; context: { items: _items, column: column }\">\n </ng-container>\n </ng-container>\n </td>\n </ng-container>\n }\n @for (column of _configuration.secondColumns; track column.field) {\n <ng-container [matColumnDef]=\"column.field\">\n <th\n mat-header-cell\n *matHeaderCellDef\n class=\"c-th px-8\"\n [style.width]=\"column.width\"\n [style.min-width]=\"column.minWidth || column.width\"\n [style.max-width]=\"column.maxWidth\">\n <div>\n <div\n aria-hidden=\"true\"\n mat-sort-header\n class=\"c-header-title\"\n [class.justify-content-end]=\"column.align === 'right'\"\n [class.text-right]=\"column.align === 'right'\"\n [disabled]=\"!column.sortable\">\n <column-title [column]=\"column\" [titleDef]=\"_titleDef[column.field]\"></column-title>\n </div>\n @if (!_tableOption.filter?.disabled && !_tableOption.filter?.hideInlineFilter && columnOperator) {\n <ng-container *sdDesktop>\n <column-filter\n [autoId]=\"_autoId\"\n [value]=\"columnFilter?.[column.field!]!\"\n [operator]=\"columnOperator[column.field]\"\n [columnFilter]=\"columnFilter!\"\n [cacheValues]=\"cacheValues\"\n [column]=\"column\"\n (operatorChange)=\"columnOperator[column.field] = $event!; onOperatorChange(column, $event)\"\n (filterChange)=\"onFilterChange()\"\n (filterCommit)=\"onFilterCommit()\">\n </column-filter>\n </ng-container>\n }\n </div>\n </th>\n <td\n class=\"c-td px-0\"\n mat-cell\n *matCellDef=\"let item\"\n [sdHoverCopy]=\"item[column.field]\"\n [sdHoverCopyDisabled]=\"!column.cell?.copiable\">\n @if (column.type !== 'children') {\n <desktop-cell class=\"d-block px-8\" [column]=\"column\" [item]=\"item\" [cellDef]=\"_cellDef\"> </desktop-cell>\n }\n </td>\n <td mat-footer-cell *matFooterCellDef>\n <ng-container *ngIf=\"_footerDef[column.field]\">\n <ng-container *ngTemplateOutlet=\"_footerDef[column.field].templateRef; context: { items: _items, column: column }\">\n </ng-container>\n </ng-container>\n </td>\n </ng-container>\n }\n <tr class=\"c-first-header\" mat-header-row *matHeaderRowDef=\"_configuration.firstHeaders; sticky: true\"></tr>\n @if (!!_configuration.secondHeaders.length) {\n <tr class=\"c-second-header\" mat-header-row *matHeaderRowDef=\"_configuration.secondHeaders; sticky: true\"></tr>\n }\n <!-- Group header row \u2014 ch\u1EC9 1 cell sdGroupHeader colspan to\u00E0n width. -->\n <tr\n mat-row\n *matRowDef=\"let row; columns: ['sdGroupHeader']; when: isGroupHeaderRow\"\n class=\"c-group-row-tr\"></tr>\n\n <!-- Data row \u2014 d\u00F9ng displayedColumns \u0111\u1EA7y \u0111\u1EE7; when isDataRow \u0111\u1EC3 lo\u1EA1i tr\u1EEB group header. -->\n <tr\n mat-row\n cdkDrag\n [cdkDragData]=\"row\"\n [cdkDragDisabled]=\"\n !_tableOption.rowReorder?.enabled ||\n !!row.meta?.group?.items?.length ||\n (row.meta?.tree?.level ?? 0) > 0 ||\n isReorderDisabled(row)\n \"\n *matRowDef=\"let row; columns: _configuration.displayedColumns; when: isDataRow\"\n matRipple\n class=\"c-row\"\n [class.sd-tree-row]=\"!!_tableOption.tree\"\n [ngClass]=\"_tableOption.tree ? 'sd-tree-level-' + (row.meta?.tree?.level ?? 0) : null\"\n [ngStyle]=\"rowStyle(row)\"\n [class.selected]=\"row.meta.selector.isSelected\"></tr>\n\n <!-- Sub-information row \u2014 also gated b\u1EDFi isDataRow \u0111\u1EC3 KH\u00D4NG render empty expand row d\u01B0\u1EDBi group header. -->\n <tr mat-row *matRowDef=\"let row; columns: ['sdSubInformation']; when: isDataRow\" class=\"c-detail-row\"></tr>\n @if (hasFooter() && !!_configuration.displayedFooters.length) {\n <tr mat-footer-row *matFooterRowDef=\"_configuration.displayedFooters; sticky: true\"></tr>\n }\n </table>\n @if (!_loading && !_total) {\n <div class=\"c-no-data-row\">\n @if (_isFiltered) {\n @if (tableConfiguration?.images?.filterEmpty) {\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.filterEmpty\" alt=\"filter-empty\" />\n } @else {\n <img class=\"c-image sd-image-filter-empty\" alt=\"filter-empty\" />\n }\n <div class=\"T16M\">{{ 'core.component.table.no-results' | translate }}</div>\n <div class=\"T16R text-secondary\">{{ 'core.component.table.no-results-hint' | translate }}</div>\n } @else {\n @if (_requireFiltered) {\n @if (tableConfiguration?.images?.filterRequired) {\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.filterRequired\" alt=\"filter-required\" />\n } @else {\n <img class=\"c-image sd-image-filter-required\" alt=\"filter-required\" />\n }\n <div class=\"T16R text-secondary\">{{ 'core.component.table.choose-filter-hint' | translate }}</div>\n } @else {\n @if (tableConfiguration?.images?.dataEmpty) {\n <img class=\"c-image\" [src]=\"tableConfiguration!.images!.dataEmpty\" alt=\"data-empty\" />\n } @else {\n <img class=\"c-image sd-image-data-empty\" alt=\"data-empty\" />\n }\n <div class=\"T16R text-secondary\">{{ 'core.component.table.no-data' | translate }}</div>\n }\n }\n </div>\n }\n </div>\n <div class=\"c-paginator\">\n <div class=\"c-action\">\n <ng-container *sdDesktop>\n @if (_tableOption.reload?.visible) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-reload' : null\"\n class=\"mr-8\"\n [title]=\"'core.component.table.reload' | translate\"\n prefixIcon=\"refresh\"\n (click)=\"reload()\"\n [disabled]=\"!_items.length\"\n type=\"link\">\n </sd-button>\n }\n </ng-container>\n @if (_export && _items.length) {\n @if (_export.type === 'custom') {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportCustom()\" type=\"link\"> </sd-button>\n } @else {\n @if (_export.visible) {\n @if (_exporting) {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [loading]=\"_exporting\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" type=\"link\"> </sd-button>\n } @else {\n @if (_export.visible === 'ALL' || !_export.visible) {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" [matMenuTriggerFor]=\"menu\" type=\"link\"> </sd-button>\n <mat-menu #menu=\"matMenu\">\n <button mat-menu-item [attr.data-autoid]=\"_autoId ? _autoId + '-export-excel' : null\" (click)=\"exportExcel()\" type=\"button\">\n <mat-icon fontSet=\"material-icons-outlined\">file_download</mat-icon>\n <span> {{ 'core.component.table.export-excel' | translate }}</span>\n </button>\n <button mat-menu-item [attr.data-autoid]=\"_autoId ? _autoId + '-export-csv' : null\" (click)=\"exportCSV()\" type=\"button\">\n <mat-icon fontSet=\"material-icons-outlined\">file_download</mat-icon>\n <span> {{ 'core.component.table.export-csv' | translate }}</span>\n </button>\n </mat-menu>\n } @else if (_export.visible === 'EXCEL') {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export-excel' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportExcel()\" type=\"link\"> </sd-button>\n } @else if (_export.visible === 'CSV') {\n <sd-button [autoId]=\"_autoId ? _autoId + '-export-csv' : null\" class=\"mr-8\" [title]=\"_exportTitle\" prefixIcon=\"get_app\" (click)=\"exportCSV()\" type=\"link\"> </sd-button>\n }\n }\n }\n }\n }\n\n <ng-container *sdDesktop>\n @if (_configComponent) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-config' : null\"\n class=\"mr-8\"\n [title]=\"'core.component.table.setup-short' | translate\"\n prefixIcon=\"settings\"\n (click)=\"_configComponent.open()\"\n type=\"link\">\n </sd-button>\n }\n </ng-container>\n <!-- Mobile: n\u00FAt Filter m\u1EDF mobile-filter drawer (inline filter \u1EA9n tr\u00EAn mobile) -->\n <ng-container *sdMobile>\n @if (!_tableOption.filter?.disabled && _mobileFilter) {\n <sd-button\n [autoId]=\"_autoId ? _autoId + '-mobile-filter-open' : null\"\n class=\"mr-8\"\n prefixIcon=\"filter_alt\"\n (click)=\"_mobileFilter.open()\"\n type=\"link\">\n </sd-button>\n }\n </ng-container>\n </div>\n @let hidePaginator = !_tableOption.paginate?.pageSize || _total === undefined || _total <= _tableOption.paginate?.pageSize!;\n <!-- Desktop paginator: full config (pageSize dropdown, first/last buttons) -->\n <ng-container *sdDesktop>\n <mat-paginator\n [class.d-none]=\"_tableOption.paginate?.hidden || hidePaginator\"\n [length]=\"_total\"\n [pageSize]=\"_tableOption.paginate?.pageSize\"\n [pageSizeOptions]=\"_tableOption.paginate?.pages!\"\n [showFirstLastButtons]=\"_tableOption.paginate?.showFirstLastButtons\"\n [hidePageSize]=\"_tableOption.paginate?.hidePageSize\"></mat-paginator>\n </ng-container>\n <!-- Mobile paginator: \u1EA9n pageSize dropdown + first/last buttons \u0111\u1EC3 ti\u1EBFt ki\u1EC7m kh\u00F4ng gian -->\n <ng-container *sdMobile>\n <mat-paginator\n [class.d-none]=\"_tableOption.paginate?.hidden || hidePaginator\"\n [length]=\"_total\"\n [pageSize]=\"_tableOption.paginate?.pageSize\"\n [showFirstLastButtons]=\"false\"\n hidePageSize></mat-paginator>\n </ng-container>\n <!-- \"\u0110ang hi\u1EC3n th\u1ECB...\" ch\u1EC9 desktop -->\n <ng-container *sdDesktop>\n @if (!_tableOption.paginate?.hidden && hidePaginator && _total !== undefined && _total > 0) {\n <div class=\"T14R pr-16\">\n {{ 'core.component.table.showing' | translate }} <span class=\"T14M ml-2\">1-{{ _total }}/{{ _total }}</span>\n </div>\n }\n </ng-container>\n </div>\n </div>\n <selector-action [autoId]=\"_autoId\" [tableOption]=\"_tableOption\" [selectedTableItems]=\"_selectedTableItems\" (clear)=\"onClearSelection(groupedItems)\" />\n @if (_tableOption.config?.visible) {\n <config [autoId]=\"_autoId\" [tableOption]=\"_tableOption\" />\n }\n <!-- Mobile filter drawer \u2014 ch\u1EC9 instantiate khi c\u1EA7n render tr\u00EAn mobile -->\n @if (!_tableOption.filter?.disabled && filterRegister) {\n <mobile-filter\n [autoId]=\"_autoId\"\n [filter]=\"_tableOption.filter!\"\n [externalFilters]=\"_tableOption.filter?.externalFilters!\"\n [columns]=\"_configuration.firstColumns\"\n [filterDefs]=\"sdFilterDefs()\"\n [filterRegister]=\"filterRegister\"\n [cacheValues]=\"cacheValues\">\n </mobile-filter>\n }\n}\n", styles: [":host{display:flex;flex-direction:column;overflow:auto;width:100%;height:100%}:host .c-header-title{height:40px;display:flex;align-items:center}:host .c-container{position:relative;min-height:50px;display:flex;flex-direction:column;flex:1}:host .c-container .c-table{position:relative;flex:1;display:flex;flex-direction:column}:host .c-container .c-table table{border-collapse:separate;width:100%}:host .c-container .c-table table tr.c-first-header.mat-mdc-header-row{height:40px}:host .c-container .c-table table tr.c-second-header.mat-mdc-header-row{height:40px}:host .c-container .c-table table tr.c-detail-row{height:0}:host .c-container .c-table table tr.c-row.activated{background-color:#e5ecff}:host .c-container .c-table table tr.c-row.selected{background-color:#eef2ff}:host .c-container .c-table table tr.c-row:not(.selected):not(.activated):hover{background-color:#f5f5f5}:host .c-container .c-table table tr.c-row td{border-bottom-width:0}:host .c-container .c-table table tr.c-row.c-expandable{cursor:pointer}:host .c-container .c-table table tr.c-row.c-expandable:hover{background:#f5f5f5}:host .c-container .c-table table th.mat-mdc-header-cell{background-color:#f2f3f4;border-bottom:0!important}:host .c-container .c-table table td.mat-mdc-cell,:host .c-container .c-table table td.mat-mdc-footer-cell,:host .c-container .c-table table th.mat-mdc-header-cell{border-bottom-color:#f2f2f2!important}:host .c-container .c-table .c-th{vertical-align:middle;font-weight:500;font-size:14px;line-height:20px;color:#212121}:host .c-container .c-table .c-th.sd-col-resize-host{position:relative}:host .c-container .c-table .c-th.sd-resizing{-webkit-user-select:none;user-select:none}:host .c-container .c-table .c-th .sd-col-resize-handle{position:absolute;top:0;right:0;width:6px;height:100%;cursor:col-resize;-webkit-user-select:none;user-select:none;z-index:2}:host .c-container .c-table .c-th .sd-col-resize-handle:hover{background:#00000014}:host .c-container .c-table .c-td:first{padding-left:10px}:host .c-container .c-table .c-no-data-row{flex:1;width:100%;position:sticky;left:0;display:flex;flex-direction:column;align-items:center;justify-content:center}:host .c-container .c-table .c-no-data-row .c-image{margin-bottom:16px;width:96px}:host .c-container .c-loading{position:absolute;inset:0 0 56px;background:#00000026;z-index:2;display:flex;align-items:center;justify-content:center}:host .c-container .c-paginator{display:flex;flex-direction:row;justify-content:space-between;align-items:center;background-color:#fff}:host .c-container .c-paginator .c-action{padding:5px}:host .c-container .c-empty{text-align:center;background-color:#fff;border:none!important}:host .c-container .c-empty mat-icon{font-size:150px;margin-top:30px;margin-bottom:30px;opacity:.2;width:auto;height:auto}:host button.c-btn-add{background-color:#fff;box-shadow:0 2px 4px #2f313629}:host .lds-ring{display:inline-block;position:relative;width:40px;height:40px}:host .lds-ring div{box-sizing:border-box;display:block;position:absolute;width:32px;height:32px;margin:4px;border:4px solid #cef;border-radius:50%;animation:lds-ring 1.2s cubic-bezier(.5,0,.5,1) infinite;border-color:#cef transparent transparent transparent}:host .lds-ring div:nth-child(1){animation-delay:-.45s}:host .lds-ring div:nth-child(2){animation-delay:-.3s}:host .lds-ring div:nth-child(3){animation-delay:-.15s}@keyframes lds-ring{0%{transform:rotate(0)}to{transform:rotate(360deg)}}:host .sd-reorder-header,:host .sd-reorder-cell{width:40px;min-width:40px;padding:0 4px;box-sizing:border-box}:host .sd-reorder-cell mat-icon{cursor:grab;color:#00000061;display:flex;align-items:center}:host .sd-reorder-cell mat-icon:active{cursor:grabbing}:host .sd-reorder-cell mat-icon.sd-reorder-disabled{opacity:.3;cursor:not-allowed;pointer-events:none}:host ::ng-deep .mat-sort-header-content{display:block;text-align:left;width:100%}:host ::ng-deep .mat-select-arrow{color:#a6a6a6}:host ::ng-deep .mat-sort-header-disabled{background-image:none!important;cursor:default!important;padding-right:0!important}:host ::ng-deep .mat-sort-header-container{align-items:start!important}:host ::ng-deep .mat-sort-header-arrow{margin-top:4px!important}:host ::ng-deep .mat-sort-header-arrow{display:none!important}:host ::ng-deep .mat-sort-header{cursor:pointer}:host ::ng-deep .mat-sort-header[aria-sort]{background-repeat:no-repeat;background-position:center right 0;background-size:16px 16px;cursor:pointer;padding-right:24px}:host ::ng-deep .mat-sort-header[aria-sort=none]{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath fill='%237A7A7A' d='M12 5.83L15.17 9l1.41-1.41L12 3 7.41 7.59 8.83 9 12 5.83zm0 12.34L8.83 15l-1.41 1.41L12 21l4.59-4.59L15.17 15 12 18.17z'/%3E%3C/svg%3E\")}:host ::ng-deep .mat-sort-header[aria-sort=ascending]{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath fill='%237A7A7A' d='M4 12l1.41 1.41L11 7.83V20h2V7.83l5.58 5.59L20 12l-8-8-8 8z'/%3E%3C/svg%3E\")}:host ::ng-deep .mat-sort-header[aria-sort=descending]{background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' height='24px' viewBox='0 0 24 24' width='24px' fill='%23000000'%3E%3Cpath d='M0 0h24v24H0V0z' fill='none'/%3E%3Cpath fill='%237A7A7A' d='M20 12l-1.41-1.41L13 16.17V4h-2v12.17l-5.58-5.59L4 12l8 8 8-8z'/%3E%3C/svg%3E\")}:host ::ng-deep .c-paginator .mat-mdc-paginator .mdc-text-field--outlined .mat-mdc-form-field-infix,:host ::ng-deep .c-paginator .mat-mdc-paginator .mdc-text-field--no-label .mat-mdc-form-field-infix{padding-top:2px;padding-bottom:2px;min-height:32px}:host ::ng-deep .c-paginator .mat-mdc-paginator-range-label{margin:0 4px}:host ::ng-deep .c-paginator .mat-mdc-paginator-page-size-select mat-select{margin-top:2px!important}:host ::ng-deep .cdk-drag-preview{box-shadow:0 4px 12px #00000026;background:#fff;display:table;width:100%}:host ::ng-deep .cdk-drag-preview td{padding:0 8px;border-bottom:1px solid rgba(0,0,0,.12)}:host ::ng-deep .cdk-drag-placeholder{opacity:0}:host ::ng-deep .cdk-drop-list-dragging tr.c-row:not(.cdk-drag-placeholder){transition:transform .25s cubic-bezier(0,0,.2,1)}:host ::ng-deep .sd-selection-cell{width:42px;min-width:42px;max-width:42px}:host ::ng-deep .sd-filler-cell{width:auto;min-width:0;padding:0!important;border:0}:host ::ng-deep .sd-group-header-cell{background:#f5f7fc;border-top:1px solid #e6e9f2;border-bottom:1px solid #e6e9f2}:host ::ng-deep .sd-group-row{min-height:36px}:host ::ng-deep .sd-group-selection-slot{width:42px;min-width:42px;max-width:42px}:host ::ng-deep .c-selection{transform:scale(.85);transform-origin:center}:host ::ng-deep .sd-tree-line{display:inline-flex;align-items:center;min-height:32px}:host ::ng-deep .sd-tree-indent{flex:0 0 auto}:host ::ng-deep .sd-tree-toggle-slot{flex:0 0 auto;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px}:host ::ng-deep .sd-tree-toggle-btn{display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;padding:0;border:0;background:transparent;border-radius:50%;cursor:pointer;color:var(--sd-text-secondary, #5b6472);transition:background-color .15s ease}:host ::ng-deep .sd-tree-toggle-btn mat-icon{font-size:18px;width:18px;height:18px;line-height:18px}:host ::ng-deep .sd-tree-toggle-btn:hover{background-color:#0f172a14}:host ::ng-deep .sd-tree-spinner{transform:scale(.45)}:host ::ng-deep .sd-tree-stt{margin-left:2px;white-space:nowrap}:host ::ng-deep .sd-tree-index-cell{text-align:left!important;white-space:nowrap;padding-left:8px;padding-right:8px}:host ::ng-deep .sd-tree-index-header{padding-left:32px}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-0{background:#fbfcfe}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-0>td{font-weight:500}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-1{background:#fff}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-2{background:#fafbfd}:host ::ng-deep tr.c-row.sd-tree-row.sd-tree-level-3{background:#f6f8fb}:host ::ng-deep .sd-stt-root{font-weight:700;color:#1f2330}\n", ":host ::ng-deep .sticky-shadow-right{overflow:visible!important}:host ::ng-deep .sticky-shadow-right:after{content:\"\";position:absolute;top:0;right:0;bottom:0;width:8px;transform:translate(100%);background:linear-gradient(to right,rgba(0,0,0,.12),transparent);pointer-events:none}:host ::ng-deep .sticky-shadow-left{overflow:visible!important}:host ::ng-deep .sticky-shadow-left:after{content:\"\";position:absolute;top:0;left:0;bottom:0;width:8px;transform:translate(-100%);background:linear-gradient(to left,rgba(0,0,0,.12),transparent);pointer-events:none}\n"] }]
4469
4608
  }], ctorParameters: () => [] });
4470
4609
 
4471
- const SdConvertToPagingReq = (filterRequest, args) => {
4472
- const { externalFilters, columns, fieldMapping } = args;
4473
- const req = {
4474
- filters: [],
4475
- orders: args?.orders || [],
4476
- pageNumber: filterRequest.pageNumber,
4477
- pageSize: filterRequest.pageSize,
4478
- };
4479
- const { filters } = req;
4480
- const { rawExternalFilter, rawColumnFilter, columnOperator, orderBy, orderDirection } = filterRequest;
4481
- // Xử lý external filter
4482
- for (const externalFilter of externalFilters || []) {
4483
- const value = rawExternalFilter?.[externalFilter.field];
4484
- const field = fieldMapping?.[externalFilter.field] || externalFilter.field;
4485
- // Nếu có giá trị thì mới xử lý filter
4486
- if (value !== undefined && value !== null && value !== '') {
4487
- if (externalFilter.type === 'string') {
4488
- filters.push({
4489
- field,
4490
- operator: externalFilter.defaultOperator || 'CONTAIN',
4491
- data: value,
4492
- });
4493
- }
4494
- else if (externalFilter.type === 'boolean') {
4495
- filters.push({
4496
- field,
4497
- operator: 'EQUAL',
4498
- data: value === true || value === 1 || value === 'true' || value === '1',
4499
- });
4500
- }
4501
- else if (externalFilter.type === 'date') {
4502
- if (typeof value === 'object' && 'from' in value && 'to' in value) {
4503
- if (value?.from) {
4504
- filters.push({
4505
- field,
4506
- operator: 'GREATER_OR_EQUAL',
4507
- data: DateUtilities$1.begin(value?.from).toISOString(),
4508
- });
4509
- }
4510
- if (value?.to) {
4511
- filters.push({
4512
- field,
4513
- operator: 'LESS_THAN',
4514
- data: DateUtilities$1.begin(DateUtilities$1.addDays(value?.to, 1)).toISOString(),
4515
- });
4516
- }
4517
- }
4518
- else {
4519
- if (DateUtilities$1.isDate(value)) {
4520
- if (externalFilter.defaultOperator === 'GREATER_OR_EQUAL') {
4521
- filters.push({
4522
- field,
4523
- operator: 'GREATER_OR_EQUAL',
4524
- data: DateUtilities$1.begin(value).toISOString(),
4525
- });
4526
- }
4527
- if (externalFilter.defaultOperator === 'LESS_OR_EQUAL') {
4528
- filters.push({
4529
- field,
4530
- operator: 'LESS_THAN',
4531
- data: DateUtilities$1.begin(DateUtilities$1.addDays(value, 1)).toISOString(),
4532
- });
4533
- }
4534
- }
4535
- }
4536
- }
4537
- else {
4538
- if (Array.isArray(value)) {
4539
- if (value.length) {
4540
- filters.push({
4541
- field,
4542
- operator: 'IN',
4543
- data: value,
4544
- });
4545
- }
4546
- }
4547
- else {
4548
- filters.push({
4549
- field,
4550
- operator: externalFilter.defaultOperator || 'EQUAL',
4551
- data: value,
4552
- });
4553
- }
4554
- }
4555
- }
4556
- }
4557
- // Xử lý column filter
4558
- for (const column of columns || []) {
4559
- const value = rawColumnFilter?.[column.field];
4560
- const field = fieldMapping?.[column.field] || column.field;
4561
- const operator = columnOperator?.[column.field] || column.filter?.operator?.default;
4562
- // Nếu có giá trị thì mới xử lý filter
4563
- if (value !== undefined && value !== null && value !== '') {
4564
- if (column.type === 'string') {
4565
- filters.push({
4566
- field,
4567
- operator: operator || 'CONTAIN',
4568
- data: value,
4569
- });
4570
- }
4571
- else if (column.type === 'boolean') {
4572
- filters.push({
4573
- field,
4574
- operator: 'EQUAL',
4575
- data: value === true || value === 1 || value === 'true' || value === '1',
4576
- });
4577
- }
4578
- else if (column.type === 'date' || column.type === 'datetime') {
4579
- if (typeof value === 'object' && 'from' in value && 'to' in value) {
4580
- if (value?.from && value?.to) {
4581
- filters.push({
4582
- field,
4583
- operator: 'BETWEEN',
4584
- data: {
4585
- from: DateUtilities$1.begin(value?.from).toISOString(),
4586
- to: DateUtilities$1.end(value?.to).toISOString(),
4587
- },
4588
- });
4589
- }
4590
- else if (value?.from) {
4591
- filters.push({
4592
- field,
4593
- operator: 'GREATER_OR_EQUAL',
4594
- data: DateUtilities$1.begin(value?.from).toISOString(),
4595
- });
4596
- }
4597
- else if (value?.to) {
4598
- filters.push({
4599
- field,
4600
- operator: 'LESS_THAN',
4601
- data: DateUtilities$1.begin(DateUtilities$1.addDays(value?.to, 1)).toISOString(),
4602
- });
4603
- }
4604
- }
4605
- else {
4606
- if (DateUtilities$1.isDate(value)) {
4607
- filters.push({
4608
- field,
4609
- operator: 'BETWEEN',
4610
- data: {
4611
- from: DateUtilities$1.begin(value).toISOString(),
4612
- to: DateUtilities$1.end(value).toISOString(),
4613
- },
4614
- });
4615
- }
4616
- }
4617
- }
4618
- else {
4619
- if (Array.isArray(value)) {
4620
- if (value.length) {
4621
- filters.push({
4622
- field,
4623
- operator: 'IN',
4624
- data: value,
4625
- });
4626
- }
4627
- }
4628
- else {
4629
- filters.push({
4630
- field,
4631
- operator: operator || 'EQUAL',
4632
- data: value,
4633
- });
4634
- }
4635
- }
4636
- }
4637
- }
4638
- // Xử lý orders
4639
- if (orderBy && orderDirection) {
4640
- req.orders.push({
4641
- field: orderBy,
4642
- direction: orderDirection,
4643
- });
4644
- }
4645
- return req;
4646
- };
4647
-
4648
4610
  /**
4649
4611
  * Generated bundle index. Do not edit.
4650
4612
  */