@acorex/components 20.7.36 → 20.7.38

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.
@@ -6,16 +6,16 @@ import { AXDecoratorIconComponent, AXDecoratorGenericComponent } from '@acorex/c
6
6
  import { AXPopoverComponent } from '@acorex/components/popover';
7
7
  import { AXSelectBoxComponent } from '@acorex/components/select-box';
8
8
  import { AXCalendarService } from '@acorex/core/date-time';
9
+ import { AXEventService, AXSystemEvents } from '@acorex/core/events';
9
10
  import { AXFormatService, AXFormatPipe } from '@acorex/core/format';
10
11
  import { AXLocaleService } from '@acorex/core/locale';
11
- import { AXPlatform } from '@acorex/core/platform';
12
12
  import { AXTranslatorPipe, AXTranslationService } from '@acorex/core/translation';
13
13
  import { AXUnsubscriber } from '@acorex/core/utils';
14
14
  import * as i0 from '@angular/core';
15
15
  import { inject, signal, Injectable, DOCUMENT, input, computed, output, ViewEncapsulation, ChangeDetectionStrategy, Component, DestroyRef, viewChild, linkedSignal, model, effect, untracked, NgModule } from '@angular/core';
16
- import { map } from 'rxjs';
17
16
  import * as i1 from '@angular/forms';
18
17
  import { FormsModule } from '@angular/forms';
18
+ import { filter } from 'rxjs';
19
19
  import { isEqual, orderBy } from 'lodash-es';
20
20
  import { AXDragDirective, AXDropZoneDirective } from '@acorex/cdk/drag-drop';
21
21
  import { AXTooltipDirective } from '@acorex/components/tooltip';
@@ -25,6 +25,7 @@ class AXSchedulerService {
25
25
  constructor() {
26
26
  this.formatService = inject(AXFormatService);
27
27
  this.calendarService = inject(AXCalendarService);
28
+ this.localeService = inject(AXLocaleService);
28
29
  this.HOURS_PER_DAY = 24;
29
30
  this.MINUTES_PER_HOUR = 60;
30
31
  this.BLOCK_DURATION_MINUTES = 30;
@@ -322,6 +323,24 @@ class AXSchedulerService {
322
323
  return 1;
323
324
  }
324
325
  }
326
+ /**
327
+ * Resolves the 0-based week-start index (0 = Sunday … 6 = Saturday) from locale or an override.
328
+ */
329
+ resolveWeekStartsOn(firstDayOfWeek) {
330
+ if (firstDayOfWeek) {
331
+ return this.getDayOfWeekNumber(firstDayOfWeek) - 1;
332
+ }
333
+ return this.localeService.activeProfile().calendar.week.startsOn;
334
+ }
335
+ /**
336
+ * Aligns a date to the start of its week using locale week layout and calendar weekday indexing.
337
+ */
338
+ alignToWeekStart(date, options) {
339
+ const startsOn = this.resolveWeekStartsOn(options?.firstDayOfWeek);
340
+ const dayIndex = date.weekdayIndex;
341
+ const daysToSubtract = (dayIndex - startsOn + 7) % 7;
342
+ return date.startOf('day').add('day', -daysToSubtract);
343
+ }
325
344
  /**
326
345
  * Gets slot data for a given start date and view.
327
346
  *
@@ -562,9 +581,9 @@ class AXSchedulerService {
562
581
  const internalWeekend = this.internalWeekend();
563
582
  const globalWeekend = this.calendarService.resolveCalendar(calendar).weekend;
564
583
  if (internalWeekend.length === 0) {
565
- return globalWeekend.includes(date.dayOfWeek - 1);
584
+ return globalWeekend.includes(date.weekdayIndex);
566
585
  }
567
- return this.internalWeekend().includes(date.dayOfWeek - 1);
586
+ return this.internalWeekend().includes(date.weekdayIndex);
568
587
  }
569
588
  /**
570
589
  * Filters appointments by resources and unassigned appointments.
@@ -1398,7 +1417,7 @@ class AXSchedulerMonthViewComponent extends NXComponent {
1398
1417
  this.calendar = input(...(ngDevMode ? [undefined, { debugName: "calendar" }] : []));
1399
1418
  this.date = input.required(...(ngDevMode ? [{ debugName: "date" }] : []));
1400
1419
  this.appointments = input([], ...(ngDevMode ? [{ debugName: "appointments" }] : []));
1401
- this.firstDayOfWeek = input('saturday', ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : []));
1420
+ this.firstDayOfWeek = input(...(ngDevMode ? [undefined, { debugName: "firstDayOfWeek" }] : []));
1402
1421
  this.selectedAppointmentId = input(null, ...(ngDevMode ? [{ debugName: "selectedAppointmentId" }] : []));
1403
1422
  this.tooltipTemplate = input(...(ngDevMode ? [undefined, { debugName: "tooltipTemplate" }] : []));
1404
1423
  // --- Constants ---
@@ -1435,19 +1454,11 @@ class AXSchedulerMonthViewComponent extends NXComponent {
1435
1454
  }, ...(ngDevMode ? [{ debugName: "monthStartDate" }] : []));
1436
1455
  // Generate the list of weekday names based on the firstDayOfWeek
1437
1456
  this.daysArray = computed(() => {
1438
- const dayNames = [];
1439
- const currentDate = this.date(); // Use a stable date for generating week headers
1440
- const currentDayOfWeek = currentDate.dayOfWeek;
1441
- const desiredFirstDayName = this.firstDayOfWeek();
1442
- const desiredFirstDayNumber = this.schedulerService.getDayOfWeekNumber(desiredFirstDayName);
1443
- // Calculate days to subtract to get to the start of the week based on firstDayOfWeek
1444
- const daysToSubtract = (currentDayOfWeek - desiredFirstDayNumber + 7) % 7;
1445
- let headerStartDate = currentDate.add('day', -daysToSubtract);
1446
- for (let i = 0; i < 7; i++) {
1447
- dayNames.push(headerStartDate);
1448
- headerStartDate = headerStartDate.add('day', 1);
1449
- }
1450
- return dayNames;
1457
+ const currentDate = this.date();
1458
+ const headerStartDate = this.schedulerService.alignToWeekStart(currentDate, {
1459
+ firstDayOfWeek: this.firstDayOfWeek(),
1460
+ });
1461
+ return Array.from({ length: 7 }, (_, index) => headerStartDate.add('day', index));
1451
1462
  }, ...(ngDevMode ? [{ debugName: "daysArray" }] : []));
1452
1463
  // --- Calendar Grid Calculation ---
1453
1464
  this.calendarDaysInfo = computed(() => {
@@ -1456,10 +1467,11 @@ class AXSchedulerMonthViewComponent extends NXComponent {
1456
1467
  return { days: [], weeksNeeded: 0 };
1457
1468
  const firstDayOfMonth = monthStart;
1458
1469
  const lastDayOfMonth = monthStart.endOf('month');
1459
- const firstDayGridNum = this.schedulerService.getDayOfWeekNumber(this.firstDayOfWeek());
1460
- const firstDisplayedDayOfWeek = firstDayOfMonth.dayOfWeek;
1461
- const leadingDays = (firstDisplayedDayOfWeek - firstDayGridNum + this.DAYS_IN_WEEK) % this.DAYS_IN_WEEK;
1462
- const firstDayOfGrid = firstDayOfMonth.add('day', -leadingDays);
1470
+ const firstDayOfGrid = this.schedulerService.alignToWeekStart(firstDayOfMonth, {
1471
+ firstDayOfWeek: this.firstDayOfWeek(),
1472
+ });
1473
+ const leadingDays = Math.round((firstDayOfMonth.startOf('day').date.getTime() - firstDayOfGrid.startOf('day').date.getTime()) /
1474
+ this.schedulerService.MILLISECONDS_PER_DAY);
1463
1475
  // Determine how many total days to show to fill out the weeks
1464
1476
  // A common approach is 6 weeks to ensure consistency, but your dynamic weeksNeeded is fine too.
1465
1477
  // Let's stick to your dynamic `weeksNeeded` and `totalDaysToGenerate`
@@ -4520,13 +4532,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.3", ngImpor
4520
4532
  class AXSchedulerComponent extends NXComponent {
4521
4533
  constructor() {
4522
4534
  super(...arguments);
4523
- this.platformService = inject(AXPlatform);
4524
4535
  this.unsubscriber = inject(AXUnsubscriber);
4525
4536
  this.formatService = inject(AXFormatService);
4526
4537
  this.localeService = inject(AXLocaleService);
4527
4538
  this.calendarService = inject(AXCalendarService);
4528
4539
  this.schedulerService = inject(AXSchedulerService);
4529
4540
  this.translationService = inject(AXTranslationService);
4541
+ this.eventService = inject(AXEventService);
4542
+ this.translationRevision = signal(0, ...(ngDevMode ? [{ debugName: "translationRevision" }] : []));
4530
4543
  this.viewModeSelectbox = viewChild(AXSelectBoxComponent, ...(ngDevMode ? [{ debugName: "viewModeSelectbox" }] : []));
4531
4544
  this.calendarPopover = viewChild('calendarPopover', ...(ngDevMode ? [{ debugName: "calendarPopover" }] : []));
4532
4545
  this._calendarDayCellTemplate = viewChild('calendarDayCellTemplate', ...(ngDevMode ? [{ debugName: "_calendarDayCellTemplate" }] : []));
@@ -4543,12 +4556,32 @@ class AXSchedulerComponent extends NXComponent {
4543
4556
  agenda: 'agenda',
4544
4557
  };
4545
4558
  this.calendar = input(...(ngDevMode ? [undefined, { debugName: "calendar" }] : []));
4546
- this.calendarType = linkedSignal(() => this.calendar() ?? this.localeService.activeProfile().calendar.system, ...(ngDevMode ? [{ debugName: "calendarType" }] : []));
4559
+ this.calendarType = linkedSignal(() => this.localeService.activeProfile().calendar.system, ...(ngDevMode ? [{ debugName: "calendarType" }] : []));
4547
4560
  this.startingDate = input(new Date(), ...(ngDevMode ? [{ debugName: "startingDate" }] : []));
4548
- this.currentDate = linkedSignal(() => this.calendarService.create(this.startingDate(), this.calendarType()).startOf('day'), ...(ngDevMode ? [{ debugName: "currentDate" }] : []));
4561
+ this.currentDate = linkedSignal(...(ngDevMode ? [{ debugName: "currentDate", source: () => ({
4562
+ startingDate: this.startingDate(),
4563
+ calendarType: this.calendarType(),
4564
+ }),
4565
+ computation: (source, previous) => {
4566
+ if (previous) {
4567
+ return this.calendarService.create(previous.value.date, source.calendarType);
4568
+ }
4569
+ return this.calendarService.create(source.startingDate, source.calendarType).startOf('day');
4570
+ } }] : [{
4571
+ source: () => ({
4572
+ startingDate: this.startingDate(),
4573
+ calendarType: this.calendarType(),
4574
+ }),
4575
+ computation: (source, previous) => {
4576
+ if (previous) {
4577
+ return this.calendarService.create(previous.value.date, source.calendarType);
4578
+ }
4579
+ return this.calendarService.create(source.startingDate, source.calendarType).startOf('day');
4580
+ },
4581
+ }]));
4549
4582
  this._appointments = signal([], ...(ngDevMode ? [{ debugName: "_appointments" }] : []));
4550
4583
  this.appointments = this._appointments.asReadonly();
4551
- this.rtl = signal(this.platformService.isRtl(), ...(ngDevMode ? [{ debugName: "rtl" }] : []));
4584
+ this.rtl = this.localeService.isRtl;
4552
4585
  this.isFullScreen = signal(false, ...(ngDevMode ? [{ debugName: "isFullScreen" }] : []));
4553
4586
  this.viewsDataSource = signal([], ...(ngDevMode ? [{ debugName: "viewsDataSource" }] : []));
4554
4587
  this.prevDate = signal(this.calendarService.create(new Date(), this.calendarType()), ...(ngDevMode ? [{ debugName: "prevDate" }] : []));
@@ -4570,7 +4603,7 @@ class AXSchedulerComponent extends NXComponent {
4570
4603
  this.showUnassignedAppointments = input(true, ...(ngDevMode ? [{ debugName: "showUnassignedAppointments" }] : []));
4571
4604
  this.resources = input([], ...(ngDevMode ? [{ debugName: "resources" }] : []));
4572
4605
  this.resourceTemplate = input(...(ngDevMode ? [undefined, { debugName: "resourceTemplate" }] : []));
4573
- this.firstDayOfWeek = input('sunday', ...(ngDevMode ? [{ debugName: "firstDayOfWeek" }] : []));
4606
+ this.firstDayOfWeek = input(...(ngDevMode ? [undefined, { debugName: "firstDayOfWeek" }] : []));
4574
4607
  this.tooltipTemplate = input(...(ngDevMode ? [undefined, { debugName: "tooltipTemplate" }] : []));
4575
4608
  this.dataSource = input([], ...(ngDevMode ? [{ debugName: "dataSource" }] : []));
4576
4609
  this.holidays = input(...(ngDevMode ? [undefined, { debugName: "holidays" }] : []));
@@ -4597,6 +4630,14 @@ class AXSchedulerComponent extends NXComponent {
4597
4630
  this.onAppointmentClicked = output();
4598
4631
  this.onAppointmentDblClicked = output();
4599
4632
  this.onAppointmentRightClick = output();
4633
+ /** Applies an explicit `[calendar]` override only when it differs from the active locale profile. */
4634
+ this.#calendarOverrideEffect = effect(() => {
4635
+ const override = this.calendar();
4636
+ const profileSystem = this.localeService.activeProfile().calendar.system;
4637
+ if (override != null && override !== profileSystem) {
4638
+ untracked(() => this.calendarType.set(override));
4639
+ }
4640
+ }, ...(ngDevMode ? [{ debugName: "#calendarOverrideEffect" }] : []));
4600
4641
  this.#appointmentEffect = effect(() => {
4601
4642
  const dataSource = this.dataSource();
4602
4643
  const range = {
@@ -4646,6 +4687,8 @@ class AXSchedulerComponent extends NXComponent {
4646
4687
  this.schedulerService.internalWeekend.set(weekend);
4647
4688
  }, ...(ngDevMode ? [{ debugName: "#weekendEffect" }] : []));
4648
4689
  this.currentDateText = computed(() => {
4690
+ this.translationRevision();
4691
+ this.localeService.activeProfile();
4649
4692
  switch (this.selectedView()) {
4650
4693
  case 'day':
4651
4694
  case 'timeline-day':
@@ -4817,12 +4860,14 @@ class AXSchedulerComponent extends NXComponent {
4817
4860
  }
4818
4861
  ngOnInit() {
4819
4862
  this.calendarService.calendarChanges$.pipe(this.unsubscriber.takeUntilDestroy).subscribe(() => {
4820
- const currentDate = this.calendarService.create(this.currentDate().date, this.calendarType());
4821
- if (!currentDate.equal(this.currentDate())) {
4822
- this.currentDate.set(currentDate);
4823
- }
4863
+ this.syncCurrentDateAfterLocaleChange();
4864
+ });
4865
+ this.localeService.profileChanged$.pipe(this.unsubscriber.takeUntilDestroy).subscribe((profile) => {
4866
+ this.calendarType.set(profile.calendar.system);
4867
+ queueMicrotask(() => this.syncCurrentDateAfterLocaleChange());
4824
4868
  });
4825
4869
  this.translationService.langChanges$.pipe(this.unsubscriber.takeUntilDestroy).subscribe(async () => {
4870
+ this.translationRevision.update((v) => v + 1);
4826
4871
  await this.fillDataSource();
4827
4872
  this.viewModeSelectbox()?.selectItems(this.viewsDataSource().find((c) => c.id == this.selectedView()));
4828
4873
  const currentDate = this.calendarService.create(this.currentDate().date, this.calendarType());
@@ -4830,11 +4875,38 @@ class AXSchedulerComponent extends NXComponent {
4830
4875
  this.currentDate.set(currentDate);
4831
4876
  }
4832
4877
  });
4833
- this.platformService.directionChange
4834
- .pipe(map((event) => event.data === 'rtl'), this.unsubscriber.takeUntilDestroy)
4835
- .subscribe((isRtl) => this.rtl.set(isRtl));
4878
+ this.eventService.onEvent
4879
+ .pipe(filter((event) => event.type === AXSystemEvents.AXLanguageLoaded), this.unsubscriber.takeUntilDestroy)
4880
+ .subscribe(() => this.translationRevision.update((v) => v + 1));
4836
4881
  this.fillDataSource();
4837
4882
  }
4883
+ /** Applies an explicit `[calendar]` override only when it differs from the active locale profile. */
4884
+ #calendarOverrideEffect;
4885
+ /**
4886
+ * Picks a stable anchor inside the visible week so re-aligning after a locale
4887
+ * change does not drift when the previous week start was a Sunday/Monday edge.
4888
+ */
4889
+ getWeekReanchorDate() {
4890
+ const current = this.calendarService.create(this.currentDate().date, this.calendarType());
4891
+ const today = this.calendarService.now(this.calendarType()).startOf('day');
4892
+ const weekEnd = current.add('day', 6);
4893
+ if (today.compare(current, 'day') >= 0 && today.compare(weekEnd, 'day') <= 0) {
4894
+ return today;
4895
+ }
4896
+ return current.add('day', 3);
4897
+ }
4898
+ syncCurrentDateAfterLocaleChange() {
4899
+ let currentDate = this.calendarService.create(this.currentDate().date, this.calendarType());
4900
+ const view = this.selectedView();
4901
+ if (view === 'week' || view === 'timeline-weekly') {
4902
+ currentDate = this.schedulerService.alignToWeekStart(this.getWeekReanchorDate(), {
4903
+ firstDayOfWeek: this.firstDayOfWeek(),
4904
+ });
4905
+ }
4906
+ if (!currentDate.equal(this.currentDate())) {
4907
+ this.currentDate.set(currentDate);
4908
+ }
4909
+ }
4838
4910
  #appointmentEffect;
4839
4911
  #holidayEffect;
4840
4912
  #weekendEffect;
@@ -4848,11 +4920,9 @@ class AXSchedulerComponent extends NXComponent {
4848
4920
  }
4849
4921
  getGridDateRange(dateInMonth) {
4850
4922
  const firstDayOfMonth = dateInMonth.startOf('month');
4851
- const firstDayGridNum = this.schedulerService.getDayOfWeekNumber(this.firstDayOfWeek());
4852
- const firstDisplayedDayOfWeek = firstDayOfMonth.dayOfWeek;
4853
- const daysFromPrevMonth = (firstDisplayedDayOfWeek - firstDayGridNum + 7) % 7;
4854
- const gridStartDate = firstDayOfMonth.add('day', -daysFromPrevMonth);
4855
- // Calculate end date based on standard 6 weeks for safety, or calculate dynamically if preferred
4923
+ const gridStartDate = this.schedulerService.alignToWeekStart(firstDayOfMonth, {
4924
+ firstDayOfWeek: this.firstDayOfWeek(),
4925
+ });
4856
4926
  const gridEndDate = gridStartDate.add('day', 6 * 7 - 1);
4857
4927
  return { gridStartDate, gridEndDate };
4858
4928
  }
@@ -4920,20 +4990,21 @@ class AXSchedulerComponent extends NXComponent {
4920
4990
  this.handleViewChanged(oldView, newView);
4921
4991
  }
4922
4992
  handleViewChanged(oldView, newView) {
4993
+ if (oldView === newView) {
4994
+ return;
4995
+ }
4923
4996
  if (oldView === 'week') {
4924
- const oldDayOfWeek = this.prevDate().dayOfWeek % 7;
4925
- const date = this.currentDate().add('day', oldDayOfWeek - 1);
4926
- this.currentDate.set(date);
4997
+ const startsOn = this.schedulerService.resolveWeekStartsOn(this.firstDayOfWeek());
4998
+ const offset = (this.prevDate().weekdayIndex - startsOn + 7) % 7;
4999
+ this.currentDate.set(this.currentDate().add('day', offset));
4927
5000
  }
4928
5001
  this.prevDate.set(this.currentDate());
4929
- if (newView === 'week') {
4930
- const currentDate = this.currentDate();
4931
- const currentDayOfWeek = currentDate.dayOfWeek;
4932
- const desiredFirstDayName = this.firstDayOfWeek();
4933
- const desiredFirstDayNumber = this.schedulerService.getDayOfWeekNumber(desiredFirstDayName);
4934
- const daysToSubtract = (currentDayOfWeek - desiredFirstDayNumber + 7) % 7;
4935
- const startOfWeekDate = currentDate.add('day', -daysToSubtract);
4936
- this.currentDate.set(startOfWeekDate);
5002
+ if (newView === 'week' || newView === 'timeline-weekly') {
5003
+ const today = this.calendarService.now(this.calendarType()).startOf('day');
5004
+ this.prevDate.set(today);
5005
+ this.currentDate.set(this.schedulerService.alignToWeekStart(today, {
5006
+ firstDayOfWeek: this.firstDayOfWeek(),
5007
+ }));
4937
5008
  }
4938
5009
  this.prevView.set(newView);
4939
5010
  }
@@ -4947,7 +5018,9 @@ class AXSchedulerComponent extends NXComponent {
4947
5018
  this.currentDate.set(this.currentDate().add('day', 1));
4948
5019
  break;
4949
5020
  case 'week':
4950
- this.currentDate.set(this.currentDate().add('week', 1));
5021
+ this.currentDate.set(this.schedulerService.alignToWeekStart(this.currentDate().add('week', 1), {
5022
+ firstDayOfWeek: this.firstDayOfWeek(),
5023
+ }));
4951
5024
  break;
4952
5025
  case 'agenda':
4953
5026
  case 'timeline-weekly':
@@ -4972,7 +5045,9 @@ class AXSchedulerComponent extends NXComponent {
4972
5045
  this.currentDate.set(this.currentDate().add('day', -1));
4973
5046
  break;
4974
5047
  case 'week':
4975
- this.currentDate.set(this.currentDate().add('week', -1));
5048
+ this.currentDate.set(this.schedulerService.alignToWeekStart(this.currentDate().add('week', -1), {
5049
+ firstDayOfWeek: this.firstDayOfWeek(),
5050
+ }));
4976
5051
  break;
4977
5052
  case 'agenda':
4978
5053
  case 'timeline-weekly':
@@ -4995,11 +5070,9 @@ class AXSchedulerComponent extends NXComponent {
4995
5070
  calendarDateChanged(e) {
4996
5071
  let currentDate = this.calendarService.create(e.value, this.calendarType());
4997
5072
  if (this.selectedView() === 'week') {
4998
- const currentDayOfWeek = currentDate.dayOfWeek;
4999
- const desiredFirstDayName = this.firstDayOfWeek();
5000
- const desiredFirstDayNumber = this.schedulerService.getDayOfWeekNumber(desiredFirstDayName);
5001
- const daysToSubtract = (currentDayOfWeek - desiredFirstDayNumber + 7) % 7;
5002
- currentDate = currentDate.add('day', -daysToSubtract);
5073
+ currentDate = this.schedulerService.alignToWeekStart(currentDate, {
5074
+ firstDayOfWeek: this.firstDayOfWeek(),
5075
+ });
5003
5076
  }
5004
5077
  if (!currentDate.equal(this.currentDate())) {
5005
5078
  this.currentDate.set(currentDate);