@dereekb/dbx-form 13.10.5 → 13.10.7

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,13 +6,13 @@ import * as i0 from '@angular/core';
6
6
  import { Injectable, inject, Directive, Optional, SkipSelf, Injector, ChangeDetectionStrategy, Component, viewChild, input, effect, computed, ElementRef, output, InjectionToken, NgModule } from '@angular/core';
7
7
  import { FieldType } from '@ngx-formly/material';
8
8
  import { switchMap, first, tap, map, distinctUntilChanged, shareReplay, of, combineLatestWith, BehaviorSubject, filter, combineLatest, EMPTY, startWith, throttleTime, skip } from 'rxjs';
9
- import { filterMaybe, distinctUntilHasDifferentValues, SubscriptionObject, asObservableFromGetter, asObservable } from '@dereekb/rxjs';
9
+ import { filterMaybe, distinctUntilHasDifferentValues, asObservableFromGetter, asObservable } from '@dereekb/rxjs';
10
10
  import { DateCellScheduleDayCode, expandDateCellScheduleDayCodesToDayOfWeekSet, dateCellTimingStartsAtForStartOfDay, dateCellTimingRelativeIndexFactory, dateCellDayOfWeekFactory, dateTimezoneUtcNormal, findMaxDate, findMinDate, isSameDateRange, isSameDateDay, isSameDate, dateCellTimingDateFactory, expandDateCellScheduleRange, formatToISO8601DayStringForSystem, changeDateCellScheduleDateRangeToTimezone, isSameDateCellScheduleDateRange, dateCellTimingRelativeIndexArrayFactory, isInfiniteDateRange, copyDateCellScheduleDateFilterConfig, SYSTEM_DATE_TIMEZONE_UTC_NORMAL_INSTANCE, dateCellScheduleDateFilter, fullDateCellScheduleRange, dateCellTimingTimezoneNormalInstance, expandDateCellScheduleDayCodes, fullWeekDateCellScheduleDayCodes, dateCellScheduleDayCodesAreSetsEquivalent, simplifyDateCellScheduleDayCodes, dateCellTimingStartDateFactory, isDateInDateRangeFunction, isDateWithinDateCellRangeFunction, dateCellScheduleEncodedWeek, enabledDaysFromDateCellScheduleDayCodes, dateCellScheduleDayCodesFromEnabledDays, formatToMonthDayString, dateRange, DateRangeType } from '@dereekb/date';
11
11
  import { isInAllowedDaysOfWeekSet, mapValuesToSet, unique, mergeArrays, iterableToArray, isIterable, firstValueFromIterable, minAndMaxNumber, range, toggleInSet, removeFromSet, addToSet, isIndexNumberInIndexRangeFunction, getDaysOfWeekNames, reduceBooleansWithAnd, mergeObjects, KeyValueTypleValueFilter, filterFromPOJO } from '@dereekb/util';
12
12
  import { ComponentStore } from '@ngrx/component-store';
13
13
  import { endOfDay, startOfDay, isBefore, endOfWeek } from 'date-fns';
14
14
  import * as i1$1 from '@dereekb/dbx-core';
15
- import { TimezoneAbbreviationPipe, switchMapDbxInjectionComponentConfig, DbxInjectionComponent } from '@dereekb/dbx-core';
15
+ import { completeOnDestroy, cleanSubscription, TimezoneAbbreviationPipe, switchMapDbxInjectionComponentConfig, DbxInjectionComponent } from '@dereekb/dbx-core';
16
16
  import { toSignal, toObservable } from '@angular/core/rxjs-interop';
17
17
  import { MatDialog } from '@angular/material/dialog';
18
18
  import { CalendarMonthViewComponent, CalendarDatePipe } from 'angular-calendar';
@@ -241,6 +241,18 @@ class DbxCalendarScheduleSelectionStore extends ComponentStore {
241
241
  currentSelectionValueStart$ = this.currentSelectionValue$.pipe(map((x) => x?.dateScheduleRange.start), distinctUntilChanged(isSameDate), shareReplay(1));
242
242
  currentSelectionValueDateCellTimingDateFactory$ = this.currentSelectionValue$.pipe(map((x) => (x ? dateCellTimingDateFactory({ startsAt: x.dateScheduleRange.start, timezone: x.dateScheduleRange.timezone }) : undefined)), shareReplay(1));
243
243
  currentSelectionValueDateCellDurationSpanExpansion$ = this.currentSelectionValue$.pipe(map((x) => (x ? expandDateCellScheduleRange({ dateCellScheduleRange: x.dateScheduleRange }) : [])), shareReplay(1));
244
+ /**
245
+ * State-anchored coordinate contract: the emitted indexes are anchored at
246
+ * `state.start` (== `filter.start` when a filter is set, otherwise `today` from
247
+ * `initialCalendarScheduleSelectionState()`). Consumers can use them directly
248
+ * with `state.indexFactory(date)`.
249
+ *
250
+ * This holds because `currentSelectionValue.dateScheduleRange.start` always
251
+ * equals `state.start` for filter-relative outputs, so the expansion's `i`
252
+ * values share the same anchor. If a future change re-introduces a non-
253
+ * `state.start` output anchor, translate output indexes back via
254
+ * `state.indexFactory(dateScheduleRange.start)` here.
255
+ */
244
256
  selectionValueSelectedIndexes$ = this.currentSelectionValueDateCellDurationSpanExpansion$.pipe(map((x) => new Set(x.map((y) => y.i))), distinctUntilHasDifferentValues(), shareReplay(1));
245
257
  selectionValueSelectedDates$ = this.currentSelectionValueDateCellTimingDateFactory$.pipe(switchMap((dateFactory) => {
246
258
  return dateFactory ? this.selectionValueSelectedIndexes$.pipe(map((x) => mapValuesToSet(x, (y) => formatToISO8601DayStringForSystem(dateFactory(y))))) : of(new Set());
@@ -577,11 +589,14 @@ function updateStateWithDateCellScheduleRangeValue(state, inputChange) {
577
589
  return state;
578
590
  }
579
591
  if (change != null) {
580
- // The incoming ex indexes are relative to change.start, but toggledIndexes must be
581
- // relative to state.start. Adjust by the offset between them.
582
- const inputStartIndex = state.indexFactory(change.start);
583
- const adjustedEx = inputStartIndex !== 0 && change.ex ? change.ex.map((i) => i + inputStartIndex) : (change.ex ?? []);
584
- const nextState = { ...state, inputStart: change.start, inputEnd: change.end, toggledIndexes: new Set(adjustedEx) };
592
+ // After the legacy-restored output anchor, change.ex is already state-anchored
593
+ // (change.start equals state.start for filter-relative outputs), so no offset
594
+ // is applied. inputStart is clamped to state.minMaxDateRange.start when set so
595
+ // the round-trip preserves the clamp and pre-clamp days don't re-render via the
596
+ // "toggled outside range" branch.
597
+ const minMaxStart = state.minMaxDateRange?.start;
598
+ const clampedInputStart = minMaxStart && minMaxStart.getTime() > change.start.getTime() ? minMaxStart : change.start;
599
+ const nextState = { ...state, inputStart: clampedInputStart, inputEnd: change.end, toggledIndexes: new Set(change.ex ?? []) };
585
600
  return updateStateWithChangedScheduleDays(finalizeNewCalendarScheduleSelectionState(nextState), expandDateCellScheduleDayCodes(change.w || '89'));
586
601
  }
587
602
  return noSelectionCalendarScheduleSelectionState(state); // clear selection, retain disabled days
@@ -923,26 +938,8 @@ function computeScheduleSelectionValue(state) {
923
938
  const rangeStartIndex = systemIndexFactory(rangeStart);
924
939
  const startIndex = systemIndexFactory(startInSystemTimezone);
925
940
  const filterStartIndexOffset = rangeStartIndex - startIndex;
926
- // When minMaxDateRange constrains the start to after filter.start,
927
- // use rangeStart as the output start instead of filter.start.
928
- // This prevents outputting a start date that violates the minMaxDateRange
929
- // and avoids the round-trip corruption when the value is synced back to the store.
930
- if (filterStartIndexOffset > 0 && state.minMaxDateRange?.start) {
931
- // rangeStart already respects minMaxDateRange. Convert it to the filter's timezone for the output.
932
- if (filter.timezone) {
933
- const filterNormal = dateTimezoneUtcNormal(filter.timezone);
934
- start = filterNormal.startOfDayInTargetTimezone(rangeStart);
935
- }
936
- else {
937
- start = rangeStart;
938
- }
939
- // No filter offset exclusions needed since start is at the selection start.
940
- // indexOffset stays as dateCellRange.i (relative to state.start → rangeStart).
941
- }
942
- else {
943
- filterOffsetExcludedRange = range(0, filterStartIndexOffset);
944
- indexOffset = indexOffset - filterStartIndexOffset;
945
- }
941
+ filterOffsetExcludedRange = range(0, filterStartIndexOffset);
942
+ indexOffset = indexOffset - filterStartIndexOffset;
946
943
  }
947
944
  const excluded = computeSelectionResultRelativeToFilter
948
945
  ? allExcluded.filter((x) => {
@@ -1205,9 +1202,9 @@ class DbxScheduleSelectionCalendarDateRangeComponent {
1205
1202
  this.range.enable();
1206
1203
  }
1207
1204
  }, ...(ngDevMode ? [{ debugName: "_disabledEffect" }] : /* istanbul ignore next */ []));
1208
- _pickerOpened = new BehaviorSubject(false);
1209
- _syncSub = new SubscriptionObject();
1210
- _valueSub = new SubscriptionObject();
1205
+ _pickerOpened = completeOnDestroy(new BehaviorSubject(false));
1206
+ _syncSub = cleanSubscription();
1207
+ _valueSub = cleanSubscription();
1211
1208
  range = new FormGroup({
1212
1209
  start: new FormControl(null),
1213
1210
  end: new FormControl(null)
@@ -1301,10 +1298,6 @@ class DbxScheduleSelectionCalendarDateRangeComponent {
1301
1298
  }
1302
1299
  });
1303
1300
  }
1304
- ngOnDestroy() {
1305
- this._syncSub.destroy();
1306
- this._valueSub.destroy();
1307
- }
1308
1301
  clickedDateRangeInput() {
1309
1302
  if (this.openPickerOnTextClick()) {
1310
1303
  const picker = this.picker();
@@ -1583,7 +1576,7 @@ class DbxScheduleSelectionCalendarComponent {
1583
1576
  clickEvent = output();
1584
1577
  config = input(...(ngDevMode ? [undefined, { debugName: "config" }] : /* istanbul ignore next */ []));
1585
1578
  readonly = input(false, ...(ngDevMode ? [{ debugName: "readonly" }] : /* istanbul ignore next */ []));
1586
- _centerRangeSub = new SubscriptionObject();
1579
+ _centerRangeSub = cleanSubscription();
1587
1580
  config$ = toObservable(this.config).pipe(distinctUntilChanged(), shareReplay(1));
1588
1581
  readonly$ = this.config$.pipe(switchMap((x) => (x?.readonly != null ? asObservableFromGetter(x.readonly) : of(undefined))), combineLatestWith(toObservable(this.readonly)), map(([configReadonly, inputReadonly]) => {
1589
1582
  return configReadonly || inputReadonly || false;
@@ -1650,9 +1643,6 @@ class DbxScheduleSelectionCalendarComponent {
1650
1643
  }
1651
1644
  });
1652
1645
  }
1653
- ngOnDestroy() {
1654
- this._centerRangeSub.destroy();
1655
- }
1656
1646
  dayClicked({ date }) {
1657
1647
  if (!this.readonlySignal()) {
1658
1648
  this.dbxCalendarScheduleSelectionStore.toggleSelectedDates(date);
@@ -1758,14 +1748,14 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.10", ngImpo
1758
1748
  class DbxFormCalendarDateScheduleRangeFieldComponent extends FieldType {
1759
1749
  compact = inject(CompactContextStore, { optional: true });
1760
1750
  dbxCalendarScheduleSelectionStore = inject(DbxCalendarScheduleSelectionStore);
1761
- _syncSub = new SubscriptionObject();
1762
- _valueSub = new SubscriptionObject();
1763
- _timezoneSub = new SubscriptionObject();
1764
- _minMaxDateRangeSub = new SubscriptionObject();
1765
- _defaultWeekSub = new SubscriptionObject();
1766
- _filterSub = new SubscriptionObject();
1767
- _exclusionsSub = new SubscriptionObject();
1768
- _formControlObs = new BehaviorSubject(undefined);
1751
+ _syncSub = cleanSubscription();
1752
+ _valueSub = cleanSubscription();
1753
+ _timezoneSub = cleanSubscription();
1754
+ _minMaxDateRangeSub = cleanSubscription();
1755
+ _defaultWeekSub = cleanSubscription();
1756
+ _filterSub = cleanSubscription();
1757
+ _exclusionsSub = cleanSubscription();
1758
+ _formControlObs = completeOnDestroy(new BehaviorSubject(undefined));
1769
1759
  formControl$ = this._formControlObs.pipe(filterMaybe());
1770
1760
  value$ = this.formControl$.pipe(switchMap((control) => control.valueChanges.pipe(startWith(control.value))), shareReplay(1));
1771
1761
  disableCustomize$ = this.value$.pipe(map((x) => (this.allowCustomizeWithoutDateRange ? false : !x)), distinctUntilChanged(), shareReplay(1));
@@ -1860,13 +1850,6 @@ class DbxFormCalendarDateScheduleRangeFieldComponent extends FieldType {
1860
1850
  }
1861
1851
  ngOnDestroy() {
1862
1852
  super.ngOnDestroy();
1863
- this._syncSub.destroy();
1864
- this._valueSub.destroy();
1865
- this._filterSub.destroy();
1866
- this._timezoneSub.destroy();
1867
- this._minMaxDateRangeSub.destroy();
1868
- this._exclusionsSub.destroy();
1869
- this._formControlObs.complete();
1870
1853
  }
1871
1854
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: DbxFormCalendarDateScheduleRangeFieldComponent, deps: null, target: i0.ɵɵFactoryTarget.Component });
1872
1855
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "21.2.10", type: DbxFormCalendarDateScheduleRangeFieldComponent, isStandalone: true, selector: "ng-component", providers: [provideCalendarScheduleSelectionStoreIfParentIsUnavailable()], usesInheritance: true, ngImport: i0, template: `
@@ -1987,13 +1970,13 @@ class DbxForgeCalendarDateScheduleRangeFieldComponent {
1987
1970
  validationMessages = input(...(ngDevMode ? [undefined, { debugName: "validationMessages" }] : /* istanbul ignore next */ []));
1988
1971
  defaultValidationMessages = input(...(ngDevMode ? [undefined, { debugName: "defaultValidationMessages" }] : /* istanbul ignore next */ []));
1989
1972
  // Subscription management
1990
- _syncSub = new SubscriptionObject();
1991
- _valueSub = new SubscriptionObject();
1992
- _timezoneSub = new SubscriptionObject();
1993
- _minMaxDateRangeSub = new SubscriptionObject();
1994
- _defaultWeekSub = new SubscriptionObject();
1995
- _filterSub = new SubscriptionObject();
1996
- _exclusionsSub = new SubscriptionObject();
1973
+ _syncSub = cleanSubscription();
1974
+ _valueSub = cleanSubscription();
1975
+ _timezoneSub = cleanSubscription();
1976
+ _minMaxDateRangeSub = cleanSubscription();
1977
+ _defaultWeekSub = cleanSubscription();
1978
+ _filterSub = cleanSubscription();
1979
+ _exclusionsSub = cleanSubscription();
1997
1980
  // Field value signal (double-call pattern: field()() to get FieldState)
1998
1981
  fieldValue = computed(() => {
1999
1982
  const state = this.field()?.();
@@ -2080,15 +2063,6 @@ class DbxForgeCalendarDateScheduleRangeFieldComponent {
2080
2063
  }
2081
2064
  });
2082
2065
  }
2083
- ngOnDestroy() {
2084
- this._syncSub.destroy();
2085
- this._valueSub.destroy();
2086
- this._filterSub.destroy();
2087
- this._timezoneSub.destroy();
2088
- this._minMaxDateRangeSub.destroy();
2089
- this._defaultWeekSub.destroy();
2090
- this._exclusionsSub.destroy();
2091
- }
2092
2066
  static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.10", ngImport: i0, type: DbxForgeCalendarDateScheduleRangeFieldComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
2093
2067
  static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.1.0", version: "21.2.10", type: DbxForgeCalendarDateScheduleRangeFieldComponent, isStandalone: true, selector: "dbx-forge-calendar-date-schedule-range-field", inputs: { field: { classPropertyName: "field", publicName: "field", isSignal: true, isRequired: true, transformFunction: null }, key: { classPropertyName: "key", publicName: "key", isSignal: true, isRequired: true, transformFunction: null }, label: { classPropertyName: "label", publicName: "label", isSignal: true, isRequired: false, transformFunction: null }, placeholder: { classPropertyName: "placeholder", publicName: "placeholder", isSignal: true, isRequired: false, transformFunction: null }, className: { classPropertyName: "className", publicName: "className", isSignal: true, isRequired: false, transformFunction: null }, tabIndex: { classPropertyName: "tabIndex", publicName: "tabIndex", isSignal: true, isRequired: false, transformFunction: null }, props: { classPropertyName: "props", publicName: "props", isSignal: true, isRequired: false, transformFunction: null }, meta: { classPropertyName: "meta", publicName: "meta", isSignal: true, isRequired: false, transformFunction: null }, validationMessages: { classPropertyName: "validationMessages", publicName: "validationMessages", isSignal: true, isRequired: false, transformFunction: null }, defaultValidationMessages: { classPropertyName: "defaultValidationMessages", publicName: "defaultValidationMessages", isSignal: true, isRequired: false, transformFunction: null } }, providers: [provideCalendarScheduleSelectionStoreIfParentIsUnavailable()], ngImport: i0, template: `
2094
2068
  <div class="dbx-schedule-selection-field">