@sd-angular/core 19.0.44 → 19.0.45

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 (66) hide show
  1. package/README.md +4 -0
  2. package/assets/scss/core/form.scss +21 -40
  3. package/assets/scss/core/view.scss +24 -0
  4. package/assets/scss/sd-core.scss +1 -0
  5. package/assets/scss/themes/_material-tokens.scss +71 -0
  6. package/assets/scss/themes/default.scss +10 -0
  7. package/components/form-generic/src/components/form-builder/components/expression-builder/expression-builder.component.d.ts +1 -0
  8. package/components/form-generic/src/models/form-generic-expression.model.d.ts +10 -4
  9. package/components/form-generic/src/pipes/component-viewed.pipe.d.ts +1 -0
  10. package/components/import-excel/src/pipes/column-transform.pipe.d.ts +1 -0
  11. package/components/inform/src/inform.component.d.ts +12 -3
  12. package/components/table/src/services/table-export/table-export.service.d.ts +1 -1
  13. package/components/table/src/services/table-filter/table-filter.model.d.ts +7 -0
  14. package/components/table/src/services/table-local/table-local-sort.util.d.ts +8 -2
  15. package/components/table/src/services/table-local/table-local.util.d.ts +6 -1
  16. package/components/table/src/table.component.d.ts +1 -1
  17. package/components/timeline/pipes/view-date.pipe.d.ts +1 -0
  18. package/configurations/src/sd-core.configuration.d.ts +7 -0
  19. package/fesm2022/sd-angular-core-components-form-generic.mjs +44 -31
  20. package/fesm2022/sd-angular-core-components-form-generic.mjs.map +1 -1
  21. package/fesm2022/sd-angular-core-components-import-excel.mjs +19 -5
  22. package/fesm2022/sd-angular-core-components-import-excel.mjs.map +1 -1
  23. package/fesm2022/sd-angular-core-components-inform.mjs +16 -2
  24. package/fesm2022/sd-angular-core-components-inform.mjs.map +1 -1
  25. package/fesm2022/sd-angular-core-components-section.mjs +2 -2
  26. package/fesm2022/sd-angular-core-components-section.mjs.map +1 -1
  27. package/fesm2022/sd-angular-core-components-table.mjs +174 -66
  28. package/fesm2022/sd-angular-core-components-table.mjs.map +1 -1
  29. package/fesm2022/sd-angular-core-components-timeline.mjs +4 -1
  30. package/fesm2022/sd-angular-core-components-timeline.mjs.map +1 -1
  31. package/fesm2022/sd-angular-core-components-view.mjs +2 -2
  32. package/fesm2022/sd-angular-core-components-view.mjs.map +1 -1
  33. package/fesm2022/sd-angular-core-configurations.mjs.map +1 -1
  34. package/fesm2022/sd-angular-core-forms-checkbox.mjs +2 -2
  35. package/fesm2022/sd-angular-core-forms-checkbox.mjs.map +1 -1
  36. package/fesm2022/sd-angular-core-forms-date-range.mjs +15 -2
  37. package/fesm2022/sd-angular-core-forms-date-range.mjs.map +1 -1
  38. package/fesm2022/sd-angular-core-forms-date.mjs +11 -1
  39. package/fesm2022/sd-angular-core-forms-date.mjs.map +1 -1
  40. package/fesm2022/sd-angular-core-forms-datetime.mjs +43 -9
  41. package/fesm2022/sd-angular-core-forms-datetime.mjs.map +1 -1
  42. package/fesm2022/sd-angular-core-forms-models.mjs +6 -0
  43. package/fesm2022/sd-angular-core-forms-models.mjs.map +1 -1
  44. package/fesm2022/sd-angular-core-forms-radio.mjs +2 -2
  45. package/fesm2022/sd-angular-core-forms-radio.mjs.map +1 -1
  46. package/fesm2022/sd-angular-core-forms-switch.mjs +2 -2
  47. package/fesm2022/sd-angular-core-forms-switch.mjs.map +1 -1
  48. package/fesm2022/sd-angular-core-forms-textarea.mjs +2 -2
  49. package/fesm2022/sd-angular-core-forms-textarea.mjs.map +1 -1
  50. package/fesm2022/sd-angular-core-pipes.mjs +26 -10
  51. package/fesm2022/sd-angular-core-pipes.mjs.map +1 -1
  52. package/fesm2022/sd-angular-core-services-date-time.mjs +143 -0
  53. package/fesm2022/sd-angular-core-services-date-time.mjs.map +1 -0
  54. package/fesm2022/sd-angular-core-services.mjs +1 -0
  55. package/fesm2022/sd-angular-core-services.mjs.map +1 -1
  56. package/fesm2022/sd-angular-core-utilities-models.mjs.map +1 -1
  57. package/forms/datetime/src/datetime-time-zone.adapter.d.ts +15 -0
  58. package/forms/models/src/sd-strict-date-adapter.d.ts +3 -0
  59. package/package.json +10 -6
  60. package/pipes/src/time-different.pipe.d.ts +1 -0
  61. package/services/date-time/index.d.ts +1 -0
  62. package/services/date-time/src/date-time.service.d.ts +54 -0
  63. package/services/date-time/src/public-api.d.ts +1 -0
  64. package/services/index.d.ts +1 -0
  65. package/utilities/extensions/src/string.extension.d.ts +1 -0
  66. package/utilities/models/src/icon.model.d.ts +2 -0
@@ -1,6 +1,7 @@
1
1
  import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, Pipe, SecurityContext, Input, ChangeDetectionStrategy, Component, ViewChild, EventEmitter, Output, ViewChildren, QueryList, ChangeDetectorRef, input, effect, untracked, viewChild, signal, computed } from '@angular/core';
3
3
  import { Utilities, StringUtilities as StringUtilities$1 } from '@sdcorejs/utils/fns';
4
+ import { ɵSD_LOCAL_DATE_TIME as _SD_LOCAL_DATE_TIME, ɵsdInjectDateTime as _sdInjectDateTime, SdDateTimeService } from '@sd-angular/core/services/date-time';
4
5
  import { DateUtilities } from '@sd-angular/core/utilities';
5
6
  import * as i3 from '@angular/cdk/drag-drop';
6
7
  import { moveItemInArray, transferArrayItem, DragDropModule } from '@angular/cdk/drag-drop';
@@ -19,9 +20,9 @@ import { SdNotifyService, SdConfirmService } from '@sd-angular/core/services';
19
20
  import { Subscription, Subject, filter, debounceTime, startWith, combineLatest } from 'rxjs';
20
21
  import { SdSection } from '@sd-angular/core/components/section';
21
22
  import { startWith as startWith$1, debounceTime as debounceTime$1 } from 'rxjs/operators';
22
- import { DateUtilities as DateUtilities$1, StringUtilities, ArrayUtilities, NumberUtilities } from '@sd-angular/core/utilities/extensions';
23
23
  import { EMPTY_STR } from '@sdcorejs/utils/constants';
24
24
  import * as i1 from '@sd-angular/core/pipes';
25
+ import { StringUtilities, ArrayUtilities, NumberUtilities } from '@sd-angular/core/utilities/extensions';
25
26
  import * as i1$1 from '@angular/platform-browser';
26
27
  import { SdInput, SdChip, SdChipCalendar, SdSelect, SdAutocomplete, SdRadio, SdInputNumber as SdInputNumber$1 } from '@sd-angular/core/forms';
27
28
  import { SdInputNumber } from '@sd-angular/core/forms/input-number';
@@ -262,23 +263,25 @@ const DayInfoPreviouses = [
262
263
  // { value: 'LASTMONTH', display: 'Tháng trước', format: (n: number) => `${n || 1}LastMonth` },
263
264
  // { value: 'NEXTMONTH', display: 'Tháng tới', format: (n: number) => `${n || 1}NextMonth` },
264
265
  ];
265
- const GetDatetimeValue = (value) => {
266
+ // why: a relative value is the current instant shifted by whole days, formatted once in the zone; formatting
267
+ // a wall-clock carrier such as now() in the zone would shift it a second time.
268
+ const GetDatetimeValue = (value, dateTime = _SD_LOCAL_DATE_TIME) => {
266
269
  if (!value || typeof value !== 'string') {
267
270
  return undefined;
268
271
  }
269
272
  if (value === 'now') {
270
- return DateUtilities.toFormat(new Date(), 'yyyy/MM/dd HH:mm:ss');
273
+ return dateTime.toFormat(new Date(), 'yyyy/MM/dd HH:mm:ss');
271
274
  }
272
275
  if (value?.endsWith('LastDay')) {
273
276
  const relatedValue = +value.replace('LastDay', '');
274
277
  if (relatedValue) {
275
- return DateUtilities.toFormat(DateUtilities.addDays(new Date(), -relatedValue), 'yyyy/MM/dd HH:mm:ss');
278
+ return dateTime.toFormat(DateUtilities.addDays(new Date(), -relatedValue), 'yyyy/MM/dd HH:mm:ss');
276
279
  }
277
280
  }
278
281
  if (value?.endsWith('NextDay')) {
279
282
  const relatedValue = +value.replace('LastDay', '');
280
283
  if (relatedValue) {
281
- return DateUtilities.toFormat(DateUtilities.addDays(new Date(), relatedValue), 'yyyy/MM/dd HH:mm:ss');
284
+ return dateTime.toFormat(DateUtilities.addDays(new Date(), relatedValue), 'yyyy/MM/dd HH:mm:ss');
282
285
  }
283
286
  }
284
287
  return undefined;
@@ -405,7 +408,7 @@ const GetAttributes = (components) => {
405
408
  }
406
409
  return attributes;
407
410
  };
408
- const TemplateToCondition = (template, entity) => {
411
+ const TemplateToCondition = (template, entity, dateTime = _SD_LOCAL_DATE_TIME) => {
409
412
  if (!template) {
410
413
  return undefined;
411
414
  }
@@ -423,7 +426,7 @@ const TemplateToCondition = (template, entity) => {
423
426
  if (typeof val === 'string') {
424
427
  // Xử lý đối với trường hợp string là 'datetime' thì cần format thành string đồng nhất để compare chính xác
425
428
  if (DateUtilities.isDate(val)) {
426
- template = template.replace(match, `'${DateUtilities.toFormat(val, 'yyyy/MM/dd HH:mm:ss')}'`);
429
+ template = template.replace(match, `'${dateTime.toFormat(val, 'yyyy/MM/dd HH:mm:ss')}'`);
427
430
  }
428
431
  else {
429
432
  // Nếu là chuỗi thì thêm dấu nháy khi replace giá trị để so sánh, ví dụ 'a' === 'a'
@@ -448,14 +451,14 @@ const GetEntityValue = (entity, key) => {
448
451
  }
449
452
  return value;
450
453
  };
451
- const NormalizeExpressionValue = (value) => {
454
+ const NormalizeExpressionValue = (value, dateTime) => {
452
455
  if (typeof value === 'string' && DateUtilities.isDate(value)) {
453
- return DateUtilities.toFormat(value, 'yyyy/MM/dd HH:mm:ss');
456
+ return dateTime.toFormat(value, 'yyyy/MM/dd HH:mm:ss');
454
457
  }
455
458
  return value;
456
459
  };
457
- const EvaluateExpressionCondition = (condition, entity) => {
458
- const actualValue = NormalizeExpressionValue(GetEntityValue(entity, condition.field));
460
+ const EvaluateExpressionCondition = (condition, entity, dateTime) => {
461
+ const actualValue = NormalizeExpressionValue(GetEntityValue(entity, condition.field), dateTime);
459
462
  const { operator } = condition;
460
463
  if (!operator) {
461
464
  return undefined;
@@ -466,11 +469,11 @@ const EvaluateExpressionCondition = (condition, entity) => {
466
469
  if (operator === 'NOT_NULL') {
467
470
  return !!actualValue;
468
471
  }
469
- const expectedValue = typeof condition.value === 'string' ? GetDatetimeValue(condition.value) || condition.value : condition.value;
472
+ const expectedValue = typeof condition.value === 'string' ? GetDatetimeValue(condition.value, dateTime) || condition.value : condition.value;
470
473
  if (expectedValue === undefined || expectedValue === null || expectedValue === '') {
471
474
  return undefined;
472
475
  }
473
- const normalizedExpectedValue = NormalizeExpressionValue(expectedValue);
476
+ const normalizedExpectedValue = NormalizeExpressionValue(expectedValue, dateTime);
474
477
  if (operator === 'EQUAL') {
475
478
  return actualValue === normalizedExpectedValue;
476
479
  }
@@ -491,12 +494,12 @@ const EvaluateExpressionCondition = (condition, entity) => {
491
494
  }
492
495
  return undefined;
493
496
  };
494
- const EvaluateExpression = (condition, entity) => {
497
+ const EvaluateExpression = (condition, entity, dateTime = _SD_LOCAL_DATE_TIME) => {
495
498
  if (condition.type === 'combinator') {
496
499
  if (!condition.conditions?.length) {
497
500
  return undefined;
498
501
  }
499
- const results = condition.conditions.map(child => EvaluateExpression(child, entity));
502
+ const results = condition.conditions.map(child => EvaluateExpression(child, entity, dateTime));
500
503
  if (results.some(result => typeof result !== 'boolean')) {
501
504
  return undefined;
502
505
  }
@@ -505,14 +508,14 @@ const EvaluateExpression = (condition, entity) => {
505
508
  }
506
509
  return results.some(result => result === true);
507
510
  }
508
- return EvaluateExpressionCondition(condition, entity);
511
+ return EvaluateExpressionCondition(condition, entity, dateTime);
509
512
  };
510
- const ExpressionToJavascriptExpression = (condition) => {
513
+ const ExpressionToJavascriptExpression = (condition, dateTime = _SD_LOCAL_DATE_TIME) => {
511
514
  const queries = [];
512
515
  if (condition.type === 'combinator') {
513
516
  if (condition.conditions?.length) {
514
517
  for (const child of condition.conditions) {
515
- queries.push(`(${ExpressionToJavascriptExpression(child)})`);
518
+ queries.push(`(${ExpressionToJavascriptExpression(child, dateTime)})`);
516
519
  }
517
520
  }
518
521
  // Nếu nhiều hơn 1 điều kiện, bọc điều kiện bằng cặp ngoặc ()
@@ -525,10 +528,10 @@ const ExpressionToJavascriptExpression = (condition) => {
525
528
  }
526
529
  }
527
530
  else {
528
- return ConvertToJavascriptExpression(condition);
531
+ return ConvertToJavascriptExpression(condition, dateTime);
529
532
  }
530
533
  };
531
- const ConvertToJavascriptExpression = (condition) => {
534
+ const ConvertToJavascriptExpression = (condition, dateTime) => {
532
535
  const { value } = condition;
533
536
  // Convert thành syntax ${field}
534
537
  const field = '${' + condition.field + '}';
@@ -546,7 +549,7 @@ const ConvertToJavascriptExpression = (condition) => {
546
549
  return undefined;
547
550
  }
548
551
  // Xử lý đối với trường hợp string là 'datetime' thì value sẽ có thể là 'now', '1LastDay' ....
549
- const datetimeValue = GetDatetimeValue(value);
552
+ const datetimeValue = GetDatetimeValue(value, dateTime);
550
553
  if (operator === 'EQUAL') {
551
554
  // Nếu là chuỗi thì thêm dấu nháy, nếu không thì bỏ qua
552
555
  if (typeof value === 'string') {
@@ -789,6 +792,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
789
792
  class ComponentViewedPipe {
790
793
  formatNumberPipe;
791
794
  formGenericService;
795
+ // why: datetime values show as the wall clock of ISdCoreConfiguration.timeZone; outside DI it stays local.
796
+ #dateTime = _sdInjectDateTime() ?? _SD_LOCAL_DATE_TIME;
792
797
  constructor(formatNumberPipe, formGenericService) {
793
798
  this.formatNumberPipe = formatNumberPipe;
794
799
  this.formGenericService = formGenericService;
@@ -809,10 +814,10 @@ class ComponentViewedPipe {
809
814
  }
810
815
  if (component.type === 'datetime') {
811
816
  if (component.subtype === 'datetime') {
812
- return DateUtilities$1.toFormat(val, 'dd/MM/yyyy HH:mm') || EMPTY_STR;
817
+ return this.#dateTime.toFormat(val, 'dd/MM/yyyy HH:mm') || EMPTY_STR;
813
818
  }
814
819
  else {
815
- return DateUtilities$1.toFormat(val, 'dd/MM/yyyy') || EMPTY_STR;
820
+ return this.#dateTime.toFormat(val, 'dd/MM/yyyy') || EMPTY_STR;
816
821
  }
817
822
  }
818
823
  if (component.type === 'select' || component.type === 'radio' || component.type === 'checklist') {
@@ -876,13 +881,15 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.21", ngImpo
876
881
  /* eslint-disable @typescript-eslint/no-explicit-any */
877
882
  // Xử lý thông tin required, disabled, hidden của component
878
883
  class WhenExpressionPipe {
884
+ // why: date conditions compare in ISdCoreConfiguration.timeZone; `new WhenExpressionPipe()` outside DI stays local.
885
+ #dateTime = _sdInjectDateTime() ?? _SD_LOCAL_DATE_TIME;
879
886
  #hidden = (component, variables) => {
880
887
  if (component.properties?.hidden) {
881
888
  return true;
882
889
  }
883
890
  const hiddenWhenExpression = component.properties?.hiddenWhenExpression;
884
891
  if (hiddenWhenExpression) {
885
- const result = EvaluateExpression(hiddenWhenExpression, variables);
892
+ const result = EvaluateExpression(hiddenWhenExpression, variables, this.#dateTime);
886
893
  if (typeof result === 'boolean') {
887
894
  return result;
888
895
  }
@@ -890,7 +897,7 @@ class WhenExpressionPipe {
890
897
  }
891
898
  const visibleWhenExpression = component.properties?.visibleWhenExpression;
892
899
  if (visibleWhenExpression) {
893
- const result = EvaluateExpression(visibleWhenExpression, variables);
900
+ const result = EvaluateExpression(visibleWhenExpression, variables, this.#dateTime);
894
901
  if (typeof result === 'boolean') {
895
902
  return !result;
896
903
  }
@@ -901,7 +908,7 @@ class WhenExpressionPipe {
901
908
  #disabled = (component, variables) => {
902
909
  const disabledWhenExpression = component.properties?.disabledWhenExpression;
903
910
  if (disabledWhenExpression) {
904
- const result = EvaluateExpression(disabledWhenExpression, variables);
911
+ const result = EvaluateExpression(disabledWhenExpression, variables, this.#dateTime);
905
912
  if (typeof result === 'boolean') {
906
913
  return result;
907
914
  }
@@ -912,7 +919,7 @@ class WhenExpressionPipe {
912
919
  #required = (component, variables) => {
913
920
  const requiredWhenExpression = component.properties?.requiredWhenExpression;
914
921
  if (requiredWhenExpression) {
915
- const result = EvaluateExpression(requiredWhenExpression, variables);
922
+ const result = EvaluateExpression(requiredWhenExpression, variables, this.#dateTime);
916
923
  if (typeof result === 'boolean') {
917
924
  return result;
918
925
  }
@@ -2969,6 +2976,8 @@ class SdFormRender {
2969
2976
  setVariables = new Subject();
2970
2977
  ref = inject(ChangeDetectorRef);
2971
2978
  formGenericConfiguration = inject(SD_FORM_GENERIC_CONFIGURATION, { optional: true });
2979
+ // why: validation expressions compare dates in ISdCoreConfiguration.timeZone.
2980
+ #dateTime = inject(SdDateTimeService);
2972
2981
  ngAfterViewInit() {
2973
2982
  this.#subscription.add(combineLatest([this.#configurationChanges, this.#entityChanges])
2974
2983
  .pipe(startWith$1([]))
@@ -3032,7 +3041,7 @@ class SdFormRender {
3032
3041
  const messages = [];
3033
3042
  for (const validation of this.configuration?.validations?.filter(e => e.alert === alert) || []) {
3034
3043
  if (validation.type === 'expression') {
3035
- const result = EvaluateExpression(validation.expression, this.entity);
3044
+ const result = EvaluateExpression(validation.expression, this.entity, this.#dateTime);
3036
3045
  if (result) {
3037
3046
  messages.push(validation.message);
3038
3047
  }
@@ -3148,6 +3157,8 @@ class ExpressionBuilderComponent {
3148
3157
  modelChange = new EventEmitter();
3149
3158
  sdChange = new EventEmitter();
3150
3159
  edit = new EventEmitter();
3160
+ // why: a picked datetime is a model value; keep it in ISdCoreConfiguration.timeZone like the expressions that read it.
3161
+ #dateTime = inject(SdDateTimeService);
3151
3162
  constructor(ref) {
3152
3163
  this.ref = ref;
3153
3164
  }
@@ -3210,7 +3221,7 @@ class ExpressionBuilderComponent {
3210
3221
  condition.value = DayInfoPreviouses.find(e => e.value === condition.dayInfo.related)?.format(condition.dayInfo.relatedValue) ?? null;
3211
3222
  }
3212
3223
  else if (condition.value?.type === 'DATETIME' && condition.value) {
3213
- condition.value = DateUtilities$1.toFormat(condition.value, 'yyyy/MM/dd HH:mm:00');
3224
+ condition.value = this.#dateTime.toFormat(condition.value, 'yyyy/MM/dd HH:mm:00');
3214
3225
  }
3215
3226
  };
3216
3227
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.21", ngImport: i0, type: ExpressionBuilderComponent, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Component });
@@ -4141,8 +4152,10 @@ class DatetimeControl {
4141
4152
  set _component(component) {
4142
4153
  this.component = component;
4143
4154
  }
4144
- date = DateUtilities.toFormat(new Date(), 'dd/MM/yyyy');
4145
- datetime = DateUtilities.toFormat(new Date(), 'dd/MM/yyyy HH:mm');
4155
+ // why: the builder preview shows today as the app does, in ISdCoreConfiguration.timeZone.
4156
+ #dateTime = inject(SdDateTimeService);
4157
+ date = this.#dateTime.toFormat(new Date(), 'dd/MM/yyyy');
4158
+ datetime = this.#dateTime.toFormat(new Date(), 'dd/MM/yyyy HH:mm');
4146
4159
  #subscription = new Subscription();
4147
4160
  constructor(ref, builderService) {
4148
4161
  this.ref = ref;